import 'package:flutter/material.dart'; import 'package:trainhub_flutter/core/theme/app_colors.dart'; import 'package:trainhub_flutter/core/constants/ui_constants.dart'; import 'package:trainhub_flutter/core/utils/id_generator.dart'; import 'package:trainhub_flutter/domain/entities/program_week.dart'; import 'package:trainhub_flutter/domain/entities/program_workout.dart'; import 'package:trainhub_flutter/domain/entities/training_plan.dart'; class ProgramWeekView extends StatelessWidget { final ProgramWeekEntity week; final List workouts; final List availablePlans; final Function(ProgramWorkoutEntity) onAddWorkout; final Function(String) onDeleteWorkout; const ProgramWeekView({ super.key, required this.week, required this.workouts, required this.availablePlans, required this.onAddWorkout, required this.onDeleteWorkout, }); @override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.only(bottom: 24), elevation: 2, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Week ${week.position}', style: Theme.of(context).textTheme.headlineSmall, ), const Divider(), SizedBox( height: 500, child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: List.generate(7, (dayIndex) { final dayNum = dayIndex + 1; final dayWorkouts = workouts .where((w) => w.day == dayNum.toString()) .toList(); return Expanded( child: _DayColumn( dayNum: dayNum, dayIndex: dayIndex, dayWorkouts: dayWorkouts, availablePlans: availablePlans, week: week, onAddWorkout: onAddWorkout, onDeleteWorkout: onDeleteWorkout, ), ); }), ), ), ], ), ), ); } } class _DayColumn extends StatelessWidget { final int dayNum; final int dayIndex; final List dayWorkouts; final List availablePlans; final ProgramWeekEntity week; final Function(ProgramWorkoutEntity) onAddWorkout; final Function(String) onDeleteWorkout; const _DayColumn({ required this.dayNum, required this.dayIndex, required this.dayWorkouts, required this.availablePlans, required this.week, required this.onAddWorkout, required this.onDeleteWorkout, }); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( border: dayIndex < 6 ? const Border( right: BorderSide(color: Colors.grey, width: 0.5), ) : null, color: dayIndex % 2 == 0 ? Theme.of(context).colorScheme.surfaceContainerLow : null, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainerHigh, border: const Border( bottom: BorderSide(color: Colors.grey, width: 0.5), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( _getDayName(dayNum), style: const TextStyle(fontWeight: FontWeight.bold), ), InkWell( onTap: () => _showAddWorkoutSheet(context), borderRadius: BorderRadius.circular(16), child: const Icon(Icons.add_circle_outline, size: 20), ), ], ), ), Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(4), child: Column( children: [ if (dayWorkouts.isEmpty) Padding( padding: const EdgeInsets.only(top: 16.0), child: Center( child: Text( 'Rest', style: TextStyle( color: Theme.of( context, ).colorScheme.onSurfaceVariant, fontSize: 12, ), ), ), ) else ...dayWorkouts.map( (workout) => _WorkoutCard( workout: workout, onDelete: () => onDeleteWorkout(workout.id), ), ), ], ), ), ), ], ), ); } String _getDayName(int day) { const days = [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun', ]; if (day >= 1 && day <= 7) return days[day - 1]; return 'Day $day'; } void _showAddWorkoutSheet(BuildContext context) { showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => _AddWorkoutSheet( availablePlans: availablePlans, week: week, dayNum: dayNum, onAddWorkout: (workout) { onAddWorkout(workout); Navigator.pop(context); }, ), ); } } class _WorkoutCard extends StatelessWidget { final ProgramWorkoutEntity workout; final VoidCallback onDelete; const _WorkoutCard({required this.workout, required this.onDelete}); @override Widget build(BuildContext context) { final isNote = workout.type == 'note'; return Card( margin: const EdgeInsets.only(bottom: 4), color: isNote ? AppColors.info.withValues(alpha: 0.08) : null, child: ListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 8, vertical: 0, ), visualDensity: VisualDensity.compact, leading: Icon( isNote ? Icons.sticky_note_2_outlined : Icons.fitness_center, size: 14, color: isNote ? AppColors.info : AppColors.textMuted, ), title: Text( workout.name ?? 'Untitled', style: const TextStyle(fontSize: 12), maxLines: 2, overflow: TextOverflow.ellipsis, ), subtitle: workout.description != null && workout.description!.isNotEmpty ? Text( workout.description!, style: const TextStyle(fontSize: 10), maxLines: 1, overflow: TextOverflow.ellipsis, ) : null, trailing: InkWell( onTap: onDelete, borderRadius: BorderRadius.circular(12), child: const Icon(Icons.close, size: 14), ), ), ); } } class _AddWorkoutSheet extends StatefulWidget { final List availablePlans; final ProgramWeekEntity week; final int dayNum; final Function(ProgramWorkoutEntity) onAddWorkout; const _AddWorkoutSheet({ required this.availablePlans, required this.week, required this.dayNum, required this.onAddWorkout, }); @override State<_AddWorkoutSheet> createState() => _AddWorkoutSheetState(); } class _AddWorkoutSheetState extends State<_AddWorkoutSheet> with SingleTickerProviderStateMixin { late final TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); } @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(vertical: 16), constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.6, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), child: Text( 'Add to Schedule', style: Theme.of(context).textTheme.titleMedium, ), ), TabBar( controller: _tabController, tabs: const [ Tab( icon: Icon(Icons.fitness_center, size: 16), text: 'Training Plan', ), Tab( icon: Icon(Icons.sticky_note_2_outlined, size: 16), text: 'Note', ), ], ), const Divider(height: 1), Expanded( child: TabBarView( controller: _tabController, children: [ _PlanPickerTab( availablePlans: widget.availablePlans, week: widget.week, dayNum: widget.dayNum, onAddWorkout: widget.onAddWorkout, ), _NoteTab( week: widget.week, dayNum: widget.dayNum, onAddWorkout: widget.onAddWorkout, ), ], ), ), ], ), ); } } class _PlanPickerTab extends StatelessWidget { final List availablePlans; final ProgramWeekEntity week; final int dayNum; final Function(ProgramWorkoutEntity) onAddWorkout; const _PlanPickerTab({ required this.availablePlans, required this.week, required this.dayNum, required this.onAddWorkout, }); @override Widget build(BuildContext context) { if (availablePlans.isEmpty) { return const Padding( padding: EdgeInsets.all(16.0), child: Center( child: Text('No training plans available. Create one first!'), ), ); } return ListView.builder( itemCount: availablePlans.length, itemBuilder: (context, index) { final plan = availablePlans[index]; return ListTile( leading: const Icon(Icons.fitness_center), title: Text(plan.name), subtitle: Text('${plan.totalExercises} exercises'), onTap: () { final newWorkout = ProgramWorkoutEntity( id: IdGenerator.generate(), programId: week.programId, weekId: week.id, day: dayNum.toString(), type: 'workout', name: plan.name, refId: plan.id, description: '${plan.sections.length} sections', completed: false, ); onAddWorkout(newWorkout); }, ); }, ); } } class _NoteTab extends StatefulWidget { final ProgramWeekEntity week; final int dayNum; final Function(ProgramWorkoutEntity) onAddWorkout; const _NoteTab({ required this.week, required this.dayNum, required this.onAddWorkout, }); @override State<_NoteTab> createState() => _NoteTabState(); } class _NoteTabState extends State<_NoteTab> { final TextEditingController _titleController = TextEditingController(); final TextEditingController _contentController = TextEditingController(); @override void dispose() { _titleController.dispose(); _contentController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(UIConstants.spacing16), child: Column( children: [ TextField( controller: _titleController, decoration: const InputDecoration( labelText: 'Title', hintText: 'e.g. Active Recovery', ), autofocus: true, ), const SizedBox(height: UIConstants.spacing12), TextField( controller: _contentController, decoration: const InputDecoration( labelText: 'Note (optional)', hintText: 'Additional details...', ), maxLines: 3, ), const SizedBox(height: UIConstants.spacing16), SizedBox( width: double.infinity, child: FilledButton.icon( onPressed: () { if (_titleController.text.isEmpty) return; final newWorkout = ProgramWorkoutEntity( id: IdGenerator.generate(), programId: widget.week.programId, weekId: widget.week.id, day: widget.dayNum.toString(), type: 'note', name: _titleController.text, description: _contentController.text.isEmpty ? null : _contentController.text, completed: false, ); widget.onAddWorkout(newWorkout); }, icon: const Icon(Icons.add), label: const Text('Add Note'), ), ), ], ), ); } }