import 'dart:typed_data'; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:mqtt_client/mqtt_server_client.dart'; import 'package:mqtt_client/mqtt_client.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:beta_app1/login/api_service.dart'; import 'package:beta_app1/login/user.dart'; import 'package:beta_app1/login/login.dart'; import 'package:beta_app1/page/control.dart'; import 'package:beta_app1/page/monitoring.dart'; import 'package:beta_app1/page/info.dart'; import 'package:beta_app1/page/homecontent.dart'; final sensorData = SensorData(); class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State { int _currentIndex = 0; bool _isConnected = false; late MqttServerClient _client; late StreamSubscription>>? subscription; final ApiService apiService = ApiService(); FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); List _children = []; double? tdsmin; double? tdsmax; double? tdsnow; @override void initState() { super.initState(); _initializeClient(); var initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); var initializationSettings = InitializationSettings(android: initializationSettingsAndroid); flutterLocalNotificationsPlugin .initialize(initializationSettings) .then((_) { print('Notification plugin initialized'); }); } void _initializeClient() async { final prefs = await SharedPreferences.getInstance(); String mqttServerIp = prefs.getString('mqtt_server_ip') ?? '192.168.1.100'; String baseUrl = 'http://$mqttServerIp'; _client = MqttServerClient(mqttServerIp, 'Mqtt_Flutter'); _client.port = 1883; _client.keepAlivePeriod = 20; _client.connectTimeoutPeriod = 2000; _client.onConnected = _onConnected; _client.onDisconnected = _onDisconnected; _client.autoReconnect = true; _client.onAutoReconnect = _onAutoReconnect; _client.onAutoReconnected = _onAutoReconnected; final connMess = MqttConnectMessage() .withClientIdentifier('Mqtt_Flutter') .withWillTopic('willtopic') .withWillMessage('My Will message') .startClean() .withWillQos(MqttQos.atLeastOnce); print('Connecting to Mosquitto client...'); _client.connectionMessage = connMess; try { await _client.connect().timeout(Duration(seconds: 1)); setState(() { _isConnected = true; }); _subscribeToTopics(); } catch (e) { print('Exception: $e'); _showErrorDialog('Failed to connect to MQTT broker'); } _children = [ HomeContent(), SensorMonitoringScreen(client: _client), ControlScreen(client: _client), InfoPage(), ]; } void _onConnected() { print('Connected'); setState(() { _isConnected = true; }); } void _onDisconnected() { print('Disconnected'); setState(() { _isConnected = false; }); } void _onAutoReconnect() { print('Attempting to reconnect...'); setState(() { _isConnected = false; }); } void _onAutoReconnected() { print('Successfully reconnected'); setState(() { _isConnected = true; }); } //Fungsi untuk subscribe Topic MQTT sensor void _subscribeToTopics() { _client.subscribe('sensor/tds', MqttQos.atMostOnce); _client.subscribe('sensor/waterflow1', MqttQos.atLeastOnce); _client.subscribe('sensor/waterflow2', MqttQos.atLeastOnce); _client.subscribe('sensor/ultrasonic', MqttQos.atLeastOnce); _client.subscribe( 'parameter', MqttQos.atLeastOnce); // Subscribe to parameter topic subscription = _client.updates!.listen( (List> messages) async { final message = messages[0].payload as MqttPublishMessage; final payload = MqttPublishPayload.bytesToStringAsString(message.payload.message); print('Received message: $payload from topic: ${messages[0].topic}'); try { if (messages[0].topic == 'sensor/waterflow1' || messages[0].topic == 'sensor/waterflow2') { Map data = jsonDecode(payload); if (data.containsKey('speed_waterflow') && data['speed_waterflow'] != null && data.containsKey('total_cairan') && data['total_cairan'] != null) { double flowRatePerSecond = (data['speed_waterflow'] as num).toDouble(); double totalFlow = (data['total_cairan'] as num).toDouble(); if (mounted) { setState(() { if (messages[0].topic == 'sensor/waterflow1') { sensorData.waterflow1Value = totalFlow; sensorData.waterflow1speed = flowRatePerSecond; } else if (messages[0].topic == 'sensor/waterflow2') { sensorData.waterflow2Value = totalFlow; sensorData.waterflow2speed = flowRatePerSecond; } }); } // Simpan data ke database await saveDataToDatabase( messages[0].topic, totalFlow, flowRatePerSecond); } else { print('Invalid data format in ${messages[0].topic}: $data'); } } else if (messages[0].topic == 'parameter') { Map data = jsonDecode(payload); if (data.containsKey('tdsmin') && data['tdsmin'] != null) { setState(() { tdsmin = (data['tdsmin'] as num).toDouble(); }); print('Updated tdsmin: $tdsmin'); } if (data.containsKey('tdsmax') && data['tdsmax'] != null) { setState(() { tdsmax = (data['tdsmax'] as num).toDouble(); }); print('Updated tdsmax: $tdsmax'); } } else { double? value; try { value = double.parse(payload); } catch (e) { print('Error parsing payload: $e'); return; } if (mounted) { setState(() { switch (messages[0].topic) { case 'sensor/tds': sensorData.tdsValue = value; tdsnow = value; if (tdsnow != null && tdsmax != null && tdsnow! > tdsmax!) { _showNotification( 'TDS Alert', 'Nilai TDS ($tdsnow) melebihi batas maksimum ($tdsmax). Mohon segera tambahkan air', ); } break; case 'sensor/ultrasonic': sensorData.ultrasonicValue = value; break; default: print('Unknown topic: ${messages[0].topic}'); } }); } // Simpan data ke database await saveDataToDatabase(messages[0].topic, value, null); } } catch (e) { print('Exception in _subscribeToTopics: $e'); } }, ); } Future _showNotification(String title, String body) async { print('Attempting to show notification'); final Int64List vibrationPattern = Int64List(4); vibrationPattern[0] = 0; vibrationPattern[1] = 1000; vibrationPattern[2] = 5000; vibrationPattern[3] = 2000; final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( 'alerts_channel', 'Alerts', channelDescription: 'Channel for important alerts', importance: Importance.max, priority: Priority.high, ticker: 'ticker', vibrationPattern: vibrationPattern, enableVibration: true, enableLights: true, color: const Color.fromARGB(255, 255, 0, 0), ledColor: const Color.fromARGB(255, 255, 0, 0), ledOnMs: 1000, ledOffMs: 500, ); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); try { await flutterLocalNotificationsPlugin.show( 0, title, body, notificationDetails, ); print('Notification shown successfully'); } catch (e) { print('Error showing notification: $e'); } } Future saveTdsValues(double tdsmin, double tdsmax) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setDouble('tdsmin', tdsmin); await prefs.setDouble('tdsmax', tdsmax); } Future> loadTdsValues() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); double? tdsmin = prefs.getDouble('tdsmin'); double? tdsmax = prefs.getDouble('tdsmax'); return {'tdsmin': tdsmin, 'tdsmax': tdsmax}; } // Fungsi untuk menyimpan data ke database Future saveDataToDatabase( String topic, double value, double? speed) async { final prefs = await SharedPreferences.getInstance(); String mqttServerIp = prefs.getString('mqtt_server_ip') ?? '192.168.1.100'; String url = 'http://$mqttServerIp/test_api/save_msg.php'; final response = await http.post( Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, body: jsonEncode( {'topic': topic, 'value': value, 'speed': speed}), ); if (response.statusCode == 200) { print('Data berhasil disimpan ke database'); } else { print('Gagal menyimpan data ke database: ${response.body}'); } } void onTabTapped(int index) { setState(() { _currentIndex = index; }); } Future removeAuthToken() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove('auth_token'); } void _logout() async { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('auth_token'); if (token != null) { try { await apiService.logout(token); await removeAuthToken(); Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => LoginPage()), ); } catch (e) { _showErrorDialog('Failed to logout. Please try again'); } } } void _showErrorDialog(String message) { TextEditingController _controller = TextEditingController(); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('Error'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(message), SizedBox(height: 20), Text('Masukkan IP Server:'), TextField( controller: _controller, decoration: InputDecoration(hintText: 'Contoh: 192.168.1.100'), ), ], ), actions: [ TextButton( onPressed: () async { String ipServer = _controller.text.trim(); if (ipServer.isNotEmpty) { final prefs = await SharedPreferences.getInstance(); await prefs.setString('mqtt_server_ip', ipServer); try { _client.disconnect(); _initializeClient(); } catch (e) { print('Exception: $e'); _showErrorDialog('failed to reconnect to MQTT Broker'); } Navigator.of(context).pop(); } }, child: Text('Simpan'), ), ], ); }, ); } void _toggleConnection() async { if (_isConnected) { _client.disconnect(); } else { final prefs = await SharedPreferences.getInstance(); String ipServer = prefs.getString('mqtt_server_ip') ?? '192.168.65.153'; // Default IP try { await _client.connect(ipServer); } catch (e) { print('Exception: $e'); _showErrorDialog('Failed to connect to MQTT broker'); } } } @override void dispose() { subscription?.cancel(); _client.disconnect(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( ['Dashboard', 'Monitoring', 'Control', 'Info'][_currentIndex], style: TextStyle( fontWeight: FontWeight.bold, color: Color.fromARGB(255, 66, 66, 66)), ), centerTitle: true, ), drawer: Drawer( child: FutureBuilder>( future: apiService.fetchUsers(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { return Center(child: Text('No data available')); } User user = snapshot.data!.first; return Container( color: Colors.white, child: ListView( padding: EdgeInsets.zero, children: [ UserAccountsDrawerHeader( decoration: const BoxDecoration( gradient: LinearGradient( colors: [Colors.green, Colors.teal], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), accountName: Text( user.fullname, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 22, color: Colors.white, ), ), accountEmail: Text( user.username, style: TextStyle( fontSize: 16, color: Colors.white.withOpacity(0.8), ), ), currentAccountPicture: const CircleAvatar( backgroundImage: AssetImage('images/ahri_icon.jpg'), ), ), ListTile( leading: const Icon(Icons.logout, color: Colors.red), title: Text( 'Logout', style: TextStyle(fontSize: 16, color: Colors.red), ), onTap: _logout, ), ], ), ); }, ), ), body: Stack( children: [ AnimatedSwitcher( duration: const Duration(milliseconds: 300), transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: animation, child: child, ); }, child: _currentIndex >= 0 && _currentIndex < _children.length ? _children[_currentIndex] : Container(), ), ], ), bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, onTap: onTabTapped, items: const [ BottomNavigationBarItem( icon: Icon(Icons.home_filled), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Icons.monitor), label: 'Monitor', ), BottomNavigationBarItem( icon: Icon(Icons.settings), label: 'Control', ), BottomNavigationBarItem( icon: Icon(Icons.bookmark), label: 'Info', ), ], type: BottomNavigationBarType.fixed, selectedItemColor: Colors.green, unselectedItemColor: Colors.grey, showSelectedLabels: true, showUnselectedLabels: false, selectedLabelStyle: const TextStyle( fontWeight: FontWeight.bold, ), ), floatingActionButton: FloatingActionButton( backgroundColor: _isConnected ? Colors.green : Colors.red, onPressed: _toggleConnection, child: Icon( _isConnected ? Icons.wifi : Icons.wifi_off, color: Colors.white, ), ), ); } } class SensorData { double? tdsValue; double? waterflow1Value; double? waterflow1speed; double? waterflow2Value; double? waterflow2speed; double? ultrasonicValue; }