E32221349_Medibox/lib/notifikasi_page.dart

450 lines
16 KiB
Dart

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<NotifikasiPage> createState() => _NotifikasiPageState();
}
class _NotifikasiPageState extends State<NotifikasiPage> {
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final List<DocumentSnapshot> _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<void> _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<void> _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<void> _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<void> _refresh() async {
setState(() {
_isInitialLoading = true;
_hasMore = true;
_lastDocument = null;
});
await _loadInitialNotifikasi();
}
List<DocumentSnapshot> applyFilter(List<DocumentSnapshot> docs) {
return docs.where((doc) {
final data = doc.data() as Map<String, dynamic>?;
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<int> items,
int? selectedValue,
ValueChanged<int?> onChanged,
) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('$label: '),
DropdownButton<int?>(
value: selectedValue,
hint: const Text('Semua'),
items:
[null, ...items].map((val) {
return DropdownMenuItem<int?>(
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<String, dynamic>;
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,
),
),
],
),
),
);
},
),
),
),
],
),
),
);
}
}