update thermal logic

This commit is contained in:
Muhammad Iqbal 2025-05-18 23:50:06 +07:00
parent 8aa455758b
commit 3ae23dfdef
4 changed files with 108 additions and 50 deletions

View File

@ -16,9 +16,9 @@ class _DashboardScreenState extends State<DashboardScreen> {
late DatabaseReference _thermalRef; late DatabaseReference _thermalRef;
Map<String, dynamic> sensorData = { Map<String, dynamic> sensorData = {
'PIR': 'Memuat...', 'PIR': '0',
'Ultrasonik': 'Memuat...', 'Ultrasonik': '0',
'Status Pengusir': 'Memuat...', 'Status Pengusir': '0',
}; };
List<double> thermalData = List.filled(64, 0.0); List<double> thermalData = List.filled(64, 0.0);
@ -30,7 +30,6 @@ class _DashboardScreenState extends State<DashboardScreen> {
_sensorRef = FirebaseDatabase.instance.ref('sensor'); _sensorRef = FirebaseDatabase.instance.ref('sensor');
_thermalRef = FirebaseDatabase.instance.ref('thermal/temperatures'); _thermalRef = FirebaseDatabase.instance.ref('thermal/temperatures');
// Listener sensor data
_sensorRef.onValue.listen((event) { _sensorRef.onValue.listen((event) {
final data = event.snapshot.value; final data = event.snapshot.value;
if (data != null && data is Map) { if (data != null && data is Map) {
@ -46,11 +45,9 @@ class _DashboardScreenState extends State<DashboardScreen> {
} }
}); });
// Listener thermal data
_thermalRef.onValue.listen((event) { _thermalRef.onValue.listen((event) {
final data = event.snapshot.value; final data = event.snapshot.value;
if (data != null) { if (data != null) {
// Firebase RTDB terkadang mengirim List<dynamic> atau Map<dynamic,dynamic>
List<double> temps = []; List<double> temps = [];
if (data is List) { if (data is List) {
temps = data.map<double>((e) { temps = data.map<double>((e) {
@ -58,7 +55,6 @@ class _DashboardScreenState extends State<DashboardScreen> {
return 0.0; return 0.0;
}).toList(); }).toList();
} else if (data is Map) { } else if (data is Map) {
// Jika data disimpan sebagai Map index:string -> value
temps = List<double>.filled(64, 0); temps = List<double>.filled(64, 0);
data.forEach((key, value) { data.forEach((key, value) {
int idx = int.tryParse(key) ?? -1; int idx = int.tryParse(key) ?? -1;
@ -82,7 +78,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.grey[100], backgroundColor: const Color.fromARGB(255, 242, 242, 242),
appBar: CustomHeader( appBar: CustomHeader(
deviceName: 'HamaGuard', deviceName: 'HamaGuard',
notificationCount: 5, notificationCount: 5,
@ -94,39 +90,47 @@ class _DashboardScreenState extends State<DashboardScreen> {
}, },
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: Column( child: Column(
children: [ children: [
// Thermal Sensor // Thermal Sensor Card
Card( Card(
elevation: 4,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)), borderRadius: BorderRadius.circular(20),
),
elevation: 2,
shadowColor: Colors.green.withOpacity(0.2),
color: Colors.white,
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
children: const [ children: const [
Icon(Icons.thermostat_outlined, color: Colors.red), Icon(Icons.thermostat_outlined,
SizedBox(width: 8), color: Colors.deepOrangeAccent, size: 30),
SizedBox(width: 12),
Text( Text(
'Thermal Sensor', 'Thermal Sensor',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 16), fontWeight: FontWeight.w600,
fontSize: 20,
color: Colors.deepOrangeAccent,
),
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 16),
ThermalHeatmap(temperatures: thermalData), ThermalHeatmap(temperatures: thermalData),
], ],
), ),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 24),
// PIR & Ultrasonik // Sensor Cards row (PIR & Ultrasonik)
Row( Row(
children: [ children: [
Expanded( Expanded(
@ -134,28 +138,29 @@ class _DashboardScreenState extends State<DashboardScreen> {
title: 'PIR', title: 'PIR',
value: sensorData['PIR'] ?? '-', value: sensorData['PIR'] ?? '-',
icon: Icons.motion_photos_on_outlined, icon: Icons.motion_photos_on_outlined,
color: Colors.orange, color: Colors.orange.shade700,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 20),
Expanded( Expanded(
child: SensorCard( child: SensorCard(
title: 'Ultrasonik', title: 'Jarak',
value: sensorData['Ultrasonik'] ?? '-', value: sensorData['Ultrasonik'] ?? '-',
icon: Icons.straighten, icon: Icons.straighten,
color: Colors.blue, color: Colors.blue.shade700,
), ),
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 20),
// Status Pengusir // Status Pengusir
SensorCard( SensorCard(
title: 'Status Pengusir', title: 'Status Pengusir',
value: sensorData['Status Pengusir'] ?? '-', value: sensorData['Status Pengusir'] ?? '-',
icon: Icons.speaker, icon: Icons.speaker,
color: Colors.green, color: Colors.green.shade700,
isWide: true,
), ),
], ],
), ),
@ -169,6 +174,7 @@ class SensorCard extends StatelessWidget {
final String value; final String value;
final IconData icon; final IconData icon;
final Color color; final Color color;
final bool isWide;
const SensorCard({ const SensorCard({
super.key, super.key,
@ -176,20 +182,56 @@ class SensorCard extends StatelessWidget {
required this.value, required this.value,
required this.icon, required this.icon,
required this.color, required this.color,
this.isWide = false,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Card(
elevation: 3, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), elevation: 2, // shadow tipis
child: ListTile( shadowColor: color.withOpacity(0.1), // lebih transparan
leading: CircleAvatar( color: Colors.white,
backgroundColor: color.withAlpha(15), child: Container(
child: Icon(icon, color: color), width: isWide ? double.infinity : null,
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
child: Row(
children: [
Container(
decoration: BoxDecoration(
color: color.withOpacity(0.15),
borderRadius: BorderRadius.circular(14),
),
padding: const EdgeInsets.all(12),
child: Icon(icon, size: 30, color: color),
),
const SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
color: color.withOpacity(0.9),
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
const SizedBox(height: 6),
Text(
value,
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 15,
color: Colors.grey[800],
),
),
],
),
),
],
), ),
title: Text(title),
subtitle: Text(value),
), ),
); );
} }

View File

@ -70,7 +70,7 @@ class _MainScreenState extends State<MainScreen> {
child: FloatingActionButton( child: FloatingActionButton(
onPressed: () => _onItemTapped(0), // Dashboard / Monitoring di tengah onPressed: () => _onItemTapped(0), // Dashboard / Monitoring di tengah
backgroundColor: backgroundColor:
_selectedIndex == 0 ? Colors.green[700] : Colors.green[400], _selectedIndex == 0 ? const Color.fromRGBO(56, 142, 60, 1) : Colors.green[400],
elevation: _selectedIndex == 0 ? 8 : 4, elevation: _selectedIndex == 0 ? 8 : 4,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(35), borderRadius: BorderRadius.circular(35),
@ -87,7 +87,7 @@ class _MainScreenState extends State<MainScreen> {
shape: const CircularNotchedRectangle(), shape: const CircularNotchedRectangle(),
notchMargin: 8, notchMargin: 8,
elevation: 8, elevation: 8,
color: Colors.white, color: const Color.fromARGB(255, 255, 255, 255),
child: SizedBox( child: SizedBox(
height: 60, height: 60,
child: Row( child: Row(

View File

@ -69,7 +69,7 @@ class _CustomHeaderState extends State<CustomHeader> {
style: const TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color.fromARGB(255, 56, 142, 60), color: Color.fromRGBO(56, 142, 60, 1),
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -90,7 +90,7 @@ class _CustomHeaderState extends State<CustomHeader> {
Padding( Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: Material( child: Material(
color: const Color.fromARGB(255, 198, 215, 197), color: const Color.fromARGB(255, 219, 230, 221),
shape: const CircleBorder(), shape: const CircleBorder(),
child: Stack( child: Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
@ -98,7 +98,7 @@ class _CustomHeaderState extends State<CustomHeader> {
IconButton( IconButton(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
icon: const Icon(Icons.notifications_none, icon: const Icon(Icons.notifications_none,
color: Colors.black87, size: 28), color: Color.fromARGB(221, 57, 35, 35), size: 28),
onPressed: widget.onNotificationTap ?? onPressed: widget.onNotificationTap ??
() { () {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(

View File

@ -8,6 +8,26 @@ class ThermalHeatmap extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return Center(
child: Container(
width: 310,
height: 310,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18),
border: Border.all(
color: Colors.deepOrangeAccent.withOpacity(0.6),
width: 3,
),
boxShadow: [
BoxShadow(
color: Colors.deepOrangeAccent.withOpacity(0.15),
blurRadius: 8,
spreadRadius: 1,
offset: Offset(0, 3),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(18),
child: SizedBox( child: SizedBox(
width: 300, width: 300,
height: 300, height: 300,
@ -15,6 +35,8 @@ class ThermalHeatmap extends StatelessWidget {
painter: _ThermalPainter(temperatures), painter: _ThermalPainter(temperatures),
), ),
), ),
),
),
); );
} }
} }
@ -50,13 +72,7 @@ class _ThermalPainter extends CustomPainter {
} }
} }
final center = Offset(size.width / 2, size.height / 2); // Garis putih di tengah dihilangkan (dihapus)
final plusPaint = Paint()
..color = Colors.white.withOpacity(0.95)
..strokeWidth = 2
..strokeCap = StrokeCap.round;
canvas.drawLine(center - const Offset(10, 0), center + const Offset(10, 0), plusPaint);
canvas.drawLine(center - const Offset(0, 10), center + const Offset(0, 10), plusPaint);
} }
Color _getColorForTemp(double t) { Color _getColorForTemp(double t) {