import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class DetailHasilLariScreen extends StatefulWidget { final Map data; const DetailHasilLariScreen({super.key, required this.data}); @override State createState() => _DetailHasilLariScreenState(); } class _DetailHasilLariScreenState extends State { List kecepatanList = []; bool loading = true; @override void initState() { super.initState(); fetchLapData(); } Future fetchLapData() async { final jenis = widget.data['type']; // ===================== // NON-LINTASAN // ===================== if (jenis == 'non-lintasan') { final duration = widget.data['duration']; final rawJarak = widget.data['jarak'] ?? widget.data['distance'] ?? 0.0; final satuan = widget.data['satuan'] ?? 'M'; final jarakKm = satuan == 'KM' ? rawJarak.toDouble() : rawJarak.toDouble() / 1000.0; if (duration is int && jarakKm > 0) { final durSec = duration / 1000; final pacePerKmSec = durSec / jarakKm; // Gunakan floor agar hanya simulasikan per-km (jangan 1.5 jadi 2) List tempPaces = List.generate(jarakKm.floor(), (_) => pacePerKmSec); widget.data['pace_sec_per_km'] = pacePerKmSec; setState(() { kecepatanList = tempPaces; loading = false; }); return; } } // ===================== // LINTASAN // ===================== final docId = widget.data['docId']; final totalLaps = widget.data['putaran']; if (docId == null || totalLaps == null) { setState(() => loading = false); return; } final doc = await FirebaseFirestore.instance.collection('activities').doc(docId).get(); final laps = doc['data']['laps']; if (laps == null || laps is! Map) { setState(() => loading = false); return; } final rawStart = widget.data['startTime']; DateTime? startTime; if (rawStart is Timestamp) { startTime = rawStart.toDate(); } else if (rawStart is int) { startTime = DateTime.fromMillisecondsSinceEpoch(rawStart); } final lapZero = laps['lap_0']; DateTime? prevTime; if (lapZero != null && lapZero['timestamp'] != null) { prevTime = DateTime.parse(lapZero['timestamp']); } else if (startTime != null) { prevTime = startTime; } List tempSpeeds = []; final jarak = widget.data['jarak'] ?? 0; final jarakPerLap = jarak / totalLaps; for (int i = 1; i <= totalLaps; i++) { final lapData = laps['lap_$i']; if (lapData == null || lapData['timestamp'] == null) continue; final timestamp = DateTime.parse(lapData['timestamp']); if (prevTime != null) { final durationMs = timestamp.difference(prevTime).inMilliseconds; if (durationMs <= 0) { tempSpeeds.add(0.0); } else { final speed = jarakPerLap / (durationMs / 1000); tempSpeeds.add(speed); } } prevTime = timestamp; } setState(() { kecepatanList = tempSpeeds; loading = false; }); } void _confirmDelete(BuildContext context) { showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Hapus Aktivitas?'), content: const Text('Apakah kamu yakin ingin menghapus aktivitas ini?'), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), TextButton( onPressed: () async { Navigator.pop(ctx); await _deleteActivity(context); }, child: const Text('Hapus', style: TextStyle(color: Colors.red)), ), ], ), ); } Future _deleteActivity(BuildContext context) async { try { final docId = widget.data['docId']; if (docId != null) { await FirebaseFirestore.instance.collection('activities').doc(docId).delete(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Aktivitas berhasil dihapus')), ); Navigator.pop(context); } else { throw Exception('ID dokumen tidak ditemukan'); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Gagal menghapus: $e')), ); } } String getRunPeriodLabel(DateTime time) { final hour = time.hour; if (hour >= 6 && hour <= 10) { return 'Berlari Pagi'; } else if (hour >= 11 && hour <= 14) { return 'Berlari Siang'; } else if (hour >= 15 && hour <= 17) { return 'Berlari Sore'; } else { return 'Berlari Malam'; } } @override Widget build(BuildContext context) { final data = widget.data; final timestamp = (data['timestamp'] as Timestamp).toDate(); final jenis = data['type'] == 'lintasan' ? 'Lintasan' : 'Non-Lintasan'; final duration = data['duration']; DateTime? startTime; final rawStart = data['startTime']; if (rawStart is Timestamp) { startTime = rawStart.toDate(); } else if (rawStart is int) { startTime = DateTime.fromMillisecondsSinceEpoch(rawStart); } final formattedStart = startTime != null ? DateFormat('HH:mm:ss').format(startTime) : '-'; String durationFormatted = '-'; if (duration is int) { final d = Duration(milliseconds: duration); durationFormatted = d.toString().split('.').first.padLeft(8, "0"); } String? paceFormatted; if (data['type'] == 'non-lintasan' && data['pace_sec_per_km'] != null) { final paceSec = (data['pace_sec_per_km'] as num).toDouble(); final paceMin = paceSec ~/ 60; final paceSecRem = (paceSec % 60).round(); paceFormatted = '$paceMin:${paceSecRem.toString().padLeft(2, '0')} /km'; } final rawJarak = data['jarak'] ?? data['distance'] ?? 0.0; final satuan = data['satuan'] ?? 'M'; final jarakKm = satuan == 'KM' ? rawJarak.toDouble() : rawJarak.toDouble() / 1000.0; final putaran = data['putaran']; return Scaffold( appBar: PreferredSize( preferredSize: const Size.fromHeight(100), child: Container( decoration: const BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), ), padding: const EdgeInsets.only(top: 40, left: 20, right: 20, bottom: 13), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.of(context).pop(), ), const Text( "Detail Hasil Lari", style: TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, ), ), IconButton( icon: const Icon(Icons.delete, color: Colors.white), tooltip: 'Hapus Aktivitas', onPressed: () => _confirmDelete(context), ), ], ), ), ), backgroundColor: Colors.white, body: Padding( padding: const EdgeInsets.only(top: 30, left: 20, right: 20, bottom: 16), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.blue.shade300, Colors.blue.shade600], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.2), offset: const Offset(0, 4), blurRadius: 8, ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Center( child: Text( getRunPeriodLabel(timestamp), style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), const SizedBox(height: 4), Text( DateFormat('dd MMMM yyyy, HH:mm').format(timestamp), style: const TextStyle(color: Colors.white70, fontSize: 14), ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ // KIRI Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( durationFormatted, style: const TextStyle( fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), const Text('Durasi', style: TextStyle(color: Colors.white70)), if (data['type'] == 'non-lintasan' && paceFormatted != null) ...[ const SizedBox(height: 12), Text( paceFormatted!, style: const TextStyle( fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), const Text('Pace', style: TextStyle(color: Colors.white70)), ], if (data['type'] == 'lintasan') ...[ const SizedBox(height: 12), Text( '$putaran', style: const TextStyle( fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), const Text('Putaran', style: TextStyle(color: Colors.white70)), ], ], ), // KANAN Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${jarakKm.toStringAsFixed(2)} KM', style: const TextStyle( fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), const Text('Jarak', style: TextStyle(color: Colors.white70)), const SizedBox(height: 12), Text( jenis, style: const TextStyle( fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), const Text('Jenis Aktivitas', style: TextStyle(color: Colors.white70)), ], ), ], ), const SizedBox(height: 16), ], ), ), const SizedBox(height: 32), if (data['type'] == 'lintasan') ...[ const SizedBox(height: 32), const Text("Splits Kecepatan", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), const SizedBox(height: 12), loading ? const Center(child: CircularProgressIndicator()) : kecepatanList.isEmpty ? const Text("Data kecepatan tidak tersedia.") : Column( children: List.generate(kecepatanList.length, (index) { final speed = kecepatanList[index]; final maxSpeed = kecepatanList.reduce((a, b) => a > b ? a : b); final barFraction = (speed / maxSpeed).clamp(0.0, 1.0); return Padding( padding: const EdgeInsets.symmetric(vertical: 6.0), child: Row( children: [ SizedBox(width: 40, child: Text("Lap ${index + 1}", style: const TextStyle(fontSize: 14))), const SizedBox(width: 8), Expanded( child: Stack( children: [ Container( height: 20, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ), FractionallySizedBox( widthFactor: barFraction, child: Container( height: 20, decoration: BoxDecoration( color: Colors.blueAccent, borderRadius: BorderRadius.circular(6), ), ), ), ], ), ), const SizedBox(width: 8), Text("${speed.toStringAsFixed(2)} m/s", style: const TextStyle(fontSize: 14)), ], ), ); }), ), ], ], ), ), ), ); } }