import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../models/training_plan.dart'; import '../services/training_plan_service.dart'; const _green = Color(0xFF456C52); const _ink = Color(0xFF17251E); const _muted = Color(0xFF778078); class TrainingLibraryScreen extends StatefulWidget { const TrainingLibraryScreen({super.key}); @override State createState() => _TrainingLibraryScreenState(); } class _TrainingLibraryScreenState extends State { late Future _plans; @override void initState() { super.initState(); _plans = Get.find().fetchPlans(); } void _reload() => setState(() { _plans = Get.find().fetchPlans(refresh: true); }); @override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text('خطط التمرين')), body: FutureBuilder( future: _plans, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } final result = snapshot.data; if (result == null) { return _CatalogMessage( message: 'تعذر تحميل مكتبة التمارين.', onRetry: _reload, ); } if (result.plans.isEmpty) { return _CatalogMessage( message: result.message ?? 'لا توجد خطط منشورة بعد. ستظهر الخطط هنا بعد إضافتها من لوحة الإدارة.', onRetry: _reload, ); } return ListView( padding: const EdgeInsets.all(18), children: [ if (result.fromCache) const _CacheNotice( message: 'نعرض نسخة محفوظة على هذا الجهاز.'), if (result.message != null) Padding( padding: const EdgeInsets.only(bottom: 12), child: Text(result.message!, style: const TextStyle(color: _muted, height: 1.5)), ), for (final plan in result.plans) _PlanCard( plan: plan, onTap: () => Get.to(() => TrainingPlanDetailScreen(plan: plan)), ), ], ); }, ), ); } class TrainingPlanSpotlight extends StatefulWidget { const TrainingPlanSpotlight({super.key}); @override State createState() => _TrainingPlanSpotlightState(); } class _TrainingPlanSpotlightState extends State { late Future _plans; @override void initState() { super.initState(); _plans = Get.find().fetchPlans(); } @override Widget build(BuildContext context) => FutureBuilder( future: _plans, builder: (context, snapshot) { final result = snapshot.data; final plan = result?.plans.isNotEmpty == true ? result!.plans.first : null; return Container( padding: const EdgeInsets.all(19), decoration: BoxDecoration( color: _green, borderRadius: BorderRadius.circular(21), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ const Icon(Icons.fitness_center, color: Colors.white70), const SizedBox(width: 9), Expanded( child: Text( plan?.title ?? 'خطتك التدريبية', style: const TextStyle( color: Colors.white, fontSize: 17, fontWeight: FontWeight.w600, ), ), ), if (result?.fromCache == true) const Icon(Icons.offline_bolt_outlined, color: Colors.white70, size: 19), ]), const SizedBox(height: 8), Text( plan == null ? 'استعرض البرامج والجلسات والشرح المصوّر للحركات.' : plan.summary.isNotEmpty ? plan.summary : '${plan.weeksDuration} أسابيع • ${plan.sessions.length} جلسة', style: const TextStyle( color: Color(0xFFDCE8DA), fontSize: 12, height: 1.6), ), const SizedBox(height: 13), Align( alignment: AlignmentDirectional.centerEnd, child: TextButton.icon( onPressed: () => Get.to(() => const TrainingLibraryScreen()), style: TextButton.styleFrom( foregroundColor: _green, backgroundColor: Colors.white), icon: const Icon(Icons.arrow_forward, size: 16), label: Text(plan == null ? 'فتح مكتبة التمارين' : 'عرض الخطة'), ), ), ], ), ); }, ); } class TrainingPlanDetailScreen extends StatelessWidget { const TrainingPlanDetailScreen({super.key, required this.plan}); final TrainingPlan plan; @override Widget build(BuildContext context) { final sessions = [...plan.sessions]..sort((a, b) => a.week != b.week ? a.week.compareTo(b.week) : a.day.compareTo(b.day)); return Scaffold( appBar: AppBar(title: const Text('تفاصيل الخطة')), body: ListView( padding: const EdgeInsets.fromLTRB(18, 8, 18, 28), children: [ Text(plan.title, style: const TextStyle( color: _ink, fontSize: 25, fontWeight: FontWeight.w700)), if (plan.summary.isNotEmpty) ...[ const SizedBox(height: 8), Text(plan.summary, style: const TextStyle(color: _muted, height: 1.6)), ], const SizedBox(height: 12), Wrap(spacing: 8, runSpacing: 8, children: [ _InfoChip('${plan.weeksDuration} أسابيع'), _InfoChip(_levelLabel(plan.level)), if (plan.goal.isNotEmpty) _InfoChip(_goalLabel(plan.goal)), ]), const SizedBox(height: 22), if (sessions.isEmpty) const Text('لم تتم إضافة جلسات لهذه الخطة بعد.', style: TextStyle(color: _muted)) else for (final session in sessions) _SessionCard(session: session), if (plan.safetyNotes.isNotEmpty) ...[ const SizedBox(height: 12), _NotesBox(title: 'ملاحظات السلامة', notes: plan.safetyNotes), ], ], ), ); } } class _SessionCard extends StatelessWidget { const _SessionCard({required this.session}); final TrainingSession session; @override Widget build(BuildContext context) => Card( margin: const EdgeInsets.only(bottom: 11), child: Padding( padding: const EdgeInsets.all(15), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('الأسبوع ${session.week} • اليوم ${session.day}', style: const TextStyle(color: _green, fontSize: 11)), const SizedBox(height: 4), Text(session.title, style: const TextStyle( color: _ink, fontSize: 16, fontWeight: FontWeight.w600)), const SizedBox(height: 5), Text( '${session.durationMinutes} دقيقة • ${_intensityLabel(session.intensity)}', style: const TextStyle(color: _muted, fontSize: 12)), if (session.notes?.isNotEmpty == true) ...[ const SizedBox(height: 7), Text(session.notes!, style: const TextStyle(color: _muted, height: 1.5)), ], if (session.exercises.isNotEmpty) ...[ const Divider(height: 22), for (final exercise in session.exercises) ListTile( contentPadding: EdgeInsets.zero, leading: const CircleAvatar( backgroundColor: Color(0xFFE8EFE5), child: Icon(Icons.play_arrow, color: _green)), title: Text(exercise.title, style: const TextStyle( color: _ink, fontWeight: FontWeight.w600)), subtitle: Text(_prescription(exercise), style: const TextStyle(color: _muted)), trailing: const Icon(Icons.chevron_right), onTap: () => Get.to( () => TrainingExerciseDetailScreen(exercise: exercise)), ), ], ]), ), ); } class TrainingExerciseDetailScreen extends StatelessWidget { const TrainingExerciseDetailScreen({super.key, required this.exercise}); final TrainingExercise exercise; @override Widget build(BuildContext context) { final gif = _httpsUri(exercise.gifUrl); final poster = _httpsUri(exercise.posterUrl); return Scaffold( appBar: AppBar(title: const Text('شرح الحركة')), body: ListView( padding: const EdgeInsets.fromLTRB(18, 8, 18, 28), children: [ Text(exercise.title, style: const TextStyle( color: _ink, fontSize: 24, fontWeight: FontWeight.w700)), const SizedBox(height: 14), ClipRRect( borderRadius: BorderRadius.circular(18), child: AspectRatio( aspectRatio: 4 / 3, child: gif == null ? _GifPlaceholder(poster: poster, title: exercise.title) : Image.network( gif.toString(), fit: BoxFit.contain, semanticLabel: 'رسم متحرك لشرح ${exercise.title}', loadingBuilder: (context, child, progress) => progress == null ? child : _GifPlaceholder( poster: poster, title: exercise.title), errorBuilder: (context, error, stack) => _GifPlaceholder( poster: poster, title: exercise.title), ), ), ), const SizedBox(height: 14), Wrap(spacing: 7, runSpacing: 7, children: [ if (exercise.prescription.sets != null) _InfoChip('${exercise.prescription.sets} مجموعات'), if (exercise.prescription.reps?.isNotEmpty == true) _InfoChip('${exercise.prescription.reps} تكرار'), if (exercise.prescription.durationSeconds != null) _InfoChip('${exercise.prescription.durationSeconds} ثانية'), if (exercise.prescription.restSeconds > 0) _InfoChip('راحة ${exercise.prescription.restSeconds} ث'), ]), if (exercise.instructions.isNotEmpty) ...[ const SizedBox(height: 22), const Text('طريقة الأداء', style: TextStyle( color: _ink, fontSize: 17, fontWeight: FontWeight.w600)), const SizedBox(height: 7), for (var i = 0; i < exercise.instructions.length; i++) Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('${i + 1}. ', style: const TextStyle( color: _green, fontWeight: FontWeight.w700)), Expanded( child: Text(exercise.instructions[i], style: const TextStyle( color: _ink, height: 1.55))), ]), ), ], if (exercise.targetMuscles.isNotEmpty) _TagSection( title: 'العضلات المستهدفة', values: exercise.targetMuscles), if (exercise.equipment.isNotEmpty) _TagSection(title: 'الأدوات', values: exercise.equipment), if (exercise.safetyNotes.isNotEmpty) ...[ const SizedBox(height: 18), _NotesBox(title: 'تنبيهات السلامة', notes: exercise.safetyNotes), ], if (exercise.alternativeUuid != null) ...[ const SizedBox(height: 12), const Text('يتوفر بديل لهذه الحركة ضمن مكتبة التمارين.', style: TextStyle(color: _muted, height: 1.5)), ], ]), ); } } class _PlanCard extends StatelessWidget { const _PlanCard({required this.plan, required this.onTap}); final TrainingPlan plan; final VoidCallback onTap; @override Widget build(BuildContext context) => Card( margin: const EdgeInsets.only(bottom: 12), child: ListTile( contentPadding: const EdgeInsets.all(14), leading: const CircleAvatar( backgroundColor: Color(0xFFE8EFE5), child: Icon(Icons.fitness_center, color: _green)), title: Text(plan.title, style: const TextStyle(color: _ink, fontWeight: FontWeight.w600)), subtitle: Padding( padding: const EdgeInsets.only(top: 5), child: Text( '${plan.weeksDuration} أسابيع • ${plan.sessions.length} جلسة${plan.summary.isNotEmpty ? '\n${plan.summary}' : ''}', style: const TextStyle(color: _muted, height: 1.45), ), ), trailing: const Icon(Icons.chevron_right), onTap: onTap, ), ); } class _CatalogMessage extends StatelessWidget { const _CatalogMessage({required this.message, required this.onRetry}); final String message; final VoidCallback onRetry; @override Widget build(BuildContext context) => Center( child: Padding( padding: const EdgeInsets.all(30), child: Column(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.fitness_center, color: _green, size: 40), const SizedBox(height: 14), Text(message, textAlign: TextAlign.center, style: const TextStyle(color: _ink, height: 1.6)), const SizedBox(height: 10), OutlinedButton.icon( onPressed: onRetry, icon: const Icon(Icons.refresh), label: const Text('إعادة المحاولة')), ]), ), ); } class _CacheNotice extends StatelessWidget { const _CacheNotice({required this.message}); final String message; @override Widget build(BuildContext context) => Padding( padding: const EdgeInsets.only(bottom: 12), child: Row(children: [ const Icon(Icons.offline_bolt_outlined, color: _green, size: 17), const SizedBox(width: 7), Text(message, style: const TextStyle(color: _muted, fontSize: 12)), ]), ); } class _GifPlaceholder extends StatelessWidget { const _GifPlaceholder({this.poster, required this.title}); final Uri? poster; final String title; @override Widget build(BuildContext context) => Container( color: const Color(0xFFE8EFE5), child: poster == null ? const Center( child: Icon(Icons.motion_photos_on_outlined, color: _green, size: 52)) : Image.network(poster.toString(), fit: BoxFit.contain, semanticLabel: 'صورة توضيحية لحركة $title', errorBuilder: (context, error, stack) => const Center( child: Icon(Icons.motion_photos_on_outlined, color: _green, size: 52))), ); } class _TagSection extends StatelessWidget { const _TagSection({required this.title, required this.values}); final String title; final List values; @override Widget build(BuildContext context) => Padding( padding: const EdgeInsets.only(top: 18), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: const TextStyle( color: _ink, fontSize: 16, fontWeight: FontWeight.w600)), const SizedBox(height: 8), Wrap( spacing: 7, runSpacing: 7, children: values.map((value) => _InfoChip(value)).toList()), ]), ); } class _NotesBox extends StatelessWidget { const _NotesBox({required this.title, required this.notes}); final String title; final List notes; @override Widget build(BuildContext context) => Container( padding: const EdgeInsets.all(15), decoration: BoxDecoration( color: const Color(0xFFE8EEE5), borderRadius: BorderRadius.circular(15)), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: const TextStyle(color: _green, fontWeight: FontWeight.w600)), const SizedBox(height: 6), for (final note in notes) Padding( padding: const EdgeInsets.only(top: 4), child: Text('• $note', style: const TextStyle(color: _green, height: 1.5)), ), ]), ); } class _InfoChip extends StatelessWidget { const _InfoChip(this.label); final String label; @override Widget build(BuildContext context) => Chip( label: Text(label, style: const TextStyle(color: _green, fontSize: 11)), backgroundColor: const Color(0xFFE8EFE5), side: BorderSide.none, visualDensity: VisualDensity.compact, ); } String _prescription(TrainingExercise exercise) { final values = []; if (exercise.prescription.sets != null) { values.add('${exercise.prescription.sets} مجموعات'); } if (exercise.prescription.reps?.isNotEmpty == true) { values.add('${exercise.prescription.reps} تكرار'); } if (values.isEmpty && exercise.durationSeconds != null) { values.add('${exercise.durationSeconds} ثانية'); } return values.isEmpty ? 'تفاصيل الحركة' : values.join(' • '); } String _levelLabel(String level) => switch (level) { 'beginner' => 'مبتدئ', 'intermediate' => 'متوسط', 'advanced' => 'متقدم', _ => level, }; String _goalLabel(String goal) => switch (goal) { 'weight_loss' => 'خفض الوزن', 'general_fitness' => 'لياقة عامة', 'strength' => 'قوة', 'mobility' => 'مرونة وحركة', _ => goal, }; String _intensityLabel(String intensity) => switch (intensity) { 'easy' => 'خفيفة', 'moderate' => 'متوسطة', 'hard' => 'عالية', _ => intensity, }; Uri? _httpsUri(String? value) { if (value == null || value.isEmpty) return null; final uri = Uri.tryParse(value); return uri != null && uri.scheme == 'https' && uri.host.isNotEmpty ? uri : null; }