import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; import 'package:shared_preferences/shared_preferences.dart'; class AdminPage extends StatefulWidget { @override _AdminPageState createState() => _AdminPageState(); } //Inisialisasi dan State Management class _AdminPageState extends State { BluetoothState _bluetoothState = BluetoothState.UNKNOWN; final GlobalKey _scaffoldKey = GlobalKey(); final FlutterBluetoothSerial _bluetooth = FlutterBluetoothSerial.instance; BluetoothConnection? connection; late int _deviceState; bool isDisconnecting = false; bool? get isConnected => connection != null && connection!.isConnected; List _devicesList = []; List> _addedDevices = []; BluetoothDevice? _device; bool _connected = false; bool _isButtonUnavailable = false; TextEditingController bluetoothNameController = TextEditingController(); TextEditingController bluetoothAddressController = TextEditingController(); TextEditingController passwordController = TextEditingController(); int _passwordPrefixCounter = 1; String? _displayedIP; //Inisialisasi dan Lifecycle Methods @override void initState() { super.initState(); FlutterBluetoothSerial.instance.state.then((state) { setState(() { _bluetoothState = state; }); }); _deviceState = 0; enableBluetooth(); FlutterBluetoothSerial.instance .onStateChanged() .listen((BluetoothState state) { setState(() { _bluetoothState = state; if (_bluetoothState == BluetoothState.STATE_OFF) { _isButtonUnavailable = true; } getPairedDevices(); }); }); loadAddedDevices(); } @override void dispose() { if (isConnected!) { isDisconnecting = true; connection?.dispose(); } super.dispose(); } // Fungsi untuk Mengaktifkan Bluetooth jika tidak aktif. Future enableBluetooth() async { _bluetoothState = await FlutterBluetoothSerial.instance.state; if (_bluetoothState == BluetoothState.STATE_OFF) { await FlutterBluetoothSerial.instance.requestEnable(); await getPairedDevices(); } else { await getPairedDevices(); } } //Mendapatkan daftar perangkat yang sudah terhubung (paired). Future getPairedDevices() async { List devices = []; try { devices = await _bluetooth.getBondedDevices(); } on PlatformException { debugPrint("Error"); } if (!mounted) { return; } setState(() { _devicesList = devices; }); } //Memuat perangkat yang telah ditambahkan dari SharedPreferences Future loadAddedDevices() async { SharedPreferences prefs = await SharedPreferences.getInstance(); String? devicesJson = prefs.getString('added_devices'); if (devicesJson != null) { List> devices = List>.from(jsonDecode(devicesJson)); setState(() { _addedDevices = devices.map((device) => Map.from(device)).toList(); _passwordPrefixCounter = _addedDevices.length + 1; }); } } //Menyimpan perangkat yang telah ditambahkan ke SharedPreferences. Future saveAddedDevices() async { SharedPreferences prefs = await SharedPreferences.getInstance(); String devicesJson = jsonEncode(_addedDevices); await prefs.setString('added_devices', devicesJson); } //Fungsi untuk Menampilkan Pesan void showMessage(String message, {Duration duration = const Duration(seconds: 3)}) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), duration: duration, ), ); } //Fungsi untuk Mendapatkan Prefix Berikutnya int _getNextAvailablePrefix() { List usedPrefixes = _addedDevices .map((device) => int.parse(device['prefix'] ?? '0')) .toList(); usedPrefixes.sort(); for (int i = 1; i <= usedPrefixes.length; i++) { if (!usedPrefixes.contains(i)) { return i; } } return usedPrefixes.length + 1; } //Ui @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { Navigator.of(context).popUntil((route) => route.isFirst); return false; }, child: Scaffold( key: _scaffoldKey, appBar: AppBar( title: const Text("Smart Door"), backgroundColor: Color.fromARGB(255, 243, 146, 34), actions: [ ElevatedButton.icon( icon: const Icon( Icons.refresh, color: Color.fromARGB(255, 0, 0, 0), ), label: const Text( "", style: TextStyle( color: Colors.white, ), ), onPressed: () async { await getPairedDevices().then((_) { showMessage('Daftar perangkat diperbarui'); }); }, ), ], ), body: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.max, children: [ Visibility( visible: _isButtonUnavailable && _bluetoothState == BluetoothState.STATE_ON, child: const LinearProgressIndicator( backgroundColor: Colors.yellow, valueColor: AlwaysStoppedAnimation(Colors.red), ), ), Padding( padding: const EdgeInsets.all(10), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ const Expanded( child: Text( 'Aktifkan Bluetooth', style: TextStyle( color: Colors.black, fontSize: 16, ), ), ), Switch( value: _bluetoothState.isEnabled, onChanged: (bool value) { future() async { if (value) { await FlutterBluetoothSerial.instance .requestEnable(); } else { await FlutterBluetoothSerial.instance .requestDisable(); } await getPairedDevices(); _isButtonUnavailable = false; if (_connected) { _disconnect(); } } future().then((_) { setState(() {}); }); }, ) ], ), ), Stack( children: [ Column( children: [ const Padding( padding: EdgeInsets.only(top: 10), child: Text( "PERANGKAT TERPASANG", style: TextStyle( fontSize: 22, color: Color.fromARGB(255, 0, 0, 0), fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Perangkat:', style: TextStyle( fontWeight: FontWeight.bold, ), ), SizedBox(width: 10), DropdownButton( items: _getDeviceItems(), onChanged: (value) async { if (isConnected!) { await _disconnect(); // Putuskan koneksi jika ada } setState(() => _device = value); await _connectToSelectedDevice(); }, value: _devicesList.isNotEmpty ? _device : null, ), ], ), ), Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( 'Perangkat Ditambahkan', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), Spacer(), IconButton( icon: Icon(Icons.add, color: Colors.blue), onPressed: () async { if (!isConnected!) { showMessage( 'Harap hubungkan Bluetooth terlebih dahulu'); return; } showDialog( context: context, builder: (context) => AlertDialog( title: Text('Tambah Perangkat'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: bluetoothNameController, decoration: const InputDecoration( labelText: 'Nama Perangkat Bluetooth', ), ), TextField( controller: bluetoothAddressController, decoration: const InputDecoration( labelText: 'Alamat Perangkat Bluetooth', ), ), Row( children: [ Container( width: 50, child: TextField( readOnly: true, controller: TextEditingController( text: _getNextAvailablePrefix() .toString() .padLeft(2, '0'), ), decoration: InputDecoration( labelText: 'Prefix', ), ), ), SizedBox(width: 10), Expanded( child: TextField( controller: passwordController, decoration: const InputDecoration( labelText: 'Password', ), obscureText: true, keyboardType: TextInputType .number, // Hanya angka yang diperbolehkan inputFormatters: [ FilteringTextInputFormatter .digitsOnly // Filter hanya angka ]), ), ], ), ], ), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('Batal'), ), TextButton( onPressed: () async { String name = bluetoothNameController.text; String address = bluetoothAddressController .text; String password = passwordController.text; bool addressExists = _addedDevices.any((device) => device['address'] == address); if (addressExists) { showMessage( 'Alamat perangkat sudah ada dalam daftar'); return; } if (name.isNotEmpty && address.isNotEmpty && password.isNotEmpty && password.length >= 6) { Navigator.of(context).pop(); int nextPrefix = _getNextAvailablePrefix(); await addDeviceToESP32( name, address, nextPrefix .toString() .padLeft(2, '0') + password); setState(() { _addedDevices.add({ 'name': name, 'address': address, 'prefix': nextPrefix.toString() }); saveAddedDevices(); bluetoothNameController .clear(); bluetoothAddressController .clear(); passwordController.clear(); }); await _disconnect(); } else { showMessage( 'Nama, alamat, dan password tidak boleh kosong atau password kurang dari 6 karakter'); } }, child: Text('Tambah'), ), ], ), ); }, ), ], ), Table( border: TableBorder.all(color: Colors.black), columnWidths: const { 0: FlexColumnWidth(2), 1: FlexColumnWidth(2), 2: FlexColumnWidth(1), }, defaultVerticalAlignment: TableCellVerticalAlignment.middle, children: [ TableRow( children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text('Nama', style: TextStyle( fontWeight: FontWeight.bold)), ), Padding( padding: const EdgeInsets.all(8.0), child: Text('Alamat', style: TextStyle( fontWeight: FontWeight.bold)), ), Padding( padding: const EdgeInsets.all(8.0), child: Text('Hapus', style: TextStyle( fontWeight: FontWeight.bold)), ), ], ), ..._addedDevices.map((device) { return TableRow( children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text(device['name'] ?? ''), ), Padding( padding: const EdgeInsets.all(8.0), child: Text(device['address'] ?? ''), ), IconButton( icon: Icon(Icons.delete, color: Colors.red), onPressed: () { showDeleteConfirmationDialog(device); }, ), ], ); }).toList(), ], ), if (_displayedIP != null) Padding( padding: const EdgeInsets.all(8.0), child: Text( 'IP Address: $_displayedIP', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.blue, ), ), ), ], ), ), ], ), Container( color: Colors.blue, ), ], ), ], ), ), ), ); } List> _getDeviceItems() { List> items = []; if (_devicesList.isEmpty) { items.add(const DropdownMenuItem( child: Text('NONE'), )); } else { _devicesList.forEach((device) { items.add(DropdownMenuItem( child: Text(device.name ?? ''), value: device, )); }); } return items; } //Menghubungkan ke perangkat Bluetooth yang dipilih. Future _connectToSelectedDevice() async { setState(() { _isButtonUnavailable = true; }); if (_device == null) { showMessage('Tidak ada perangkat yang dipilih'); } else { if (isConnected!) { await _disconnect(); // Putuskan koneksi jika ada } await BluetoothConnection.toAddress(_device!.address).then((_connection) { showMessage('Terhubung ke perangkat'); connection = _connection; setState(() { _connected = true; }); connection!.input!.listen((Uint8List data) { String response = utf8.decode(data); print("Data diterima: $response"); if (response.contains('{"ip":"')) { setState(() { _displayedIP = jsonDecode(response)['ip']; print("IP Address: $_displayedIP"); }); } }).onDone(() { if (isDisconnecting) { showMessage('Terputus secara lokal'); } else { showMessage('Terputus secara remote'); } if (mounted) { setState(() {}); } }); // Kirim perintah setelah berhasil terhubung String message = jsonEncode({ "action": "get_ip", }); connection!.output.add(utf8.encode(message + "\r\n")); connection!.output.allSent; }).catchError((error) { showMessage('Tidak bisa terhubung, terjadi kesalahan'); debugPrint(error.toString()); }); setState(() => _isButtonUnavailable = false); } } //Memutuskan koneksi Bluetooth. Future _disconnect() async { await connection?.close(); showMessage('Perangkat terputus'); if (!connection!.isConnected) { setState(() { _connected = false; _isButtonUnavailable = false; }); } } //Menambahkan perangkat ke ESP32 dengan mengirimkan data melalui Bluetooth. Future addDeviceToESP32( String name, String address, String password) async { if (isConnected!) { String message = jsonEncode({ "action": "add", "name": name, "address": address, "password": password }); connection!.output.add(utf8.encode(message + "\r\n")); await connection!.output.allSent; } } //Menghapus perangkat dari ESP32 dengan mengirimkan data melalui Bluetooth. Future deleteDeviceFromESP32(String address) async { if (isConnected!) { String message = jsonEncode({ "action": "delete", "address": address, }); connection!.output.add(utf8.encode(message + "\r\n")); await connection!.output.allSent; } } void showDeleteConfirmationDialog(Map device) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('Konfirmasi Hapus'), content: Text('Apakah Anda yakin ingin menghapus perangkat ini?'), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('Tidak'), ), TextButton( onPressed: () async { if (!isConnected!) { showMessage('Harap hubungkan Bluetooth terlebih dahulu'); return; } Navigator.of(context).pop(); await deleteDeviceFromESP32(device['address']!); setState(() { _addedDevices.remove(device); saveAddedDevices(); }); await _disconnect(); }, child: Text('Iya'), ), ], ), ); } }