TKK_E32210053/lib/admin_page.dart

652 lines
26 KiB
Dart

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<AdminPage> {
BluetoothState _bluetoothState = BluetoothState.UNKNOWN;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final FlutterBluetoothSerial _bluetooth = FlutterBluetoothSerial.instance;
BluetoothConnection? connection;
late int _deviceState;
bool isDisconnecting = false;
bool? get isConnected => connection != null && connection!.isConnected;
List<BluetoothDevice> _devicesList = [];
List<Map<String, String>> _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<void> 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<void> getPairedDevices() async {
List<BluetoothDevice> devices = [];
try {
devices = await _bluetooth.getBondedDevices();
} on PlatformException {
debugPrint("Error");
}
if (!mounted) {
return;
}
setState(() {
_devicesList = devices;
});
}
//Memuat perangkat yang telah ditambahkan dari SharedPreferences
Future<void> loadAddedDevices() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? devicesJson = prefs.getString('added_devices');
if (devicesJson != null) {
List<Map<String, dynamic>> devices =
List<Map<String, dynamic>>.from(jsonDecode(devicesJson));
setState(() {
_addedDevices =
devices.map((device) => Map<String, String>.from(device)).toList();
_passwordPrefixCounter = _addedDevices.length + 1;
});
}
}
//Menyimpan perangkat yang telah ditambahkan ke SharedPreferences.
Future<void> 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<int> 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: <Widget>[
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: <Widget>[
Visibility(
visible: _isButtonUnavailable &&
_bluetoothState == BluetoothState.STATE_ON,
child: const LinearProgressIndicator(
backgroundColor: Colors.yellow,
valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
),
),
Padding(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
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: <Widget>[
Column(
children: <Widget>[
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: <Widget>[
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: <TextInputFormatter>[
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 <int, TableColumnWidth>{
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<DropdownMenuItem<BluetoothDevice>> _getDeviceItems() {
List<DropdownMenuItem<BluetoothDevice>> 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<void> _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<void> _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<void> 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<void> 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<String, String> 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'),
),
],
),
);
}
}