Tracking funkció fejlesztése
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user