TKK_E32230176/Source-Code-Web/lib/screens/home_screen.dart

574 lines
19 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/mqtt_provider.dart';
import '../widgets/webcam_view.dart';
import '../widgets/control_panel.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Kontrol AC Ruang Kelas'),
backgroundColor: Colors.blue[800],
foregroundColor: Colors.white,
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
Provider.of<MQTTProvider>(context, listen: false).connect();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Menghubungkan ulang...'),
duration: Duration(seconds: 1)),
);
},
),
],
),
body: Consumer<MQTTProvider>(
builder: (context, provider, child) {
return RefreshIndicator(
onRefresh: () async => provider.connect(),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildConnectionStatus(provider),
const SizedBox(height: 16),
_buildWebcamSection(provider),
const SizedBox(height: 16),
_buildModeSwitch(context, provider),
const SizedBox(height: 16),
_buildStatusCards(provider),
const SizedBox(height: 8),
if (provider.acStatus == "on") _buildActiveTime(provider),
const SizedBox(height: 16),
if (provider.isAutoMode) _buildTimerSlider(provider),
const SizedBox(height: 16),
if (!provider.isAutoMode) ControlPanel(provider: provider),
if (provider.isDelayActive) _buildDelayIndicator(provider),
const SizedBox(height: 16),
_buildModeInfo(provider),
],
),
),
);
},
),
);
}
Widget _buildConnectionStatus(MQTTProvider provider) {
Color statusColor;
IconData statusIcon;
switch (provider.connectionStatus) {
case "Connected":
statusColor = Colors.green;
statusIcon = Icons.check_circle;
break;
case "Connecting...":
statusColor = Colors.orange;
statusIcon = Icons.hourglass_empty;
break;
default:
statusColor = Colors.red;
statusIcon = Icons.error;
}
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: statusColor),
),
child: Row(
children: [
Icon(statusIcon, color: statusColor),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Status Koneksi',
style: TextStyle(fontSize: 12, color: Colors.grey[600])),
Text(provider.connectionStatus,
style: TextStyle(
fontWeight: FontWeight.bold, color: statusColor)),
],
),
),
if (provider.connectionStatus != "Connected")
TextButton(
onPressed: () => provider.connect(),
child: const Text('Hubungkan'),
),
],
),
);
}
Widget _buildWebcamSection(MQTTProvider provider) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.videocam, color: Colors.blue),
SizedBox(width: 8),
Text('Monitoring Ruangan',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 8),
const Text('Deteksi kehadiran real-time',
style: TextStyle(fontSize: 12, color: Colors.grey)),
const SizedBox(height: 12),
SizedBox(height: 300, child: WebcamView()),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(Icons.people, size: 16, color: provider.presenceColor),
const SizedBox(width: 4),
Text(
'Status: ${provider.presenceStatus == "ada" ? "👥 Ada orang" : "🚪 Ruangan kosong"}',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: provider.presenceColor),
),
],
),
Text(
'Frame: ${provider.detectionCount}',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
],
);
}
// ==================== MODE SWITCH (RESPONSIF UNTUK HP & DESKTOP) ====================
Widget _buildModeSwitch(BuildContext context, MQTTProvider provider) {
// Deteksi ukuran layar
final screenWidth = MediaQuery.of(context).size.width;
final isSmallScreen = screenWidth < 500;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: provider.isAutoMode
? [Colors.green.shade700, Colors.green.shade500]
: [Colors.orange.shade700, Colors.orange.shade500],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
),
child: isSmallScreen
? _buildMobileModeSwitch(provider, context)
: _buildDesktopModeSwitch(provider, context),
);
}
// Layout untuk Desktop/Laptop (lebar layar > 500px)
Widget _buildDesktopModeSwitch(MQTTProvider provider, BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(provider.isAutoMode ? Icons.autorenew : Icons.touch_app,
color: Colors.white, size: 28),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
provider.isAutoMode ? 'Mode Otomatis' : 'Mode Manual',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white),
),
Text(
provider.isAutoMode
? 'AC dikontrol berdasarkan kehadiran'
: 'Kontrol AC secara manual dari tombol',
style: TextStyle(
fontSize: 12, color: Colors.white.withOpacity(0.9)),
),
],
),
],
),
ElevatedButton(
onPressed: () {
provider.toggleAutoMode();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(provider.isAutoMode
? '✅ Mode Auto aktif'
: '✋ Mode Manual aktif'),
backgroundColor:
provider.isAutoMode ? Colors.green : Colors.orange,
duration: const Duration(seconds: 2),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: provider.isAutoMode ? Colors.green : Colors.orange,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(provider.isAutoMode ? Icons.smart_toy : Icons.people,
size: 20),
const SizedBox(width: 8),
Text(provider.isAutoMode ? 'Ganti ke Manual' : 'Ganti ke Auto'),
],
),
),
],
);
}
// Layout untuk Mobile/HP (lebar layar < 500px)
Widget _buildMobileModeSwitch(MQTTProvider provider, BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Baris atas: icon dan teks
Row(
children: [
Icon(provider.isAutoMode ? Icons.autorenew : Icons.touch_app,
color: Colors.white, size: 28),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
provider.isAutoMode ? 'Mode Otomatis' : 'Mode Manual',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white),
),
Text(
provider.isAutoMode
? 'AC dikontrol berdasarkan kehadiran'
: 'Kontrol AC secara manual',
style: TextStyle(
fontSize: 11, color: Colors.white.withOpacity(0.9)),
),
],
),
),
],
),
const SizedBox(height: 12),
// Baris bawah: tombol (lebar penuh)
ElevatedButton(
onPressed: () {
provider.toggleAutoMode();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(provider.isAutoMode
? '✅ Mode Auto aktif'
: '✋ Mode Manual aktif'),
backgroundColor:
provider.isAutoMode ? Colors.green : Colors.orange,
duration: const Duration(seconds: 2),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: provider.isAutoMode ? Colors.green : Colors.orange,
padding: const EdgeInsets.symmetric(vertical: 12),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(provider.isAutoMode ? Icons.smart_toy : Icons.people,
size: 18),
const SizedBox(width: 8),
Text(provider.isAutoMode ? 'Ganti ke Manual' : 'Ganti ke Auto'),
],
),
),
],
);
}
// ==================== END MODE SWITCH ====================
Widget _buildStatusCards(MQTTProvider provider) {
return Row(
children: [
Expanded(
child: _buildStatusCard(
title: 'Status AC',
value: provider.acStatus == "on" ? "❄️ MENYALA" : "⭕ MATI",
icon: provider.acIcon,
color: provider.acColor,
),
),
const SizedBox(width: 16),
Expanded(
child: _buildStatusCard(
title: 'Mode Operasi',
value: provider.isAutoMode ? "🤖 OTOMATIS" : "👆 MANUAL",
icon: provider.isAutoMode ? Icons.autorenew : Icons.touch_app,
color: provider.isAutoMode ? Colors.green : Colors.orange,
),
),
],
);
}
Widget _buildStatusCard(
{required String title,
required String value,
required IconData icon,
required Color color}) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [color.withOpacity(0.2), color.withOpacity(0.05)],
),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 24),
),
const SizedBox(width: 12),
Text(title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.grey[600])),
],
),
const SizedBox(height: 12),
Text(value,
style: TextStyle(
fontSize: 22, fontWeight: FontWeight.bold, color: color)),
],
),
),
);
}
Widget _buildActiveTime(MQTTProvider provider) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.blue.shade200),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.timer, color: Colors.blue, size: 20),
const SizedBox(width: 8),
Text(
'Waktu Aktif: ${provider.activeTime}',
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.blue),
),
],
),
);
}
Widget _buildTimerSlider(MQTTProvider provider) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.timer, color: Colors.orange),
SizedBox(width: 8),
Text('Timer Mati Otomatis',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 8),
const Text(
'AC akan mati otomatis setelah ruangan kosong selama waktu yang ditentukan',
style: TextStyle(fontSize: 12, color: Colors.grey)),
const SizedBox(height: 12),
Row(
children: [
const Text('Delay: ',
style: TextStyle(fontWeight: FontWeight.bold)),
Expanded(
child: Slider(
value: provider.delayTimer.toDouble(),
min: 1,
max: 60,
divisions: 59,
label: _formatDelayTime(provider.delayTimer),
onChanged: (value) {
provider.setDelayTimer(value.toInt());
},
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Colors.orange.shade100,
borderRadius: BorderRadius.circular(20),
),
child: Text(
_formatDelayTime(provider.delayTimer),
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.orange.shade800),
),
),
],
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [1, 5, 10, 15, 30, 45, 60].map((minutes) {
return ActionChip(
label: Text(_formatDelayTimeShort(minutes)),
onPressed: () => provider.setDelayTimer(minutes),
backgroundColor: provider.delayTimer == minutes
? Colors.orange
: Colors.grey.shade200,
labelStyle: TextStyle(
color: provider.delayTimer == minutes
? Colors.white
: Colors.black87,
fontSize: 12,
),
);
}).toList(),
),
],
),
);
}
Widget _buildDelayIndicator(MQTTProvider provider) {
final minutes = provider.delayRemaining ~/ 60;
final seconds = provider.delayRemaining % 60;
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.orange),
),
child: Row(
children: [
const Icon(Icons.hourglass_empty, color: Colors.orange),
const SizedBox(width: 8),
Expanded(
child: Text(
'⏰ AC akan mati dalam ${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}',
style: const TextStyle(
color: Colors.orange, fontWeight: FontWeight.w500),
),
),
],
),
);
}
Widget _buildModeInfo(MQTTProvider provider) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: provider.isAutoMode
? Colors.green.withOpacity(0.1)
: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: provider.isAutoMode ? Colors.green : Colors.orange),
),
child: Row(
children: [
Icon(provider.isAutoMode ? Icons.info_outline : Icons.warning_amber,
color: provider.isAutoMode ? Colors.green : Colors.orange),
const SizedBox(width: 8),
Expanded(
child: Text(
provider.isAutoMode
? '🤖 Mode Otomatis: AC dikontrol berdasarkan kehadiran. Mati otomatis setelah ${provider.delayTimer} menit ruangan kosong'
: '👆 Mode Manual: Kontrol AC manual dari tombol di bawah. Timer tidak aktif',
style: TextStyle(
color: provider.isAutoMode
? Colors.green[800]
: Colors.orange[800],
fontSize: 13),
),
),
],
),
);
}
String _formatDelayTime(int minutes) {
if (minutes < 60) {
return '$minutes menit';
} else {
return '1 jam';
}
}
String _formatDelayTimeShort(int minutes) {
if (minutes < 60) {
return '${minutes}m';
} else {
return '1j';
}
}
}