MobilApp/lib/widgets/tracking/track_info_card.dart

357 lines
11 KiB
Dart
Raw Normal View History

2026-06-11 01:20:55 +02:00
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:terepi_seged/pages/tracking/presentation/controllers/tracking_controller.dart';
import 'tracking_sheet.dart';
/// Kompakt tracking státusz kártya a térkép bal felső sarkában.
///
/// Rögzítés közben: idő, távolság, pontok + szünet/stop gombok
/// Rögzítésen kívül: egyetlen start gomb
///
/// Mindig látható — tap → TrackingSheet (teljes lista + vezérlők)
class TrackInfoCard extends StatelessWidget {
const TrackInfoCard({super.key});
@override
Widget build(BuildContext context) {
return Obx(() {
final ctrl = TrackingController.to;
final isRec = ctrl.isRecording.value;
return GestureDetector(
onTap: () => _openSheet(context),
child: Container(
constraints: const BoxConstraints(minWidth: 140),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.72),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isRec
? Colors.red.withOpacity(0.5)
: Colors.white.withOpacity(0.12),
),
),
child: isRec ? _RecordingContent(ctrl: ctrl) : _IdleContent(),
),
);
});
}
void _openSheet(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) => const TrackingSheet(),
);
}
}
// ─── Rögzítés közben ──────────────────────────────────────────────────────────
class _RecordingContent extends StatelessWidget {
final TrackingController ctrl;
const _RecordingContent({required this.ctrl});
@override
Widget build(BuildContext context) {
return Obx(() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Fejléc sor — piros pont + "REC" vagy "SZÜNET"
Row(mainAxisSize: MainAxisSize.min, children: [
_StatusDot(paused: ctrl.isPaused.value),
const SizedBox(width: 5),
Text(
ctrl.isPaused.value ? 'SZÜNET' : 'REC',
style: TextStyle(
color: ctrl.isPaused.value ? Colors.orange : Colors.red,
fontSize: 11,
fontWeight: FontWeight.w700,
letterSpacing: 0.5,
),
),
const Spacer(),
// Kártya bezárása (csak megkisebbíti, nem állítja le)
GestureDetector(
onTap: () {}, // placeholder — a kártya mindig látható
child: Icon(Icons.unfold_less,
size: 14, color: Colors.white.withOpacity(0.3)),
),
]),
const SizedBox(height: 4),
// Statisztika sor
Row(
mainAxisSize: MainAxisSize.min,
children: [
_MiniStat(
icon: Icons.timer_outlined,
value: ctrl.elapsedFormatted.value,
),
const SizedBox(width: 10),
_MiniStat(
icon: Icons.route,
value: _fmtDist(ctrl.sessionDistance.value),
),
const SizedBox(width: 10),
_MiniStat(
icon: Icons.location_on_outlined,
value: '${ctrl.livePoints.length}',
),
],
),
const SizedBox(height: 6),
// Vezérlő gombok
Row(children: [
// Szünet / Folytatás
_CardButton(
icon: ctrl.isPaused.value ? Icons.play_arrow : Icons.pause,
color: ctrl.isPaused.value ? Colors.greenAccent : Colors.orange,
onTap: ctrl.isPaused.value
? ctrl.resumeRecording
: ctrl.pauseRecording,
tooltip: ctrl.isPaused.value ? 'Folytatás' : 'Szünet',
),
const SizedBox(width: 6),
// Stop
_CardButton(
icon: Icons.stop,
color: Colors.red,
onTap: () => _confirmStop(context),
tooltip: 'Befejezés',
),
const Spacer(),
// Nyíl → sheet megnyitás
Icon(Icons.keyboard_arrow_up,
size: 14, color: Colors.white.withOpacity(0.35)),
]),
],
));
}
void _confirmStop(BuildContext context) {
Get.dialog(AlertDialog(
title: const Text('Rögzítés befejezése'),
content: Obx(() => Text(
'${ctrl.elapsedFormatted.value} · '
'${_fmtDist(ctrl.sessionDistance.value)}\n'
'${ctrl.livePoints.length} pont',
)),
actions: [
TextButton(onPressed: Get.back, child: const Text('Mégse')),
FilledButton(
style: FilledButton.styleFrom(backgroundColor: Colors.red.shade700),
onPressed: () {
Get.back();
ctrl.stopRecording();
},
child: const Text('Befejezés'),
),
],
));
}
String _fmtDist(double m) => m < 1000
? '${m.toStringAsFixed(0)} m'
: '${(m / 1000).toStringAsFixed(2)} km';
}
// ─── Idle állapot ─────────────────────────────────────────────────────────────
class _IdleContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.route_outlined,
size: 14, color: Colors.white.withOpacity(0.5)),
const SizedBox(width: 6),
Text(
'Track',
style: TextStyle(
color: Colors.white.withOpacity(0.55),
fontSize: 12,
),
),
const SizedBox(width: 8),
// Indítás gomb
GestureDetector(
onTap: () => _startRecording(context),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.2),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: Colors.red.withOpacity(0.4)),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.fiber_manual_record, size: 10, color: Colors.red),
SizedBox(width: 4),
Text('Start',
style: TextStyle(
color: Colors.red,
fontSize: 11,
fontWeight: FontWeight.w600)),
],
),
),
),
],
);
}
void _startRecording(BuildContext context) {
final nameCtrl = TextEditingController(
text: 'Track ${DateTime.now().toString().substring(5, 16)}',
);
Get.dialog(AlertDialog(
title: const Text('Rögzítés indítása'),
content: TextField(
controller: nameCtrl,
autofocus: true,
decoration: const InputDecoration(
labelText: 'Track neve',
border: OutlineInputBorder(),
),
),
actions: [
TextButton(onPressed: Get.back, child: const Text('Mégse')),
FilledButton(
onPressed: () {
Get.back();
TrackingController.to.startRecording();
// .startRecording(name: nameCtrl.text.trim());
},
child: const Text('Indítás'),
),
],
));
}
}
// ─── Segéd widgetek ───────────────────────────────────────────────────────────
class _StatusDot extends StatefulWidget {
final bool paused;
const _StatusDot({required this.paused});
@override
State<_StatusDot> createState() => _StatusDotState();
}
class _StatusDotState extends State<_StatusDot>
with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
late Animation<double> _anim;
@override
void initState() {
super.initState();
_ctrl = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 700),
)..repeat(reverse: true);
_anim = Tween(begin: 0.3, end: 1.0)
.animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeInOut));
}
@override
void didUpdateWidget(_StatusDot old) {
super.didUpdateWidget(old);
widget.paused ? _ctrl.stop() : _ctrl.repeat(reverse: true);
}
@override
void dispose() {
_ctrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final color = widget.paused ? Colors.orange : Colors.red;
return AnimatedBuilder(
animation: _anim,
builder: (_, __) => Opacity(
opacity: widget.paused ? 0.6 : _anim.value,
child: Container(
width: 7,
height: 7,
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
),
),
);
}
}
class _MiniStat extends StatelessWidget {
final IconData icon;
final String value;
const _MiniStat({required this.icon, required this.value});
@override
Widget build(BuildContext context) {
return Row(mainAxisSize: MainAxisSize.min, children: [
Icon(icon, size: 11, color: Colors.white54),
const SizedBox(width: 3),
Text(
value,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
fontFeatures: [FontFeature.tabularFigures()],
),
),
]);
}
}
class _CardButton extends StatelessWidget {
final IconData icon;
final Color color;
final VoidCallback onTap;
final String tooltip;
const _CardButton({
required this.icon,
required this.color,
required this.onTap,
required this.tooltip,
});
@override
Widget build(BuildContext context) {
return Tooltip(
message: tooltip,
child: GestureDetector(
onTap: onTap,
child: Container(
width: 28,
height: 24,
decoration: BoxDecoration(
color: color.withOpacity(0.18),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: color.withOpacity(0.45)),
),
child: Icon(icon, size: 14, color: color),
),
),
);
}
}