MIF_E31221269/lib/screens/notification.dart

757 lines
30 KiB
Dart

import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:intl/intl.dart';
import 'package:jago/services/db_helper.dart';
import 'package:jago/services/notification_service.dart'; // Import layanan notifikasi
import 'package:excel/excel.dart' as excel;
import 'dart:io';
import 'dart:async';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class NotificationPage extends StatefulWidget {
@override
_NotificationPageState createState() => _NotificationPageState();
}
class _NotificationPageState extends State<NotificationPage> {
List<Map<String, dynamic>> notifications = [];
List<Map<String, dynamic>> filteredNotifications = [];
late DatabaseHelper dbHelper;
DateTime? selectedDate;
int ageInWeeks = 2;
late DatabaseReference ageRef;
int selectedAge = 0; // 0 untuk umur saat ini, 1-4 untuk umur spesifik
// Tambahkan variabel untuk row pages
int _rowsPerPage = 10;
List<int> _rowsPerPageOptions = [5, 10, 20, 50, 100];
int _currentPage = 0;
// Inisialisasi notifikasi lokal
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
// Daftar subscription untuk dibersihkan saat dispose
final List<StreamSubscription> _subscriptions = [];
@override
void initState() {
super.initState();
dbHelper = DatabaseHelper();
ageRef = FirebaseDatabase.instance.ref("chicken_age");
// Periksa tabel notifications
dbHelper.checkTables().then((_) {
_fetchAgeFromFirebase();
_loadNotificationsFromDB();
});
// Pastikan NotificationService sudah diinisialisasi
NotificationService().initialize();
}
@override
void dispose() {
// Membersihkan semua subscription untuk mencegah memory leak
for (var subscription in _subscriptions) {
subscription.cancel();
}
super.dispose();
}
void _fetchAgeFromFirebase() {
final subscription = ageRef.onValue.listen((event) {
final data = event.snapshot.value;
if (data != null) {
int newAge = int.parse(data.toString());
if (newAge != ageInWeeks) {
print("🐔 Umur ayam berubah dari $ageInWeeks ke $newAge");
setState(() {
ageInWeeks = newAge;
});
// Muat ulang notifikasi saat umur berubah
_loadNotificationsFromDB();
}
}
});
_subscriptions.add(subscription);
}
Future<void> _loadNotificationsFromDB() async {
List<Map<String, dynamic>> dbData = await dbHelper.getNotifications();
print("🔍 Total notifikasi dari database: ${dbData.length}");
setState(() {
notifications = _filterUniqueNotifications(dbData);
_applyFilter();
});
print("✅ Berhasil memuat ${notifications.length} notifikasi dari database");
}
Future<void> _refreshNotifications() async {
await _loadNotificationsFromDB();
}
List<Map<String, dynamic>> _filterUniqueNotifications(List<Map<String, dynamic>> data) {
List<Map<String, dynamic>> uniqueData = [];
Map<String, dynamic>? lastEntry;
for (var entry in data) {
if (lastEntry == null || lastEntry['event'] != entry['event']) {
uniqueData.add(entry);
lastEntry = entry;
}
}
return uniqueData;
}
void _applyFilter() {
setState(() {
// Filter berdasarkan tanggal jika dipilih
List<Map<String, dynamic>> dateFiltered;
if (selectedDate == null) {
dateFiltered = List.from(notifications);
} else {
String selectedDateString = DateFormat('yyyy-MM-dd').format(selectedDate!);
dateFiltered = notifications.where((notif) {
DateTime dateTime = DateTime.parse(notif['timestamp']);
return DateFormat('yyyy-MM-dd').format(dateTime) == selectedDateString;
}).toList();
}
// Filter berdasarkan umur ayam yang dipilih
filteredNotifications = dateFiltered.where((notif) {
// Ekstrak umur dari pesan notifikasi jika ada
String eventText = notif['event'] ?? '';
// Cek apakah notifikasi berisi informasi umur
if (eventText.contains("[Umur:")) {
// Ekstrak umur dari notifikasi
RegExp regExp = RegExp(r"\[Umur: (\d+) minggu\]");
Match? match = regExp.firstMatch(eventText);
if (match != null && match.groupCount >= 1) {
int notifAge = int.parse(match.group(1)!);
// Jika selectedAge adalah 0, gunakan umur ayam saat ini
// Jika tidak, gunakan umur yang dipilih
if (selectedAge == 0) {
return notifAge == ageInWeeks;
} else {
return notifAge == selectedAge;
}
}
return false;
} else {
// Untuk notifikasi lama yang tidak memiliki tag umur, tampilkan di umur 2 minggu
if (selectedAge == 0) {
// Jika memilih "umur saat ini", tampilkan data lama jika umur saat ini adalah 2
return ageInWeeks == 2;
} else {
// Jika memilih umur spesifik, tampilkan data lama hanya jika memilih umur 2
return selectedAge == 2;
}
}
}).toList();
// Reset halaman saat filter berubah
_currentPage = 0;
});
}
Future<void> _pickDate(BuildContext context) async {
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: selectedDate ?? DateTime.now(),
firstDate: DateTime(2024),
lastDate: DateTime(2030),
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
colorScheme: ColorScheme.light(
primary: Color(0xFFA82429),
onPrimary: Colors.white,
),
),
child: child!,
);
},
);
if (pickedDate != null) {
setState(() {
selectedDate = pickedDate;
_applyFilter();
});
}
}
void _downloadNotificationsAsExcel() async {
// Filter notifikasi sesuai umur yang dipilih
List<Map<String, dynamic>> ageFilteredData = notifications.where((notif) {
String eventText = notif['event'] ?? '';
if (eventText.contains("[Umur:")) {
RegExp regExp = RegExp(r"\[Umur: (\d+) minggu\]");
Match? match = regExp.firstMatch(eventText);
if (match != null && match.groupCount >= 1) {
int notifAge = int.parse(match.group(1)!);
// Jika selectedAge adalah 0, gunakan umur ayam saat ini
// Jika tidak, gunakan umur yang dipilih
if (selectedAge == 0) {
return notifAge == ageInWeeks;
} else {
return notifAge == selectedAge;
}
}
return false;
} else {
// Untuk notifikasi lama yang tidak memiliki tag umur, tampilkan di umur 2 minggu
if (selectedAge == 0) {
// Jika memilih "umur saat ini", tampilkan data lama jika umur saat ini adalah 2
return ageInWeeks == 2;
} else {
// Jika memilih umur spesifik, tampilkan data lama hanya jika memilih umur 2
return selectedAge == 2;
}
}
}).toList();
var excelFile = excel.Excel.createExcel();
var sheet = excelFile['Sheet1'];
sheet.appendRow(['Timestamp', 'Event', 'Umur Ayam']);
for (var notif in ageFilteredData) {
String eventText = notif['event'] ?? '';
// Ekstrak umur dari teks event jika ada
int eventAge = 2; // Default umur untuk data lama tanpa tag
if (eventText.contains("[Umur:")) {
RegExp regExp = RegExp(r"\[Umur: (\d+) minggu\]");
Match? match = regExp.firstMatch(eventText);
if (match != null && match.groupCount >= 1) {
eventAge = int.parse(match.group(1)!);
}
// Hapus tag umur dari tampilan
eventText = eventText.replaceAll(regExp, "").trim();
}
sheet.appendRow([notif['timestamp'], eventText, eventAge]);
}
// Tentukan nama file berdasarkan mode filter umur
int exportAge = selectedAge == 0 ? ageInWeeks : selectedAge;
final directory = Directory('/storage/emulated/0/Documents/Data Notifikasi');
if (!(await directory.exists())) {
await directory.create(recursive: true);
}
String filePath = '${directory.path}/Notifikasi_${DateFormat('yyyyMMdd').format(DateTime.now())}_Umur${exportAge}minggu.xlsx';
File(filePath)..writeAsBytesSync(excelFile.encode()!);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Data notifikasi berhasil diunduh sebagai Excel'),
backgroundColor: Color(0xFFA82429),
behavior: SnackBarBehavior.floating,
action: SnackBarAction(
label: 'OK',
textColor: Colors.white,
onPressed: () {},
),
)
);
}
// Tambahkan method untuk paginasi
List<Map<String, dynamic>> _getPaginatedData() {
if (filteredNotifications.isEmpty) return [];
int startIndex = _currentPage * _rowsPerPage;
int endIndex = startIndex + _rowsPerPage;
if (startIndex >= filteredNotifications.length) {
_currentPage = 0;
startIndex = 0;
endIndex = _rowsPerPage;
}
if (endIndex > filteredNotifications.length) {
endIndex = filteredNotifications.length;
}
return filteredNotifications.sublist(startIndex, endIndex);
}
@override
Widget build(BuildContext context) {
// Dapatkan data yang sudah dipaginasi
final paginatedData = _getPaginatedData();
final int totalPages = (filteredNotifications.length / _rowsPerPage).ceil();
// Tentukan judul AppBar berdasarkan mode filter umur
String appBarTitle = selectedAge == 0
? 'Notifikasi (Umur: $ageInWeeks minggu)'
: 'Notifikasi (Umur: $selectedAge minggu)';
return Scaffold(
appBar: AppBar(
backgroundColor: Color(0xFFA82429),
elevation: 0,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
title: Text(
appBarTitle,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
)
),
),
body: Column(
children: [
// Header section with curved background
Container(
padding: EdgeInsets.fromLTRB(16, 0, 16, 20),
decoration: BoxDecoration(
color: Color(0xFFA82429),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30),
),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Column(
children: [
Row(
children: [
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: Row(
children: [
Icon(
Icons.calendar_today,
color: Color(0xFFA82429),
size: 20,
),
SizedBox(width: 8),
Expanded(
child: Text(
selectedDate == null
? 'Semua Tanggal'
: DateFormat('dd/MM/yyyy').format(selectedDate!),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
TextButton(
onPressed: () => _pickDate(context),
child: Text(
'Pilih',
style: TextStyle(
color: Color(0xFFA82429),
fontWeight: FontWeight.bold,
),
),
style: TextButton.styleFrom(
minimumSize: Size(0, 0),
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
),
),
],
),
),
),
],
),
SizedBox(height: 12),
Row(
children: [
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: DropdownButtonHideUnderline(
child: DropdownButton<int>(
value: selectedAge,
isExpanded: true,
icon: Icon(Icons.filter_list, color: Color(0xFFA82429)),
items: [
DropdownMenuItem<int>(
value: 0,
child: Text('Umur Saat Ini (${ageInWeeks} minggu)'),
),
DropdownMenuItem<int>(
value: 1,
child: Text('Umur 1 minggu'),
),
DropdownMenuItem<int>(
value: 2,
child: Text('Umur 2 minggu'),
),
DropdownMenuItem<int>(
value: 3,
child: Text('Umur 3 minggu'),
),
DropdownMenuItem<int>(
value: 4,
child: Text('Umur 4 minggu'),
),
],
onChanged: (int? newValue) {
if (newValue != null) {
setState(() {
selectedAge = newValue;
_applyFilter();
});
}
},
),
),
),
),
SizedBox(width: 10),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: IconButton(
icon: Icon(Icons.file_download, color: Color(0xFFA82429)),
onPressed: _downloadNotificationsAsExcel,
tooltip: 'Unduh Data Excel',
),
),
],
),
],
),
),
// Paginasi controls
Padding(
padding: EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
'Baris per halaman: ',
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 0),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(4),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<int>(
value: _rowsPerPage,
isDense: true,
items: _rowsPerPageOptions.map((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text(value.toString()),
);
}).toList(),
onChanged: (newValue) {
if (newValue != null) {
setState(() {
_rowsPerPage = newValue;
_currentPage = 0; // Reset ke halaman pertama
});
}
},
),
),
),
],
),
if (totalPages > 0)
Row(
children: [
IconButton(
icon: Icon(Icons.arrow_back_ios, size: 16),
color: _currentPage > 0 ? Color(0xFFA82429) : Colors.grey,
onPressed: _currentPage > 0
? () {
setState(() {
_currentPage--;
});
}
: null,
),
Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Color(0xFFA82429),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'${_currentPage + 1} / $totalPages',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
icon: Icon(Icons.arrow_forward_ios, size: 16),
color: _currentPage < totalPages - 1 ? Color(0xFFA82429) : Colors.grey,
onPressed: _currentPage < totalPages - 1
? () {
setState(() {
_currentPage++;
});
}
: null,
),
],
),
],
),
),
// List of notifications
Expanded(
child: RefreshIndicator(
onRefresh: _refreshNotifications,
color: Color(0xFFA82429),
child: paginatedData.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.notifications_off,
size: 64,
color: Colors.grey,
),
SizedBox(height: 16),
Text(
selectedAge == 0
? 'Tidak ada notifikasi untuk umur ayam $ageInWeeks minggu'
: 'Tidak ada notifikasi untuk umur ayam $selectedAge minggu',
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
textAlign: TextAlign.center,
),
],
),
)
: ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 16),
itemCount: paginatedData.length,
itemBuilder: (context, index) {
final notif = paginatedData[index];
final DateTime dateTime = DateTime.parse(notif['timestamp']);
String eventText = notif['event'] ?? 'Data tidak tersedia';
// Ekstrak umur dari teks event jika ada
int eventAge = 2; // Default untuk data lama tanpa tag
if (eventText.contains("[Umur:")) {
RegExp regExp = RegExp(r"\[Umur: (\d+) minggu\]");
Match? match = regExp.firstMatch(eventText);
if (match != null && match.groupCount >= 1) {
eventAge = int.parse(match.group(1)!);
}
// Hapus tag umur dari tampilan
eventText = eventText.replaceAll(regExp, "").trim();
}
// Tentukan warna berdasarkan jenis notifikasi
Color cardColor;
Color timeColor;
IconData iconData;
if (eventText.contains('Kritis') ||
eventText.contains('Sangat Tinggi') ||
eventText.contains('Sangat Rendah') ||
eventText.contains('darurat')) {
cardColor = Colors.red;
timeColor = Colors.red[200]!;
// Use temperature or humidity specific icons when relevant
if (eventText.contains('Suhu')) {
iconData = Icons.thermostat;
} else if (eventText.contains('Kelembapan')) {
iconData = Icons.water_drop;
} else {
iconData = Icons.warning;
}
} else if (eventText.contains('tinggi') || eventText.contains('rendah')) {
cardColor = Colors.orange;
timeColor = Colors.orange[200]!;
// Use temperature or humidity specific icons when relevant
if (eventText.contains('Suhu')) {
iconData = Icons.thermostat;
} else if (eventText.contains('Kelembapan')) {
iconData = Icons.water_drop;
} else {
iconData = Icons.warning_amber;
}
} else if (eventText.contains('Fuzzy')) {
cardColor = Colors.blue;
timeColor = Colors.blue[200]!;
// Use temperature or humidity specific icons for Fuzzy notifications
if (eventText.contains('Suhu')) {
iconData = Icons.thermostat;
} else if (eventText.contains('Kelembapan')) {
iconData = Icons.water_drop;
} else {
iconData = Icons.auto_graph;
}
} else if (eventText.contains('Status')) {
cardColor = Colors.purple;
timeColor = Colors.purple[200]!;
// Use temperature or humidity specific icons for Status notifications
if (eventText.contains('Suhu')) {
iconData = Icons.thermostat;
} else if (eventText.contains('Kelembapan')) {
iconData = Icons.water_drop;
} else {
iconData = Icons.info;
}
} else if (eventText.contains('normal') ||
eventText.contains('Normal') ||
eventText.contains('Optimal')) {
cardColor = Colors.green;
timeColor = Colors.green[200]!;
// Use temperature or humidity specific icons for normal notifications
if (eventText.contains('Suhu')) {
iconData = Icons.thermostat;
} else if (eventText.contains('Kelembapan')) {
iconData = Icons.water_drop;
} else {
iconData = Icons.check_circle;
}
} else {
cardColor = Colors.blueGrey;
timeColor = Colors.blueGrey[200]!;
iconData = Icons.notifications;
}
return Card(
elevation: 3,
margin: EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Column(
children: [
// Colored header
Container(
color: cardColor,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
Icon(
iconData,
color: Colors.white,
size: 20,
),
SizedBox(width: 8),
Expanded(
child: Text(
DateFormat('dd/MM/yyyy HH:mm').format(dateTime),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: timeColor,
borderRadius: BorderRadius.circular(12),
),
child: Text(
'Umur: $eventAge minggu',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: cardColor,
),
),
),
],
),
),
// Message body
Container(
width: double.infinity,
padding: EdgeInsets.all(16),
child: Text(
eventText,
style: TextStyle(
fontSize: 16,
color: Colors.black87,
),
),
),
],
),
),
);
},
),
),
),
],
),
);
}
}