import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:intl/intl.dart'; import 'detail_hasil_screen.dart'; import 'all_activity_screen.dart'; class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); Future getUsername() async { final user = FirebaseAuth.instance.currentUser; if (user == null) return ''; final doc = await FirebaseFirestore.instance.collection('users').doc(user.uid).get(); return doc.data()?['username'] ?? ''; } Future> fetchRunningData() async { final user = FirebaseAuth.instance.currentUser; if (user == null) return {}; final now = DateTime.now(); final startOfWeek = DateTime(now.year, now.month, now.day - (now.weekday - 1)); final endOfWeek = startOfWeek.add(const Duration(days: 7)).subtract(const Duration(seconds: 1)); final snapshot = await FirebaseFirestore.instance.collection('activities').get(); final Map distancePerDay = {}; for (var doc in snapshot.docs) { final data = doc.data()['data']; if (data == null || data['userId'] != user.uid) continue; final timestamp = (data['timestamp'] as Timestamp?)?.toDate(); if (timestamp == null || timestamp.isBefore(startOfWeek) || timestamp.isAfter(endOfWeek)) continue; final day = DateFormat('EEEE', 'id_ID').format(timestamp); final jarak = (data['jarak'] ?? 0).toDouble(); final satuan = data['satuan'] ?? 'M'; final jarakKm = satuan == 'M' ? jarak / 1000.0 : jarak; distancePerDay[day] = (distancePerDay[day] ?? 0) + jarakKm; } const sortedDays = ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu', 'Minggu']; for (var dayName in sortedDays) { distancePerDay.putIfAbsent(dayName, () => 0.0); } return distancePerDay; } Future>> fetchRingkasanData() async { final user = FirebaseAuth.instance.currentUser; if (user == null) return []; final snapshot = await FirebaseFirestore.instance.collection('activities').get(); final List> result = []; for (var doc in snapshot.docs) { final data = doc.data()['data']; // penting! if (data == null || data['userId'] != user.uid) continue; result.add({ ...data, 'docId': doc.id, // tambahkan docId untuk referensi penghapusan nanti }); } result.sort((a, b) { final aTime = (a['timestamp'] as Timestamp?)?.toDate() ?? DateTime(2000); final bTime = (b['timestamp'] as Timestamp?)?.toDate() ?? DateTime(2000); return bTime.compareTo(aTime); }); return result; } String getGreeting() { final hour = DateTime.now().hour; if (hour >= 6 && hour <= 10) { return 'Selamat Pagi'; } else if (hour >= 11 && hour <= 14) { return 'Selamat Siang'; } else if (hour >= 15 && hour <= 17) { return 'Selamat Sore'; } else { return 'Selamat Malam'; } } DateTime? parseTimestamp(dynamic raw) { if (raw is Timestamp) { return raw.toDate(); } else if (raw is DateTime) { return raw; } else if (raw is String) { return DateTime.tryParse(raw); } else if (raw is int) { return DateTime.fromMillisecondsSinceEpoch(raw); } return null; } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: PreferredSize( preferredSize: const Size.fromHeight(80), child: FutureBuilder( future: getUsername(), builder: (context, snapshot) { final greeting = getGreeting(); final username = snapshot.data ?? '...'; return 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: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '$greeting, $username', style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), ), Image.asset( 'images/run.png', height: 55, width: 55, ), ], ), ); }, ), ), body: RefreshIndicator( onRefresh: () async { // Memanggil kembali data setelah pull-to-refresh fetchRunningData(); fetchRingkasanData(); }, child: Padding( padding: const EdgeInsets.only(top: 0.0, bottom: 0.0, right: 14.0, left: 14.0), child: FutureBuilder( future: Future.wait([fetchRunningData(), fetchRingkasanData(), getUsername()]), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } final runningData = snapshot.data?[0] as Map? ?? {}; final ringkasanData = snapshot.data?[1] as List>? ?? []; final sortedDays = ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu', 'Minggu']; final values = sortedDays.map((day) => runningData[day] ?? 0.0).toList(); final totalMinggu = values.fold(0.0, (a, b) => a + b); int totalDurasiDetik = 0; final now = DateTime.now(); final startOfWeek = DateTime(now.year, now.month, now.day - (now.weekday - 1)); final endOfWeek = startOfWeek.add(const Duration(days: 7)).subtract(const Duration(seconds: 1)); for (var data in ringkasanData) { final timestamp = parseTimestamp(data['timestamp']); if (timestamp == null) continue; if (timestamp.isBefore(startOfWeek) || timestamp.isAfter(endOfWeek)) continue; totalDurasiDetik += (((data['duration'] ?? data['durasi'] ?? 0) as num).toInt() ~/ 1000); } String formatDurasi(int totalSeconds) { final jam = totalSeconds ~/ 3600; final menit = (totalSeconds % 3600) ~/ 60; final detik = totalSeconds % 60; return '${jam}j ${menit}m ${detik}d'; } final totalDurasiFormatted = formatDurasi(totalDurasiDetik); final maxValue = values.reduce((a, b) => a > b ? a : b); final highestDay = sortedDays[values.indexOf(maxValue)]; return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Minggu ini', style: TextStyle( fontSize: 22, fontWeight: FontWeight.w700, ), ), TextButton( onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (_) => const AllActivityScreen()), ); }, child: const Text( 'View all weeks', style: TextStyle(color: Colors.blue), ), ), ], ), const SizedBox(height: 1), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Jarak', style: TextStyle(fontSize: 12, fontWeight: FontWeight.normal), ), Text( '${totalMinggu.toStringAsFixed(2)} km', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ], ), const SizedBox(width: 20), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Waktu', style: TextStyle(fontSize: 12, fontWeight: FontWeight.normal), ), Text( totalDurasiFormatted, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ], ), ], ), const SizedBox(height: 20), SizedBox( height: 170, child: Padding( padding: const EdgeInsets.only(left: 5.0, right: 6.0), child: LineChart( LineChartData( minY: 0.0, maxY: (values.isEmpty ? 5.0 : values.reduce((a, b) => a > b ? a : b)), gridData: FlGridData( show: true, drawVerticalLine: true, drawHorizontalLine: false, verticalInterval: 1, getDrawingVerticalLine: (value) { return FlLine( color: Colors.blue.withOpacity(0.2), strokeWidth: 1, ); }, ), extraLinesData: ExtraLinesData( verticalLines: [ VerticalLine( x: 0, color: Colors.blue.withOpacity(0.2), strokeWidth: 1, ), VerticalLine( x: (sortedDays.length - 1).toDouble(), color: Colors.blue.withOpacity(0.2), strokeWidth: 1, ), ], ), titlesData: FlTitlesData( bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: 1, getTitlesWidget: (value, meta) { int index = value.toInt(); if (index < 0 || index >= sortedDays.length) { return const SizedBox.shrink(); } EdgeInsets padding = EdgeInsets.zero; if (index == 0) { padding = const EdgeInsets.only(left: 12); } else if (index == sortedDays.length - 1) { padding = const EdgeInsets.only(right: 12); } return Padding( padding: padding.add(const EdgeInsets.only(top: 4)), // tambah jarak dari grafik ke teks child: Text( sortedDays[index].substring(0, 3), style: const TextStyle(fontSize: 10), ), ); }, ), ), leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 35, getTitlesWidget: (value, _) { if (value == 0.0 || value == maxValue || value == (maxValue + 1)) { return Padding( padding: const EdgeInsets.only(left: 8), child: Text('${value.toStringAsFixed(1)} KM', style: const TextStyle(fontSize: 10)), ); } return const SizedBox.shrink(); }, ), ), topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), ), borderData: FlBorderData(show: false), lineBarsData: [ LineChartBarData( spots: List.generate(values.length, (i) => FlSpot(i.toDouble(), values[i])), isCurved: false, barWidth: 3, dotData: FlDotData(show: true), belowBarData: BarAreaData( show: true, color: Colors.lightBlueAccent.withOpacity(0.3), ), color: Colors.blue, ), ], ), ), ), ), const SizedBox(height: 24), Text('Hari Teraktif: $highestDay (${maxValue.toStringAsFixed(2)} KM)', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), const SizedBox(height: 16), const Text('Ringkasan Hasil Lari', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), const SizedBox(height: 8), ringkasanData.isEmpty ? const Text('Belum ada data untuk hari teraktif.') : ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: ringkasanData.length, separatorBuilder: (_, __) => const SizedBox(height: 12), itemBuilder: (context, index) { final data = ringkasanData[index]; final timestamp = (data['timestamp'] as Timestamp?)?.toDate(); if (timestamp == null) return const SizedBox(); final jarak = (data['jarak'] ?? 0).toDouble(); final satuan = data['satuan'] ?? 'M'; final jarakKm = satuan == 'M' ? jarak / 1000.0 : jarak; return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => DetailHasilLariScreen( data: { ...data, 'docId': ringkasanData[index]['docId'] ?? '', // tambahkan docId kalau tersedia }, ), ), ); }, child: Card( color: const Color.fromARGB(255, 60, 193, 255), elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: ListTile( leading: Padding( padding: const EdgeInsets.only(top: 12.0), // geser ke bawah child: const Icon(Icons.directions_run), ), title: Text('${jarak.toStringAsFixed(0)} $satuan'), subtitle: Text( '${DateFormat('dd MMM yyyy, HH:mm').format(timestamp)}\n(${jarakKm.toStringAsFixed(2)} KM)', ), isThreeLine: true, trailing: Padding( padding: const EdgeInsets.only(top: 12.0), // geser ke bawah child: const Icon(Icons.arrow_forward_ios, size: 16), ), ), ), ); }, ), ], ), ); }, ), ), ) ); } }