import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:get/get.dart'; import 'package:latlong2/latlong.dart'; import '../controllers/tracking_controller.dart'; import '../../../../models/track.dart'; import 'track_list_view.dart'; class TrackingView extends GetView { const TrackingView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( extendBody: true, appBar: AppBar( title: const Text('Nyomvonal'), actions: [ IconButton( icon: const Icon(Icons.list_alt), tooltip: 'Mentett track-ek', onPressed: () => Get.to(() => const TrackListView()), ), ], ), body: Stack( children: [ _LiveMap(), _StatsPanel(), ], ), bottomNavigationBar: _ControlBar(), ); } } // ─── Térkép ───────────────────────────────────────────────────────────────── class _LiveMap extends GetView { const _LiveMap(); @override Widget build(BuildContext context) { final mapController = MapController(); return Obx(() { final points = controller.livePoints; final center = points.isNotEmpty ? points.last : const LatLng(47.5, 19.0); // Magyarország közepe // Középre követ rögzítés közben if (controller.isRecording.value && points.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { mapController.move(points.last, mapController.camera.zoom); }); } return FlutterMap( mapController: mapController, options: MapOptions( initialCenter: center, initialZoom: 15.0, interactionOptions: const InteractionOptions( flags: InteractiveFlag.all, ), ), children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'hu.app_dev.terepi_seged', ), if (points.length >= 2) PolylineLayer( polylines: [ Polyline( points: points, color: Colors.blue.shade700, strokeWidth: 4.0, borderColor: Colors.blue.shade200, borderStrokeWidth: 1.5, ), ], ), if (points.isNotEmpty) MarkerLayer( markers: [ // Kezdőpont Marker( point: points.first, child: const Icon(Icons.flag, color: Colors.green, size: 28), ), // Aktuális pozíció Marker( point: points.last, child: _PulsingDot( color: controller.isPaused.value ? Colors.orange : Colors.blue, ), ), ], ), ], ); }); } } /// Pulzáló pont az aktuális pozícióhoz. class _PulsingDot extends StatefulWidget { final Color color; const _PulsingDot({required this.color}); @override State<_PulsingDot> createState() => _PulsingDotState(); } class _PulsingDotState extends State<_PulsingDot> with SingleTickerProviderStateMixin { late AnimationController _anim; late Animation _scale; @override void initState() { super.initState(); _anim = AnimationController( vsync: this, duration: const Duration(milliseconds: 1000)) ..repeat(reverse: true); _scale = Tween(begin: 0.8, end: 1.3) .animate(CurvedAnimation(parent: _anim, curve: Curves.easeInOut)); } @override void dispose() { _anim.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return ScaleTransition( scale: _scale, child: Container( width: 18, height: 18, decoration: BoxDecoration( color: widget.color, shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 2), boxShadow: [ BoxShadow( color: widget.color.withOpacity(0.5), blurRadius: 8, spreadRadius: 2) ], ), ), ); } } // ─── Statisztika panel ──────────────────────────────────────────────────────── class _StatsPanel extends GetView { const _StatsPanel(); @override Widget build(BuildContext context) { return Obx(() { if (!controller.isRecording.value && controller.livePoints.isEmpty) { return const SizedBox.shrink(); } return Positioned( top: 12, left: 12, right: 12, child: Card( elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), color: Theme.of(context).colorScheme.surface.withOpacity(0.92), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _Stat( icon: Icons.timer_outlined, label: 'Idő', value: controller.elapsedFormatted.value, ), _Divider(), _Stat( icon: Icons.straighten, label: 'Távolság', value: _formatDist(controller.sessionDistance.value), ), _Divider(), _Stat( icon: Icons.speed, label: 'Sebesség', value: '${controller.currentSpeedKmh.value.toStringAsFixed(1)} km/h', ), _Divider(), _Stat( icon: Icons.location_on_outlined, label: 'Pontok', value: controller.livePoints.length.toString(), ), ], ), ), ), ); }); } String _formatDist(double m) { if (m < 1000) return '${m.toStringAsFixed(0)} m'; return '${(m / 1000).toStringAsFixed(2)} km'; } } class _Stat extends StatelessWidget { final IconData icon; final String label; final String value; const _Stat({required this.icon, required this.label, required this.value}); @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 16, color: Theme.of(context).colorScheme.primary), const SizedBox(height: 2), Text(value, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15)), Text(label, style: TextStyle(fontSize: 10, color: Colors.grey.shade600)), ], ); } } class _Divider extends StatelessWidget { const _Divider(); @override Widget build(BuildContext context) => Container(height: 36, width: 1, color: Colors.grey.shade300); } // ─── Vezérlő sáv ──────────────────────────────────────────────────────────── class _ControlBar extends GetView { const _ControlBar(); @override Widget build(BuildContext context) { return Obx(() { final recording = controller.isRecording.value; final paused = controller.isPaused.value; return Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.12), blurRadius: 8, offset: const Offset(0, -2)) ], ), padding: EdgeInsets.fromLTRB( 24, 12, 24, 12 + MediaQuery.of(context).padding.bottom), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ if (!recording) ...[ // ── Nincs aktív track ── _RoundButton( icon: Icons.fiber_manual_record, label: 'Indítás', color: Colors.red, onTap: () => controller.startRecording(), ), ] else ...[ // ── Aktív track ── _RoundButton( icon: paused ? Icons.play_arrow : Icons.pause, label: paused ? 'Folytatás' : 'Szünet', color: Colors.orange, onTap: paused ? controller.resumeRecording : controller.pauseRecording, ), // Nagy Stop gomb középen GestureDetector( onTap: () => _confirmStop(context), child: Container( width: 68, height: 68, decoration: BoxDecoration( color: Colors.red, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.red.withOpacity(0.4), blurRadius: 12, spreadRadius: 2) ], ), child: const Icon(Icons.stop, color: Colors.white, size: 32), ), ), _RoundButton( icon: Icons.share, label: 'Export', color: Colors.blue, onTap: () { final t = controller.currentTrack.value; if (t != null) controller.exportTrack(t); }, ), ], ], ), ); }); } Future _confirmStop(BuildContext context) async { final ok = await showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Track leállítása'), content: const Text( 'Leállítja a rögzítést? A track automatikusan mentésre kerül.'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Mégse')), FilledButton( onPressed: () => Navigator.pop(context, true), child: const Text('Leállítás')), ], ), ); if (ok == true) controller.stopRecording(); } } class _RoundButton extends StatelessWidget { final IconData icon; final String label; final Color color; final VoidCallback onTap; const _RoundButton( {required this.icon, required this.label, required this.color, required this.onTap}); @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 52, height: 52, decoration: BoxDecoration( color: color.withOpacity(0.12), shape: BoxShape.circle, border: Border.all(color: color, width: 1.5), ), child: Icon(icon, color: color, size: 26), ), const SizedBox(height: 4), Text(label, style: TextStyle(fontSize: 11, color: color)), ], ), ); } }