update thermal logic
This commit is contained in:
parent
8aa455758b
commit
3ae23dfdef
|
@ -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),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -8,11 +8,33 @@ class ThermalHeatmap extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
return Center(
|
||||||
child: SizedBox(
|
child: Container(
|
||||||
width: 300,
|
width: 310,
|
||||||
height: 300,
|
height: 310,
|
||||||
child: CustomPaint(
|
decoration: BoxDecoration(
|
||||||
painter: _ThermalPainter(temperatures),
|
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(
|
||||||
|
width: 300,
|
||||||
|
height: 300,
|
||||||
|
child: CustomPaint(
|
||||||
|
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) {
|
||||||
|
|
Loading…
Reference in New Issue