import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart'; import 'package:terepi_seged/pages/shell/presentations/controllers/shell_controller.dart'; import '../services/gnss/gnss_service.dart'; /// Összecsukható alsó panel — bemérés és kitűzés módváltóval. /// /// Összecsukva: mód chip-ek + kis mentés gomb (nem takarja a térképet). /// Kitűzés módban automatikusan kinyílik a ΔY/ΔX/távolság panellel. /// /// Használat a Stack aljában: /// ```dart /// Positioned( /// bottom: 0, left: 0, right: 0, /// child: SurveyBottomPanel(controller: controller), /// ) /// ``` class MapBottomPanel extends StatefulWidget { final dynamic controller; const MapBottomPanel({super.key, required this.controller}); @override State createState() => _SurveyBottomPanelState(); } class _SurveyBottomPanelState extends State with SingleTickerProviderStateMixin { late AnimationController _animCtrl; late Animation _heightAnim; // Panel magasságok static const _collapsedHeight = 68.0; static const _expandedHeight = 220.0; bool _isExpanded = false; @override void initState() { super.initState(); _animCtrl = AnimationController( vsync: this, duration: const Duration(milliseconds: 250), ); _heightAnim = Tween( begin: _collapsedHeight, end: _expandedHeight, ).animate(CurvedAnimation( parent: _animCtrl, curve: Curves.easeOutCubic, )); // Ha kitűzés módba lépünk → automatikus kinyitás ever(widget.controller.mode as Rx, (mode) { if (mode == MapSurveyMode.stakeout.index && !_isExpanded) { _expand(); } }); } @override void dispose() { _animCtrl.dispose(); super.dispose(); } void _toggle() { if (_isExpanded) { _collapse(); } else { _expand(); } } void _expand() { _animCtrl.forward(); setState(() => _isExpanded = true); ShellController.to.hideNavBar(); } void _collapse() { _animCtrl.reverse(); setState(() => _isExpanded = false); ShellController.to.showNavBar(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _heightAnim, builder: (context, child) => Container( height: _heightAnim.value, clipBehavior: Clip.hardEdge, decoration: BoxDecoration( color: Colors.black.withOpacity(0.88), borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.3), blurRadius: 8, offset: const Offset(0, -2), ), ], ), child: SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), child: Column( mainAxisSize: MainAxisSize.min, children: [ // ── Handle — húzásra nyíl/csuk ───────────────────── GestureDetector( onTap: _toggle, onVerticalDragEnd: (d) { // Felfelé húzás → kinyit, lefelé → csuk if (d.primaryVelocity! < -100) _expand(); if (d.primaryVelocity! > 100) _collapse(); }, behavior: HitTestBehavior.opaque, child: SizedBox( width: double.infinity, height: 20, child: Center( child: Container( width: 36, height: 3, decoration: BoxDecoration( color: Colors.white.withOpacity(0.25), borderRadius: BorderRadius.circular(2), ), ), ), ), ), // ── Módváltó + mentés gomb (mindig látható) ───────── Padding( padding: EdgeInsets.fromLTRB( 10, 6, 10, 6 + MediaQuery.of(context).padding.bottom), child: _ModeRow( controller: widget.controller, onSave: () => _showSaveDialog(context), ), ), // ── Kitűzési adatok (csak kinyitva) ───────────────── if (_isExpanded && _animCtrl.value > 0.5) Expanded( child: _StakeoutContent( controller: widget.controller, onSave: () => _showSaveDialog(context), ), ), ], ), ), ), ); } void _showSaveDialog(BuildContext context) { widget.controller.showAddPointDialog(); } } // ── Módváltó sor ────────────────────────────────────────────────────── class _ModeRow extends StatelessWidget { final dynamic controller; final VoidCallback onSave; const _ModeRow({required this.controller, required this.onSave}); @override Widget build(BuildContext context) { return Obx(() { final mode = (controller.mode?.value ?? MapSurveyMode.measure); return Row(children: [ // Bemérés chip Expanded( child: _ModeChip( label: 'Bemérés', icon: Icons.gps_fixed, selected: mode == MapSurveyMode.measure, onTap: () => controller.switchMode?.call(MapSurveyMode.measure), ), ), const SizedBox(width: 8), // Kitűzés chip Expanded( child: _ModeChip( label: 'Kitűzés', icon: Icons.flag_outlined, selected: mode == MapSurveyMode.stakeout, onTap: () => controller.switchMode?.call(MapSurveyMode.stakeout), ), ), const SizedBox(width: 8), // Mentés gomb — bemérés módban kis gomb if (mode == MapSurveyMode.measure) GestureDetector( onTap: onSave, child: Obx(() { final hasfix = GnssService.to.gpsQuality.value > 0; return Container( width: 44, height: 32, decoration: BoxDecoration( color: hasfix ? Colors.blue.shade700 : Colors.grey.shade800, borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.location_on, color: hasfix ? Colors.white : Colors.grey, size: 20, ), ); }), ), ]); }); } } // ── Módváltó chip ───────────────────────────────────────────────────── class _ModeChip extends StatelessWidget { final String label; final IconData icon; final bool selected; final VoidCallback onTap; const _ModeChip({ required this.label, required this.icon, required this.selected, required this.onTap, }); @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 200), height: 32, decoration: BoxDecoration( color: selected ? Colors.blue.withOpacity(0.25) : Colors.white.withOpacity(0.06), borderRadius: BorderRadius.circular(8), border: Border.all( color: selected ? Colors.blue.shade300.withOpacity(0.6) : Colors.white.withOpacity(0.12), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( icon, size: 14, color: selected ? Colors.blue.shade300 : Colors.white38, ), const SizedBox(width: 5), Text( label, style: TextStyle( fontSize: 11, fontWeight: FontWeight.w500, color: selected ? Colors.blue.shade300 : Colors.white38, ), ), ], ), ), ); } } // ── Kitűzési tartalom ───────────────────────────────────────────────── class _StakeoutContent extends StatelessWidget { final dynamic controller; final VoidCallback onSave; const _StakeoutContent({ required this.controller, required this.onSave, }); @override Widget build(BuildContext context) { return Obx(() { final dy = (controller.deltaY as double?) ?? 0.0; final dx = (controller.deltaX as double?) ?? 0.0; final dist = (controller.distanceToTarget as double?) ?? 0.0; final onTarget = (controller.isOnTarget as bool?) ?? false; final name = (controller.targetName as RxString?) ?? ''; return Padding( padding: const EdgeInsets.fromLTRB(10, 6, 10, 6), child: Column( mainAxisSize: MainAxisSize.min, children: [ // ΔY / ΔX / Távolság Row(children: [ _DeltaCell('ΔY', dy), const SizedBox(width: 6), _DeltaCell('ΔX', dx), const SizedBox(width: 6), _DeltaCell('Táv', dist, alwaysPositive: true), ]), const SizedBox(height: 6), // Célpont státusz Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: onTarget ? Colors.green.withOpacity(0.12) : Colors.orange.withOpacity(0.10), borderRadius: BorderRadius.circular(6), border: Border.all( color: onTarget ? Colors.green.withOpacity(0.4) : Colors.orange.withOpacity(0.3), ), ), child: Text( onTarget ? '✓ Célponton — rögzíthető' : '$name · ${dist.toStringAsFixed(3)} m a céltól', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w500, color: onTarget ? Colors.greenAccent : Colors.orange, ), textAlign: TextAlign.center, ), ), const SizedBox(height: 6), // Nagy mentés gomb kitűzés módban SizedBox( width: double.infinity, height: 36, child: FilledButton.icon( style: FilledButton.styleFrom( backgroundColor: onTarget ? Colors.green.shade700 : Colors.blue.shade700, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8)), ), icon: const Icon(Icons.location_on, size: 18), label: const Text('Pont rögzítése', style: TextStyle(fontSize: 13)), onPressed: onSave, ), ), ], ), ); }); } } // ── ΔY / ΔX / Távolság cella ───────────────────────────────────────── class _DeltaCell extends StatelessWidget { final String label; final double value; final bool alwaysPositive; const _DeltaCell(this.label, this.value, {this.alwaysPositive = false}); @override Widget build(BuildContext context) { final display = alwaysPositive ? value.abs() : value; final prefix = (!alwaysPositive && value > 0) ? '+' : ''; final color = value.abs() < 0.05 ? Colors.greenAccent : value.abs() < 0.5 ? Colors.orange : Colors.red; return Expanded( child: Container( padding: const EdgeInsets.symmetric(vertical: 4), decoration: BoxDecoration( color: Colors.white.withOpacity(0.05), borderRadius: BorderRadius.circular(6), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( label, style: TextStyle( fontSize: 9, color: Colors.white.withOpacity(0.4), ), ), Text( '$prefix${display.toStringAsFixed(3)} m', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: color, fontFeatures: const [FontFeature.tabularFigures()], ), ), ], ), ), ); } }