import 'dart:convert'; import 'dart:async'; import 'dart:io'; // Tambahkan ini import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; // Tambahkan ini untuk SystemNavigator.pop import 'package:google_fonts/google_fonts.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import 'package:audioplayers/audioplayers.dart'; import '../layout/main_layout.dart'; import 'kader_drawer.dart'; import '../pages/login_page.dart'; import '../kader/data_kehamilan.dart'; import '../kader/data_balita.dart'; class DashboardKaderPage extends StatefulWidget { const DashboardKaderPage({super.key}); @override State createState() => _DashboardKaderPageState(); } class _DashboardKaderPageState extends State { DateTime selectedDate = DateTime.now(); List jadwalList = []; Timer? _pollingTimer; final AudioPlayer _audioPlayer = AudioPlayer(); int _notificationCount = 0; String userName = "Kader"; String _lastJadwalDataString = ""; String? keteranganKegiatan; String? tempatKegiatan; String? jamMulai; String? jamSelesai; bool isLoadingJadwal = true; int jumlahIbu = 0; int jumlahBalita = 0; bool isLoading = true; int? kaderId; static const String baseUrl = "http://ta.myhost.id/E31230549/mposyandu_api"; final String dashboardUrl = "$baseUrl/dashboard_kader.php"; final String jadwalUrl = "$baseUrl/jadwal_posyandu/get_jadwal_by_kader.php"; @override void initState() { super.initState(); _audioPlayer.setReleaseMode(ReleaseMode.stop); _initPage(); _pollingTimer = Timer.periodic(const Duration(seconds: 10), (timer) { if (kaderId != null && kaderId != 0) { _loadJadwalKader(); } }); } @override void dispose() { _pollingTimer?.cancel(); _audioPlayer.dispose(); super.dispose(); } Future _playNotifSound() async { try { await _audioPlayer.stop(); await _audioPlayer.play(AssetSource('sounds/notif.mp3')); } catch (e) { debugPrint("DEBUG AUDIO ERROR: $e"); } } Future _initPage() async { final prefs = await SharedPreferences.getInstance(); final isLogin = prefs.getBool('isLogin') ?? false; if (!isLogin) { if (!mounted) return; Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (_) => const LoginPage(fromGuard: true)), (route) => false, ); return; } String? idString = prefs.getString('id_user'); int currentId = int.tryParse(idString ?? "0") ?? 0; setState(() { userName = prefs.getString('nama') ?? "Kader"; kaderId = currentId; _notificationCount = prefs.getInt('unread_notif_count_$idString') ?? 0; _lastJadwalDataString = prefs.getString('last_jadwal_data_string_$idString') ?? ""; }); if (currentId != 0) { await _loadDashboard(); await _loadJadwalKader(); } else { if (mounted) setState(() => isLoading = false); } } Future _loadDashboard() async { if (kaderId == null || kaderId == 0) { final prefs = await SharedPreferences.getInstance(); kaderId = int.tryParse(prefs.getString('id_user') ?? "0"); } if (kaderId == null || kaderId == 0) { if (mounted) setState(() => isLoading = false); return; } try { final res = await http .get(Uri.parse("$dashboardUrl?id_kader=$kaderId")) .timeout(const Duration(seconds: 10)); final data = jsonDecode(res.body); if (data['success'] == true) { if (mounted) { setState(() { jumlahIbu = int.tryParse(data['jumlah_ibu'].toString()) ?? 0; jumlahBalita = int.tryParse(data['jumlah_balita'].toString()) ?? 0; isLoading = false; }); } } else { debugPrint("API Error: ${data['message']}"); if (mounted) setState(() => isLoading = false); } } catch (e) { debugPrint("Error Dashboard Connection: $e"); if (mounted) setState(() => isLoading = false); } } Future _loadJadwalKader() async { if (kaderId == null || kaderId == 0) return; try { final url = "$jadwalUrl?kader_id=$kaderId"; final res = await http.get(Uri.parse(url)).timeout(const Duration(seconds: 10)); final data = jsonDecode(res.body); if (data['success'] == true) { List fetchedJadwal = data['data'] ?? []; String currentDataString = jsonEncode(fetchedJadwal); final prefs = await SharedPreferences.getInstance(); String idStr = kaderId.toString(); if (_lastJadwalDataString.isNotEmpty && currentDataString != _lastJadwalDataString) { List oldList = jsonDecode(_lastJadwalDataString); if (fetchedJadwal.length >= oldList.length) { _playNotifSound(); setState(() { _notificationCount += 1; }); await prefs.setInt('unread_notif_count_$idStr', _notificationCount); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text("Jadwal diperbarui atau ditambah!"), backgroundColor: Colors.blue.shade700, duration: const Duration(seconds: 2), ), ); } } } await prefs.setString( 'last_jadwal_data_string_$idStr', currentDataString); if (mounted) { setState(() { jadwalList = fetchedJadwal; _lastJadwalDataString = currentDataString; isLoadingJadwal = false; }); _updateJadwalByDate(selectedDate); } } else { _resetJadwal(); } } catch (e) { debugPrint("Error polling: $e"); if (mounted) setState(() => isLoadingJadwal = false); } } void _resetJadwal() { if (mounted) { setState(() { keteranganKegiatan = null; tempatKegiatan = null; jamMulai = null; jamSelesai = null; isLoadingJadwal = false; }); } } String formatTanggal(DateTime date) { return "${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}"; } void _updateJadwalByDate(DateTime date) { String tanggalDipilih = formatTanggal(date); Map? jadwalHariIni; for (var j in jadwalList) { if (j['tanggal'] != null) { String tanggalApi = j['tanggal'].toString().substring(0, 10); if (tanggalApi == tanggalDipilih) { jadwalHariIni = j; break; } } } if (jadwalHariIni != null) { setState(() { keteranganKegiatan = jadwalHariIni!['keterangan']; tempatKegiatan = jadwalHariIni['lokasi']; jamMulai = jadwalHariIni['jam_mulai']; jamSelesai = jadwalHariIni['jam_selesai']; }); } else { _resetJadwal(); } } Future _showNotifDialog() async { final prefs = await SharedPreferences.getInstance(); String idStr = kaderId.toString(); setState(() { _notificationCount = 0; }); await prefs.setInt('unread_notif_count_$idStr', 0); if (!mounted) return; showDialog( context: context, builder: (_) { return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), title: Text("Pemberitahuan Jadwal", style: GoogleFonts.poppins(fontWeight: FontWeight.w600)), content: SizedBox( width: 300, child: jadwalList.isEmpty ? const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Text("Belum ada jadwal", textAlign: TextAlign.center), ) : ListView.builder( shrinkWrap: true, itemCount: jadwalList.length > 5 ? 5 : jadwalList.length, itemBuilder: (_, index) { final item = jadwalList[(jadwalList.length - 1) - index]; return Card( elevation: 2, margin: const EdgeInsets.symmetric(vertical: 6), child: ListTile( leading: const CircleAvatar( backgroundColor: Colors.blue, child: Icon(Icons.event, color: Colors.white, size: 20), ), title: Text( item['keterangan'] ?? "Kegiatan Posyandu", style: GoogleFonts.poppins( fontWeight: FontWeight.w600, fontSize: 13), ), subtitle: Text( "Tgl: ${item['tanggal']?.toString().substring(0, 10) ?? '-'}\nLokasi: ${item['lokasi'] ?? '-'}", style: GoogleFonts.poppins(fontSize: 11), ), isThreeLine: true, ), ); }, ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text("Tutup")) ], ); }, ); } @override Widget build(BuildContext context) { return PopScope( canPop: false, // Menahan aksi "back" standar onPopInvokedWithResult: (didPop, result) { if (didPop) return; // Keluar dari aplikasi secara total if (Platform.isAndroid) { SystemNavigator.pop(); } else if (Platform.isIOS) { exit(0); } }, child: Theme( data: Theme.of(context).copyWith( textTheme: GoogleFonts.poppinsTextTheme(Theme.of(context).textTheme), dividerColor: Colors.transparent, ), child: Stack( children: [ MainLayout( title: " ", drawer: const KaderDrawer(), body: _buildDashboard(), ), Positioned( top: MediaQuery.of(context).padding.top + (kToolbarHeight - 48) / 2, right: 8, child: Stack( alignment: Alignment.center, children: [ IconButton( icon: const Icon(Icons.notifications, color: Colors.white, size: 26), onPressed: _showNotifDialog, ), if (_notificationCount > 0) Positioned( right: 8, top: 8, child: Container( padding: const EdgeInsets.all(2), decoration: BoxDecoration( color: Colors.red, shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 1.5), ), constraints: const BoxConstraints(minWidth: 18, minHeight: 18), child: Center( child: Text( _notificationCount > 9 ? "9+" : _notificationCount.toString(), style: const TextStyle( color: Colors.white, fontSize: 9, fontWeight: FontWeight.bold), ), ), ), ) ], ), ), ], ), ), ); } Widget _buildDashboard() { return RefreshIndicator( onRefresh: () async { await _loadDashboard(); await _loadJadwalKader(); }, child: Padding( padding: const EdgeInsets.all(16), child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Column( children: [ Align( alignment: Alignment.centerLeft, child: RichText( text: TextSpan( children: [ TextSpan( text: 'Selamat Datang Kader $userName\n', style: GoogleFonts.poppins( fontSize: 20, fontWeight: FontWeight.w700, color: const Color.fromARGB(255, 19, 133, 226)), ), TextSpan( text: 'Tetap semangat melayani masyarakat ', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w500, color: Colors.black87), ), const WidgetSpan( child: Icon(Icons.favorite, color: Colors.blue, size: 18)), ], ), ), ), const SizedBox(height: 15), Center( child: Image.asset('assets/images/logoo.webp', width: 300, height: 200, fit: BoxFit.contain)), const SizedBox(height: 25), isLoading ? const Center(child: CircularProgressIndicator()) : Row( children: [ _infoBoxClickable(jumlahIbu.toString(), 'Jumlah Ibu Hamil', Colors.pink, () async { await Navigator.push( context, MaterialPageRoute( builder: (_) => const DataIbuHamilPage())); _loadDashboard(); }), const SizedBox(width: 10), _infoBoxClickable( jumlahBalita.toString(), 'Jumlah Balita', const Color.fromARGB(255, 240, 220, 39), () async { await Navigator.push( context, MaterialPageRoute( builder: (_) => const DataBalitaPage())); _loadDashboard(); }), ], ), const SizedBox(height: 30), const Text('Jadwal Posyandu', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 15), isLoadingJadwal ? const CircularProgressIndicator() : keteranganKegiatan == null ? const Text("Tidak ada kegiatan posyandu hari ini", style: TextStyle(color: Colors.red)) : Center( child: IntrinsicWidth( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildDetailRow(Icons.access_time, "Jam", "${jamMulai ?? '-'} s/d ${jamSelesai ?? '-'}"), const SizedBox(height: 8), _buildDetailRow(Icons.location_on, "Lokasi", tempatKegiatan ?? "-"), const SizedBox(height: 8), _buildDetailRow(Icons.event_available, "Kegiatan", keteranganKegiatan ?? "-"), ], ), ), ), const SizedBox(height: 25), Container( width: 260, padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.2), blurRadius: 5) ], ), child: Transform.scale( scale: 0.8, child: CalendarDatePicker( initialDate: selectedDate, firstDate: DateTime(2020), lastDate: DateTime(2030), onDateChanged: (date) { setState(() => selectedDate = date); _updateJadwalByDate(date); }, ), ), ), ], ), ), ), ); } Widget _buildDetailRow(IconData icon, String label, String value) { return Row( children: [ Icon(icon, size: 18, color: Colors.blue), const SizedBox(width: 10), SizedBox( width: 90, child: Text(label, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 12))), const SizedBox( width: 10, child: Text(":", style: TextStyle(fontWeight: FontWeight.bold))), Expanded( child: Text(value, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12))), ], ); } static Widget _infoBoxClickable( String value, String label, Color color, VoidCallback onTap) { return Expanded( child: InkWell( borderRadius: BorderRadius.circular(12), onTap: onTap, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(12)), child: Column( children: [ Text(value, style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold)), const SizedBox(height: 5), Text(label, textAlign: TextAlign.center, style: const TextStyle(color: Colors.white)), ], ), ), ), ); } }