import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'workout_controller.dart'; /// Main Workout Tracking Screen class WorkoutTrackingScreen extends StatefulWidget { const WorkoutTrackingScreen({Key? key}) : super(key: key); @override State createState() => _WorkoutTrackingScreenState(); } class _WorkoutTrackingScreenState extends State { final WorkoutController workoutController = Get.put(WorkoutController()); final MapController mapController = MapController(); final List markers = []; List polylinePoints = []; @override void initState() { super.initState(); } void _updateMapWithCoordinates() { if (workoutController.coordinates.isEmpty) return; final latestCoord = workoutController.coordinates.last; // Update markers markers.clear(); markers.add( Marker( point: LatLng(latestCoord.latitude, latestCoord.longitude), width: 80, height: 80, child: const Icon( Icons.location_on, color: Colors.deepOrange, size: 32, ), ), ); // Update polyline polylinePoints = workoutController.coordinates .map((c) => LatLng(c.latitude, c.longitude)) .toList(); // Center map on user mapController.move( LatLng(latestCoord.latitude, latestCoord.longitude), 15, ); setState(() {}); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Workout Tracking'), backgroundColor: Colors.deepOrange, elevation: 0, ), body: GetBuilder( builder: (controller) { _updateMapWithCoordinates(); return Stack( children: [ // OpenStreetMap with flutter_map FlutterMap( mapController: mapController, options: MapOptions( initialCenter: LatLng(37.7749, -122.4194), // San Francisco initialZoom: 15, ), children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.fitness.app', ), PolylineLayer( polylines: polylinePoints.length > 1 ? [ Polyline( points: polylinePoints, strokeWidth: 3, color: const Color(0xFFFF5722), ), ] : [], ), MarkerLayer( markers: markers, ), ], ), // Statistics Panel Positioned( top: 0, left: 0, right: 0, child: _buildStatsPanel(controller), ), // Control Buttons Panel Positioned( bottom: 0, left: 0, right: 0, child: _buildControlPanel(controller), ), ], ); }, ), ); } Widget _buildStatsPanel(WorkoutController controller) { return Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Time Text( formatTime(controller.elapsedTime), style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, color: Colors.deepOrange, ), ), const SizedBox(height: 12), // Distance and Pace Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildStatItem( label: 'Distance', value: formatDistance(controller.distance), icon: Icons.directions_run, ), Container( height: 40, width: 1, color: Colors.grey.shade300, ), _buildStatItem( label: 'Pace', value: formatPace(controller.currentSpeed), icon: Icons.speed, ), ], ), ], ), ); } Widget _buildStatItem({ required String label, required String value, required IconData icon, }) { return Expanded( child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 16, color: Colors.deepOrange), const SizedBox(width: 6), Text( label, style: TextStyle( fontSize: 12, color: Colors.grey.shade600, ), ), ], ), const SizedBox(height: 6), Text( value, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black, ), ), ], ), ); } Widget _buildControlPanel(WorkoutController controller) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 12, offset: const Offset(0, -2), ), ], ), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Workout Type Selector (only available before start) if (!controller.isTracking) Container( margin: const EdgeInsets.only(bottom: 20), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.all(4), child: Row( children: [ _buildWorkoutTypeButton( controller, 'running', '🏃 Running', ), _buildWorkoutTypeButton( controller, 'walking', '🚶 Walking', ), ], ), ), // Main Action Buttons Row( children: [ if (!controller.isTracking) ...[ // Start Button Expanded( child: ElevatedButton.icon( onPressed: () => _startWorkout(controller), icon: const Icon(Icons.play_arrow), label: const Text('Start'), style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), ] else ...[ // Pause/Resume Button Expanded( child: ElevatedButton.icon( onPressed: () => _togglePause(controller), icon: Icon( controller.isPaused ? Icons.play_arrow : Icons.pause), label: Text(controller.isPaused ? 'Resume' : 'Pause'), style: ElevatedButton.styleFrom( backgroundColor: Colors.orange, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), const SizedBox(width: 12), // Stop Button Expanded( child: ElevatedButton.icon( onPressed: () => _stopAndSubmit(controller), icon: const Icon(Icons.stop), label: const Text('Stop'), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), ], ], ), // Loading Indicator for submission if (controller.isSubmitting) ...[ const SizedBox(height: 16), const Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Colors.deepOrange), ), ), ], // Warning Message if (!controller.isTracking && controller.coordinates.isEmpty) Padding( padding: const EdgeInsets.only(top: 12), child: Text( 'Enable location services to start tracking', style: TextStyle( fontSize: 12, color: Colors.grey.shade600, fontStyle: FontStyle.italic, ), textAlign: TextAlign.center, ), ), ], ), ); } Widget _buildWorkoutTypeButton( WorkoutController controller, String type, String label, ) { final isSelected = controller.currentWorkout.workoutType == type; return Expanded( child: InkWell( onTap: () => controller.setWorkoutType(type), child: Container( decoration: BoxDecoration( color: isSelected ? Colors.deepOrange : Colors.transparent, borderRadius: BorderRadius.circular(10), ), padding: const EdgeInsets.symmetric(vertical: 10), child: Text( label, textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.w600, color: isSelected ? Colors.white : Colors.grey.shade700, ), ), ), ), ); } void _startWorkout(WorkoutController controller) { controller.startWorkout(); // Map updates will happen through GetBuilder rebuild _updateMapWithCoordinates(); } void _togglePause(WorkoutController controller) { if (controller.isPaused) { controller.resumeWorkout(); } else { controller.pauseWorkout(); } } void _stopAndSubmit(WorkoutController controller) async { await controller.stopWorkout(); if (!mounted) return; // Show workout summary and submission dialog Get.dialog( WorkoutSummaryDialog( controller: controller, ), barrierDismissible: false, ); } @override void dispose() { super.dispose(); } } /// Workout Summary and Submission Dialog class WorkoutSummaryDialog extends StatelessWidget { final WorkoutController controller; const WorkoutSummaryDialog({ Key? key, required this.controller, }) : super(key: key); @override Widget build(BuildContext context) { return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'Workout Complete!', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.deepOrange, ), ), const SizedBox(height: 20), // Summary Stats _buildSummaryRow('Type', controller.currentWorkout.workoutType), _buildSummaryRow('Distance', formatDistance(controller.distance)), _buildSummaryRow('Duration', formatTime(controller.elapsedTime)), _buildSummaryRow( 'Max Speed', '${controller.maxSpeed.toStringAsFixed(2)} m/s'), _buildSummaryRow( 'Calories', controller.currentWorkout.duration > 0 ? '${(controller.distance / 1000 * (controller.currentWorkout.workoutType == 'running' ? 100 : 50)).toStringAsFixed(0)}' : '0', ), const SizedBox(height: 24), // Action Buttons Row( children: [ Expanded( child: OutlinedButton( onPressed: () { Get.back(); // Close dialog }, style: OutlinedButton.styleFrom( foregroundColor: Colors.grey, side: const BorderSide(color: Colors.grey), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), child: const Text('Discard'), ), ), const SizedBox(width: 12), Expanded( child: GetBuilder( builder: (ctrl) { return ElevatedButton( onPressed: ctrl.isSubmitting ? null : () => _submitWorkout(controller, context), style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, disabledBackgroundColor: Colors.grey, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), child: ctrl.isSubmitting ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( Colors.white), ), ) : const Text('Submit Workout'), ); }, ), ), ], ), ], ), ), ), ); } Widget _buildSummaryRow(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle( fontSize: 14, color: Colors.grey.shade600, ), ), Text( value, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black, ), ), ], ), ); } Future _submitWorkout( WorkoutController controller, BuildContext context, ) async { // In production, pass actual API endpoint final success = await controller.submitWorkout( 'https://your-api.com/api/v1/workouts', ); if (!context.mounted) return; if (success) { Get.back(); // Close dialog Get.snackbar( 'Success', 'Workout submitted successfully!', backgroundColor: Colors.green, colorText: Colors.white, duration: const Duration(seconds: 3), ); } else { Get.snackbar( 'Error', 'Failed to submit workout. Please try again.', backgroundColor: Colors.red, colorText: Colors.white, duration: const Duration(seconds: 3), ); } } }