TKK_E32230499/lib/screen/history.dart

517 lines
16 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'dashboard.dart';
import 'akun.dart';
import '../widgets/fade_in_up.dart';
class HistoryModel {
final String key;
final String time;
final String date;
final double suhu;
final double alkohol;
final String status;
final String jenisTape;
HistoryModel({
required this.key,
required this.time,
required this.date,
required this.suhu,
required this.alkohol,
required this.status,
required this.jenisTape,
});
factory HistoryModel.fromMap(String key, Map data) {
// Ambil waktu sekarang sebagai cadangan (fallback)
DateTime sekarang = DateTime.now();
String jamSkrg = "${sekarang.hour.toString().padLeft(2, '0')}:${sekarang.minute.toString().padLeft(2, '0')}";
String tglSkrg = "${sekarang.day.toString().padLeft(2, '0')}/${sekarang.month.toString().padLeft(2, '0')}/${sekarang.year}";
// Logika Parsing Tanggal dari Firebase
String dateFromDb = data['tanggal']?.toString() ?? tglSkrg;
// Jika format dari DB adalah YYYY-MM-DD, ubah ke DD/MM/YYYY agar filter kalender jalan
if (dateFromDb.contains("-")) {
try {
List<String> p = dateFromDb.split("-");
if (p[0].length == 4) { // YYYY-MM-DD
dateFromDb = "${p[2]}/${p[1]}/${p[0]}";
}
} catch (e) {
dateFromDb = tglSkrg;
}
}
String timeStr = data['jam']?.toString() ?? jamSkrg;
// Ubah format desimal jam (misal "27.18 Jam") menjadi "HH:MM:SS"
if (timeStr.toLowerCase().contains("jam")) {
String numStr = timeStr.replaceAll(RegExp(r'[^0-9.]'), '');
double? val = double.tryParse(numStr);
if (val != null) {
int totalDetik = (val * 3600).toInt();
int j = totalDetik ~/ 3600;
int m = (totalDetik % 3600) ~/ 60;
int s = totalDetik % 60;
timeStr = "${j.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}";
}
}
String statusStr = data['status']?.toString() ?? "Done";
String lowerStatus = statusStr.toLowerCase();
if (lowerStatus.contains("optimal") || lowerStatus.contains("awal") || lowerStatus.contains("menjelang matang")) {
statusStr = "Belum Matang";
}
return HistoryModel(
key: key,
time: timeStr,
date: dateFromDb,
suhu: (data['suhu'] as num?)?.toDouble() ?? 0.0,
alkohol: (data['alkohol'] as num?)?.toDouble() ?? 0.0,
status: statusStr,
jenisTape: data['jenisTape'] != null
? (data['jenisTape'].toString().contains("Singkong")
? "Tape Singkong"
: "Tape Ketan")
: "Tape Ketan",
);
}
}
class HistoryPage extends StatefulWidget {
const HistoryPage({super.key});
@override
State<HistoryPage> createState() => _HistoryPageState();
}
class _HistoryPageState extends State<HistoryPage> {
final DatabaseReference ref = FirebaseDatabase.instance.ref("history");
String selectedCategory = "All";
DateTime activeDate = DateTime.now();
bool _localeReady = false;
@override
void initState() {
super.initState();
initializeDateFormatting('id', null).then((_) {
if (mounted) setState(() => _localeReady = true);
});
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
if (!_localeReady)
return const Scaffold(body: Center(child: CircularProgressIndicator()));
return Scaffold(
extendBody: true,
body: Stack(
children: [
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
stops: [0.0, 0.4, 0.7, 1.0],
colors: [
Color(0xFFE8F5E9),
Color(0xFFFFFDF0),
Color(0xFFF1F8E9),
Color(0xFFE8F5E9),
],
),
),
),
Column(
children: [
FadeInUp(delay: 0, child: _buildAppBar()),
FadeInUp(delay: 150, child: _buildHorizontalCalendar()),
FadeInUp(delay: 300, child: _buildCategoryFilter()),
Expanded(child: FadeInUp(delay: 450, child: _buildHistoryList())),
],
),
],
),
bottomNavigationBar: _buildBottomNavbar(),
);
}
Widget _buildBottomNavbar() {
return Container(
decoration: const BoxDecoration(
color: Color(0xFFE8F5E9),
borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 10,
offset: Offset(0, -2),
),
],
),
child: ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
child: BottomNavigationBar(
currentIndex: 1,
onTap: (index) {
if (index == 0) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const DashboardPage()),
);
} else if (index == 2) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const AkunScreen()),
);
}
},
showSelectedLabels: false,
showUnselectedLabels: false,
backgroundColor: Colors.transparent,
elevation: 0,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: _navIcon(Icons.home_filled, 0),
label: 'Home',
),
BottomNavigationBarItem(
icon: _navIcon(Icons.history, 1),
label: 'History',
),
BottomNavigationBarItem(
icon: _navIcon(Icons.people_alt_rounded, 2),
label: 'Profile',
),
],
),
),
);
}
Widget _navIcon(IconData icon, int index) {
bool isSelected = index == 1;
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: isSelected
? const LinearGradient(
colors: [Color(0xFF2E7D32), Color(0xFF4CAF50)],
)
: null,
),
child: Icon(
icon,
color: isSelected ? Colors.white : const Color(0xFF81C784),
size: 24,
),
);
}
Widget _buildAppBar() {
return SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 24, 20, 10), // Padding seperti di pesan.dart
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.arrow_back_ios_new, size: 20),
onPressed: () => Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const DashboardPage()),
),
),
const Expanded(
child: Text(
"Fermentation History",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
IconButton(
icon: const Icon(Icons.calendar_month, color: Color(0xFF2E7D32)),
onPressed: () async {
DateTime? picked = await showDatePicker(
context: context,
initialDate: activeDate,
firstDate: DateTime(2020),
lastDate: DateTime(2100),
);
if (picked != null && picked != activeDate) {
setState(() {
activeDate = picked;
});
}
},
),
],
),
),
);
}
Widget _buildHorizontalCalendar() {
return Container(
height: 90,
margin: const EdgeInsets.symmetric(vertical: 15), // Jarak seperti di pesan.dart
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 15),
itemCount: 7,
itemBuilder: (context, index) {
DateTime date = activeDate.add(Duration(days: index - 3));
bool isSelected =
date.day == activeDate.day && date.month == activeDate.month;
return GestureDetector(
onTap: () => setState(() => activeDate = date),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: 70,
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF2E7D32) : Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: isSelected
? [
BoxShadow(
color: const Color(0xFF2E7D32).withOpacity(0.3),
blurRadius: 10,
offset: const Offset(0, 5),
),
]
: [],
border: isSelected
? null
: Border.all(color: Colors.grey.withOpacity(0.1)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
DateFormat('MMM', 'id').format(date),
style: TextStyle(
color: isSelected ? Colors.white : Colors.black54,
fontSize: 12,
),
),
Text(
date.day.toString(),
style: TextStyle(
color: isSelected ? Colors.white : Colors.black,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
DateFormat('E', 'id').format(date),
style: TextStyle(
color: isSelected ? Colors.white : Colors.black54,
fontSize: 12,
),
),
],
),
),
);
},
),
);
}
Widget _buildCategoryFilter() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: [
_filterTab("All"),
_filterTab("Tape Ketan"),
_filterTab("Tape Singkong"),
],
),
),
);
}
Widget _filterTab(String label) {
bool isSelected = selectedCategory == label;
return GestureDetector(
onTap: () => setState(() => selectedCategory = label),
child: Container(
margin: const EdgeInsets.only(right: 12),
padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 12),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF2E7D32) : const Color(0xFFE8F5E9),
borderRadius: BorderRadius.circular(15),
),
child: Text(
label,
style: TextStyle(
color: isSelected ? Colors.white : const Color(0xFF2E7D32),
fontWeight: FontWeight.bold,
),
),
),
);
}
Widget _buildHistoryList() {
return StreamBuilder(
stream: ref.onValue,
builder: (context, AsyncSnapshot<DatabaseEvent> snapshot) {
if (!snapshot.hasData || snapshot.data!.snapshot.value == null)
return const Center(child: Text("No history available"));
Map data = snapshot.data!.snapshot.value as Map;
List<HistoryModel> list = [];
// Format pembanding sesuai dengan format di model (dd/MM/yyyy)
String filterDateString = DateFormat('dd/MM/yyyy').format(activeDate);
data.forEach((key, value) {
final item = HistoryModel.fromMap(key, value);
// Memastikan pencocokan tanggal akurat
if (item.date == filterDateString &&
(selectedCategory == "All" || item.jenisTape == selectedCategory)) {
list.add(item);
}
});
// Urutkan dari yang terbaru (berdasarkan key Firebase atau jam)
list.sort((a, b) => b.time.compareTo(a.time));
if (list.isEmpty)
return const Center(child: Text("No data for this date"));
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 20),
itemCount: list.length,
itemBuilder: (context, index) => _buildHistoryCard(list[index]),
);
},
);
}
Widget _buildHistoryCard(HistoryModel item) {
bool isAlert =
item.status.toLowerCase().contains("matang") &&
!item.status.toLowerCase().contains("belum");
return Container(
margin: const EdgeInsets.only(bottom: 15),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
border: Border.all(color: Colors.grey.withOpacity(0.05)),
),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.jenisTape,
style: const TextStyle(color: Colors.black45, fontSize: 13),
),
Text(
"Suhu: ${item.suhu}°C",
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
"Alkohol: ${item.jenisTape.contains('Singkong') ? item.alkohol.toStringAsFixed(1) : item.alkohol.toStringAsFixed(2)}%",
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Row(
children: [
const Icon(
Icons.access_time_filled,
size: 16,
color: Color(0xFF2E7D32),
),
const SizedBox(width: 5),
Text(
item.time,
style: const TextStyle(
color: Color(0xFF2E7D32),
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 15),
const Icon(
Icons.calendar_month,
size: 16,
color: Color(0xFF2E7D32),
),
const SizedBox(width: 5),
Text(
item.date,
style: const TextStyle(
color: Color(0xFF2E7D32),
fontWeight: FontWeight.w600,
),
),
],
),
],
),
Positioned(
right: 0,
bottom: 0,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: const Color(0xFFE8F5E9),
borderRadius: BorderRadius.circular(10),
),
child: Text(
item.status,
style: TextStyle(
color: isAlert ? Colors.orange : const Color(0xFF2E7D32),
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
}
}