import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'bottom_navigation.dart'; class NotifikasiPage extends StatefulWidget { const NotifikasiPage({super.key}); @override State createState() => _NotifikasiPageState(); } class _NotifikasiPageState extends State { final FirebaseAuth _auth = FirebaseAuth.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance; final List _notifikasiDocs = []; final int _limit = 10; DocumentSnapshot? _lastDocument; bool _isLoadingMore = false; bool _hasMore = true; bool _isInitialLoading = true; final ScrollController _scrollController = ScrollController(); // Filter state int? filterDay; int? filterMonth; int? filterYear; int? filterHour; int? filterMinute; @override void initState() { super.initState(); _loadInitialNotifikasi(); _scrollController.addListener(_scrollListener); } @override void dispose() { _scrollController.dispose(); super.dispose(); } void _scrollListener() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100 && !_isLoadingMore && _hasMore) { _loadMoreNotifikasi(); } } Future _loadInitialNotifikasi() async { final user = _auth.currentUser; if (user == null) return; try { final querySnapshot = await _firestore .collection('users') .doc(user.uid) .collection('notifikasi') .orderBy('timestamp', descending: true) .limit(_limit) .get(); setState(() { _notifikasiDocs.clear(); _notifikasiDocs.addAll(querySnapshot.docs); _lastDocument = querySnapshot.docs.isNotEmpty ? querySnapshot.docs.last : null; _hasMore = querySnapshot.docs.length == _limit; _isInitialLoading = false; }); } catch (e) { print('Error loading initial notifications: $e'); setState(() { _isInitialLoading = false; }); } } Future _loadMoreNotifikasi() async { if (!_hasMore || _isLoadingMore || _lastDocument == null) return; setState(() => _isLoadingMore = true); final user = _auth.currentUser; if (user == null) return; try { final querySnapshot = await _firestore .collection('users') .doc(user.uid) .collection('notifikasi') .orderBy('timestamp', descending: true) .startAfterDocument(_lastDocument!) .limit(_limit) .get(); setState(() { _notifikasiDocs.addAll(querySnapshot.docs); _lastDocument = querySnapshot.docs.isNotEmpty ? querySnapshot.docs.last : _lastDocument; _hasMore = querySnapshot.docs.length == _limit; _isLoadingMore = false; }); } catch (e) { print('Error loading more notifications: $e'); setState(() { _isLoadingMore = false; }); } } String _formatDate(Timestamp? timestamp) { if (timestamp == null) return ''; final dateTime = timestamp.toDate().toLocal(); final time = "${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}"; final date = "${dateTime.day.toString().padLeft(2, '0')}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.year}"; return "$time $date"; } Future _deleteNotifikasi(String docId) async { final user = _auth.currentUser; if (user == null) return; await _firestore .collection('users') .doc(user.uid) .collection('notifikasi') .doc(docId) .delete(); setState(() { _notifikasiDocs.removeWhere((doc) => doc.id == docId); }); } Future _refresh() async { setState(() { _isInitialLoading = true; _hasMore = true; _lastDocument = null; }); await _loadInitialNotifikasi(); } List applyFilter(List docs) { return docs.where((doc) { final data = doc.data() as Map?; if (data == null) return false; final timestamp = data['timestamp'] as Timestamp?; if (timestamp == null) return false; final dt = timestamp.toDate(); if (filterYear != null && dt.year != filterYear) return false; if (filterMonth != null && dt.month != filterMonth) return false; if (filterDay != null && dt.day != filterDay) return false; if (filterHour != null && dt.hour != filterHour) return false; if (filterMinute != null && dt.minute != filterMinute) return false; return true; }).toList(); } Widget buildDropdown( String label, List items, int? selectedValue, ValueChanged onChanged, ) { return Row( mainAxisSize: MainAxisSize.min, children: [ Text('$label: '), DropdownButton( value: selectedValue, hint: const Text('Semua'), items: [null, ...items].map((val) { return DropdownMenuItem( value: val, child: Text(val?.toString() ?? 'Semua'), ); }).toList(), onChanged: onChanged, ), ], ); } Widget buildFilterRow() { final currentYear = DateTime.now().year; return Padding( padding: const EdgeInsets.all(8.0), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ buildDropdown( 'Tahun', List.generate(10, (i) => currentYear - i), filterYear, (val) => setState(() => filterYear = val), ), const SizedBox(width: 8), buildDropdown( 'Bulan', List.generate(12, (i) => i + 1), filterMonth, (val) => setState(() => filterMonth = val), ), const SizedBox(width: 8), buildDropdown( 'Tanggal', List.generate(31, (i) => i + 1), filterDay, (val) => setState(() => filterDay = val), ), const SizedBox(width: 8), buildDropdown( 'Jam', List.generate(24, (i) => i), filterHour, (val) => setState(() => filterHour = val), ), const SizedBox(width: 8), buildDropdown( 'Menit', List.generate(60, (i) => i), filterMinute, (val) => setState(() => filterMinute = val), ), const SizedBox(width: 12), ElevatedButton( onPressed: () { setState(() { filterDay = null; filterMonth = null; filterYear = null; filterHour = null; filterMinute = null; }); }, child: const Text('Reset Filter'), ), ], ), ), ); } @override Widget build(BuildContext context) { final user = _auth.currentUser; if (user == null) { return const Center(child: Text("User belum login.")); } final filteredNotifikasi = applyFilter(_notifikasiDocs); return Scaffold( backgroundColor: const Color(0xFFE9F5EC), body: SafeArea( child: Column( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), alignment: Alignment.center, decoration: const BoxDecoration( color: Colors.green, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(32), bottomRight: Radius.circular(32), ), ), child: const Text( 'Notifikasi', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), buildFilterRow(), Expanded( child: _isInitialLoading ? const Center(child: CircularProgressIndicator()) : filteredNotifikasi.isEmpty ? const Center(child: Text("Belum ada notifikasi.")) : RefreshIndicator( onRefresh: _refresh, child: ListView.builder( controller: _scrollController, physics: const AlwaysScrollableScrollPhysics(), itemCount: filteredNotifikasi.length + (_hasMore ? 1 : 0), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), itemBuilder: (context, index) { if (index >= filteredNotifikasi.length) { return const Padding( padding: EdgeInsets.all(16.0), child: Center( child: CircularProgressIndicator(), ), ); } final data = filteredNotifikasi[index].data() as Map; final docId = filteredNotifikasi[index].id; final judul = data['judul'] ?? 'Tidak diketahui'; final pesan = data['pesan'] ?? ''; final timestamp = data['timestamp'] as Timestamp?; return Dismissible( key: Key(docId), direction: DismissDirection.endToStart, background: Container( padding: const EdgeInsets.symmetric( horizontal: 20, ), alignment: Alignment.centerRight, color: Colors.red, child: const Icon( Icons.delete, color: Colors.white, ), ), confirmDismiss: (_) async { return await showDialog( context: context, builder: (_) => AlertDialog( title: const Text("Hapus Notifikasi"), content: const Text( "Apakah kamu yakin ingin menghapus notifikasi ini?", ), actions: [ TextButton( onPressed: () => Navigator.of( context, ).pop(false), child: const Text("Batal"), ), TextButton( onPressed: () => Navigator.of( context, ).pop(true), child: const Text( "Hapus", style: TextStyle( color: Colors.red, ), ), ), ], ), ); }, onDismissed: (_) => _deleteNotifikasi(docId), child: Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.symmetric( vertical: 14, horizontal: 16, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Row untuk judul dan pesan dengan jarak yang lebih rapat Row( mainAxisAlignment: MainAxisAlignment.start, children: [ // Judul Text( judul, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18, ), ), const SizedBox( width: 8, ), // Menambahkan sedikit jarak antara judul dan pesan // Pesan Expanded( child: Text( pesan, style: const TextStyle( fontSize: 16, ), overflow: TextOverflow .ellipsis, // Membatasi panjang pesan yang ditampilkan ), ), ], ), const SizedBox(height: 6), // Waktu tetap di bawah judul dan pesan Text( "Waktu : ${_formatDate(timestamp)}", style: const TextStyle( fontSize: 14, color: Colors.black54, ), ), ], ), ), ); }, ), ), ), ], ), ), ); } }