diff --git a/lib/screens/control/control_screen.dart b/lib/screens/control/control_screen.dart index b55c96e..d785957 100644 --- a/lib/screens/control/control_screen.dart +++ b/lib/screens/control/control_screen.dart @@ -11,28 +11,41 @@ class ControlScreen extends StatefulWidget { class _ControlScreenState extends State { bool isAutoMode = true; // default: mode otomatis + bool isRepellerOn = false; // status pengusir (manual mode) void toggleMode() { setState(() { isAutoMode = !isAutoMode; + if (isAutoMode) { + isRepellerOn = false; // reset repeller saat mode otomatis + } }); } - void triggerRepeller() { + void toggleRepeller() { + setState(() { + isRepellerOn = !isRepellerOn; + }); + showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - title: const Row( + title: Row( children: [ - Icon(Icons.check_circle_outline, color: Colors.green), - SizedBox(width: 8), - Text('Berhasil'), + Icon( + isRepellerOn ? Icons.check_circle : Icons.power_settings_new, + color: isRepellerOn ? Colors.green : Colors.grey, + ), + const SizedBox(width: 8), + Text(isRepellerOn ? 'Diaktifkan' : 'Dimatikan'), ], ), - content: const Text( - 'Pengusir hama berhasil diaktifkan secara manual.', - style: TextStyle(fontSize: 16), + content: Text( + isRepellerOn + ? 'Pengusir hama berhasil diaktifkan.' + : 'Pengusir hama dimatikan.', + style: const TextStyle(fontSize: 16), ), actions: [ TextButton( @@ -59,51 +72,53 @@ class _ControlScreenState extends State { context, MaterialPageRoute( builder: (context) => NotificationScreen(), - ), + ), ); }, ), - body: Padding( padding: const EdgeInsets.all(20), child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const SizedBox(height: 10), - const Text( - 'Mode Operasi', - style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 16), Card( - elevation: 3, + elevation: 4, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), + borderRadius: BorderRadius.circular(16), + ), + color: isAutoMode ? Colors.blue.shade50 : Colors.orange.shade50, child: Padding( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(20), child: Row( children: [ - Icon( - isAutoMode ? Icons.autorenew : Icons.handyman, - color: isAutoMode ? Colors.blue : Colors.orange, - size: 36, + CircleAvatar( + backgroundColor: isAutoMode ? Colors.blue : Colors.orange, + radius: 28, + child: Icon( + isAutoMode ? Icons.autorenew : Icons.handyman, + color: Colors.white, + size: 30, + ), ), - const SizedBox(width: 16), + const SizedBox(width: 20), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( isAutoMode ? 'Mode Otomatis' : 'Mode Manual', - style: const TextStyle( - fontSize: 18, fontWeight: FontWeight.w600), + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: isAutoMode ? Colors.blue : Colors.orange, + ), ), - const SizedBox(height: 4), + const SizedBox(height: 6), Text( isAutoMode - ? 'Alat akan bekerja secara otomatis sesuai sensor' - : 'Kamu dapat mengaktifkan alat secara manual', - style: const TextStyle( - fontSize: 14, color: Colors.grey), + ? 'Alat akan aktif otomatis sesuai sensor.' + : 'Kamu dapat mengendalikan alat secara manual.', + style: const TextStyle(color: Colors.black87), ), ], ), @@ -117,19 +132,74 @@ class _ControlScreenState extends State { ), ), ), - const SizedBox(height: 30), + + const SizedBox(height: 20), + + // CARD 2: STATUS PENGUSIR (Tampil hanya di mode manual) + if (!isAutoMode) + Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + color: + isRepellerOn ? Colors.green.shade50 : Colors.grey.shade100, + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + Icon( + isRepellerOn + ? Icons.volume_up_rounded + : Icons.volume_off_rounded, + color: isRepellerOn ? Colors.green : Colors.grey, + size: 32, + ), + const SizedBox(width: 20), + Expanded( + child: Text( + isRepellerOn + ? 'Pengusir aktif secara manual.' + : 'Pengusir dalam keadaan mati.', + style: TextStyle( + fontSize: 16, + color: isRepellerOn + ? Colors.green.shade700 + : Colors.grey, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 20), + + // TOMBOL AKSI (Manual mode) if (!isAutoMode) ElevatedButton.icon( - onPressed: triggerRepeller, - icon: const Icon(Icons.volume_up), - label: const Text('Aktifkan Pengusir Sekarang'), + onPressed: toggleRepeller, + icon: Icon( + isRepellerOn + ? Icons.power_settings_new_rounded + : Icons.volume_up_rounded, + ), + label: Text( + isRepellerOn + ? 'Matikan Pengusir' + : 'Aktifkan Pengusir Sekarang', + style: const TextStyle(fontSize: 16), + ), style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, + backgroundColor: + isRepellerOn ? Colors.grey.shade700 : Colors.red.shade600, foregroundColor: Colors.white, padding: - const EdgeInsets.symmetric(vertical: 14, horizontal: 24), + const EdgeInsets.symmetric(vertical: 16, horizontal: 24), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), + borderRadius: BorderRadius.circular(14)), minimumSize: const Size.fromHeight(50), ), ), diff --git a/lib/screens/dashboard/dashboard_screen.dart b/lib/screens/dashboard/dashboard_screen.dart index 377f38a..36173e7 100644 --- a/lib/screens/dashboard/dashboard_screen.dart +++ b/lib/screens/dashboard/dashboard_screen.dart @@ -78,7 +78,7 @@ class _DashboardScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color.fromARGB(255, 242, 242, 242), + backgroundColor: Colors.grey[100], appBar: CustomHeader( deviceName: 'HamaGuard', notificationCount: 5, @@ -110,14 +110,14 @@ class _DashboardScreenState extends State { Row( children: const [ Icon(Icons.thermostat_outlined, - color: Colors.deepOrangeAccent, size: 30), + color: Color.fromRGBO(56, 142, 60, 1), size: 30), SizedBox(width: 12), Text( 'Thermal Sensor', style: TextStyle( fontWeight: FontWeight.w600, fontSize: 20, - color: Colors.deepOrangeAccent, + color: Color.fromRGBO(56, 142, 60, 1), ), ), ], diff --git a/lib/screens/notification/notification_screen.dart b/lib/screens/notification/notification_screen.dart index 71000f5..2d05f86 100644 --- a/lib/screens/notification/notification_screen.dart +++ b/lib/screens/notification/notification_screen.dart @@ -3,16 +3,19 @@ import 'package:flutter/material.dart'; class NotificationScreen extends StatelessWidget { final List> notifications = [ { + "type": "warning", "title": "Peringatan!", "message": "Hama terdeteksi di area sawah A.", "time": "16 Mei 2025 - 10:15" }, { + "type": "safe", "title": "Status Aman", "message": "Tidak ada aktivitas hama selama 2 jam terakhir.", "time": "16 Mei 2025 - 09:00" }, { + "type": "info", "title": "Mode Otomatis Aktif", "message": "Sistem kembali ke mode otomatis.", "time": "15 Mei 2025 - 17:20" @@ -21,14 +24,50 @@ class NotificationScreen extends StatelessWidget { NotificationScreen({super.key}); + Color _getColor(String type) { + switch (type) { + case 'warning': + return Colors.red.shade100; + case 'safe': + return Colors.green.shade100; + case 'info': + default: + return Colors.blue.shade100; + } + } + + IconData _getIcon(String type) { + switch (type) { + case 'warning': + return Icons.warning_amber_rounded; + case 'safe': + return Icons.check_circle_outline; + case 'info': + default: + return Icons.info_outline; + } + } + + Color _getIconColor(String type) { + switch (type) { + case 'warning': + return Colors.red; + case 'safe': + return Colors.green; + case 'info': + default: + return Colors.blue; + } + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Notifikasi'), - backgroundColor: Colors.white, - foregroundColor: Colors.black, - elevation: 1, + backgroundColor: Colors.green[700], + foregroundColor: Colors.white, + elevation: 2, ), body: notifications.isEmpty ? const Center( @@ -37,22 +76,53 @@ class NotificationScreen extends StatelessWidget { style: TextStyle(fontSize: 16, color: Colors.grey), ), ) - : ListView.separated( - itemCount: notifications.length, + : ListView.builder( padding: const EdgeInsets.all(16), - separatorBuilder: (context, index) => const Divider(), + itemCount: notifications.length, itemBuilder: (context, index) { final notif = notifications[index]; - return ListTile( - leading: const Icon(Icons.notifications, color: Colors.blue), - title: Text( - notif["title"]!, - style: const TextStyle(fontWeight: FontWeight.bold), + final type = notif["type"] ?? "info"; + + return Card( + color: _getColor(type), + margin: const EdgeInsets.only(bottom: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), ), - subtitle: Text(notif["message"]!), - trailing: Text( - notif["time"]!, - style: const TextStyle(fontSize: 12, color: Colors.grey), + elevation: 3, + child: ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), + leading: Icon( + _getIcon(type), + color: _getIconColor(type), + size: 32, + ), + title: Text( + notif["title"]!, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Text( + notif["message"]!, + style: const TextStyle(fontSize: 14), + ), + const SizedBox(height: 6), + Text( + notif["time"]!, + style: const TextStyle( + fontSize: 12, + color: Colors.black54, + ), + ), + ], + ), ), ); }, diff --git a/lib/screens/setting/setting_screen.dart b/lib/screens/setting/setting_screen.dart index dfc8ea1..b12828c 100644 --- a/lib/screens/setting/setting_screen.dart +++ b/lib/screens/setting/setting_screen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import '../../widgets/costum_header.dart'; +import '../../screens/notification/notification_screen.dart'; class SettingScreen extends StatelessWidget { const SettingScreen({super.key}); @@ -7,73 +9,60 @@ class SettingScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[100], + appBar: CustomHeader( + deviceName: 'HamaGuard', + notificationCount: 5, + onNotificationTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => NotificationScreen(), + ), + ); + }, + ), body: ListView( padding: const EdgeInsets.all(16), children: [ - const Text( - 'Pengaturan', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 20), - - // Contoh opsi 1: Ganti mode manual/otomatis - ListTile( - leading: const Icon(Icons.toggle_on), - title: const Text('Mode Manual/Otomatis'), - trailing: Switch( - value: true, // sementara default ON, nanti bisa dihubungkan state nyata - onChanged: (val) { - }, - ), - ), - - const Divider(), - - // Contoh opsi 2: Notifikasi aktif/nonaktif - ListTile( - leading: const Icon(Icons.notifications), - title: const Text('Notifikasi'), - trailing: Switch( + Card( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + elevation: 2, + child: SwitchListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + secondary: const Icon(Icons.notifications_active_outlined), + title: const Text('Notifikasi'), value: true, onChanged: (val) { - + // Tambahkan logika notifikasi di sini jika dibutuhkan }, ), ), - - const Divider(), - - // Contoh opsi 3: Reset data (tombol) - ListTile( - leading: const Icon(Icons.refresh), - title: const Text('Reset Data'), - onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Data direset')), - ); - }, - ), - - const Divider(), - - // Contoh opsi 4: Tentang aplikasi - ListTile( - leading: const Icon(Icons.info_outline), - title: const Text('Tentang'), - subtitle: const Text('Versi 1.0.0\nDibuat oleh Tim HamaGuard'), - isThreeLine: true, - onTap: () { - // Bisa tampilkan dialog info lebih lengkap - showAboutDialog( - context: context, - applicationName: 'HamaGuard', - applicationVersion: '1.0.0', - applicationIcon: const Icon(Icons.bug_report, size: 40), - children: const [ - Text('Aplikasi pendeteksi dan pengusir hama otomatis.'), - ], - ); - }, + const SizedBox(height: 16), + Card( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + elevation: 2, + child: ListTile( + leading: const Icon(Icons.info_outline), + title: const Text('Tentang Aplikasi'), + subtitle: const Text('Versi 1.0.0'), + onTap: () { + showAboutDialog( + context: context, + applicationName: 'HamaGuard', + applicationVersion: '1.0.0', + applicationIcon: const Icon(Icons.bug_report, size: 40), + children: const [ + Padding( + padding: EdgeInsets.only(top: 12), + child: Text( + 'Aplikasi monitoring dan pengusir hama otomatis berbasis sensor.'), + ), + ], + ); + }, + ), ), ], ), diff --git a/lib/widgets/costum_header.dart b/lib/widgets/costum_header.dart index 095e7ce..b409e18 100644 --- a/lib/widgets/costum_header.dart +++ b/lib/widgets/costum_header.dart @@ -4,7 +4,7 @@ import 'package:intl/intl.dart'; class CustomHeader extends StatefulWidget implements PreferredSizeWidget { final String deviceName; final VoidCallback? onNotificationTap; - final int notificationCount; // Tambahan + final int notificationCount; const CustomHeader({ super.key, @@ -14,7 +14,8 @@ class CustomHeader extends StatefulWidget implements PreferredSizeWidget { }); @override - Size get preferredSize => const Size.fromHeight(kToolbarHeight + 24); + Size get preferredSize => + const Size.fromHeight(100); // Disesuaikan agar tidak overflow @override State createState() => _CustomHeaderState(); @@ -49,67 +50,67 @@ class _CustomHeaderState extends State { @override Widget build(BuildContext context) { - return AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - automaticallyImplyLeading: false, - titleSpacing: 16, - title: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // Nama alat dan tanggal - Padding( - padding: const EdgeInsets.only(right: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - widget.deviceName, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color.fromRGBO(56, 142, 60, 1), + return ClipRRect( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(24), + bottomRight: Radius.circular(24), + ), + child: Container( + color: Colors.green[700], + padding: + const EdgeInsets.only(top: 16, left: 16, right: 16, bottom: 16), + child: SafeArea( + bottom: false, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Nama alat dan tanggal + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + widget.deviceName, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Colors.white, + ), ), - ), - const SizedBox(height: 4), - Text( - _dateString, - style: TextStyle( - fontSize: 12, - color: Colors.grey[600], - height: 1.2, + const SizedBox(height: 4), + Text( + _dateString, + style: TextStyle( + fontSize: 12, + color: Colors.white70, + height: 1.2, + ), ), - ), - ], - ), - ), - const Spacer(), + ], + ), + const Spacer(), - // Icon lonceng + badge notifikasi - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Material( - color: const Color.fromARGB(255, 219, 230, 221), - shape: const CircleBorder(), - child: Stack( + // Icon notifikasi + Stack( clipBehavior: Clip.none, children: [ - IconButton( - padding: const EdgeInsets.all(8), - icon: const Icon(Icons.notifications_none, - color: Color.fromARGB(221, 57, 35, 35), size: 28), - onPressed: widget.onNotificationTap ?? - () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Notifikasi belum tersedia')), - ); - }, - splashRadius: 24, + Material( + color: Colors.green[600], + shape: const CircleBorder(), + child: IconButton( + padding: const EdgeInsets.all(8), + icon: const Icon(Icons.notifications_none, + color: Colors.white, size: 28), + onPressed: widget.onNotificationTap ?? + () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Notifikasi belum tersedia')), + ); + }, + splashRadius: 24, + ), ), - - // Badge jika notificationCount > 0 if (widget.notificationCount > 0) Positioned( right: 4, @@ -136,9 +137,9 @@ class _CustomHeaderState extends State { ), ], ), - ), + ], ), - ], + ), ), ); } diff --git a/lib/widgets/thermal_heatmap.dart b/lib/widgets/thermal_heatmap.dart index be2bed6..2a34de7 100644 --- a/lib/widgets/thermal_heatmap.dart +++ b/lib/widgets/thermal_heatmap.dart @@ -14,12 +14,12 @@ class ThermalHeatmap extends StatelessWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(18), border: Border.all( - color: Colors.deepOrangeAccent.withOpacity(0.6), + color: const Color.fromRGBO(56, 142, 60, 1).withOpacity(0.6), width: 3, ), boxShadow: [ BoxShadow( - color: Colors.deepOrangeAccent.withOpacity(0.15), + color: const Color.fromRGBO(56, 142, 60, 1).withOpacity(0.15), blurRadius: 8, spreadRadius: 1, offset: Offset(0, 3),