413 lines
12 KiB
Dart
413 lines
12 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
|
|
|
|
class UserPage extends StatefulWidget {
|
|
@override
|
|
_UserPageState createState() => _UserPageState();
|
|
}
|
|
|
|
//Inisialisasi dan State Management
|
|
class _UserPageState extends State<UserPage> {
|
|
BluetoothState _bluetoothState = BluetoothState.UNKNOWN;
|
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
|
final FlutterBluetoothSerial _bluetooth = FlutterBluetoothSerial.instance;
|
|
BluetoothConnection? connection;
|
|
late int _deviceState;
|
|
bool isDisconnecting = false;
|
|
|
|
Map<String, Color> colors = {
|
|
'onBorderColor': Colors.green,
|
|
'offBorderColor': Colors.red,
|
|
'neutralBorderColor': Colors.transparent,
|
|
'onTextColor': Colors.green,
|
|
'offTextColor': Colors.red,
|
|
'neutralTextColor': Colors.blue,
|
|
};
|
|
|
|
bool? get isConnected => connection != null && connection!.isConnected;
|
|
|
|
List<BluetoothDevice> _devicesList = [];
|
|
BluetoothDevice? _device;
|
|
bool _connected = false;
|
|
bool _isButtonUnavailable = false;
|
|
bool _isLoading = false;
|
|
//Dipanggil ketika widget diinisialisasi. Digunakan untuk mengatur state awal dan mengaktifkan Bluetooth.
|
|
@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();
|
|
});
|
|
});
|
|
}
|
|
|
|
//Dipanggil ketika widget dihancurkan. Digunakan untuk membersihkan resource yang digunakan.
|
|
@override
|
|
void dispose() {
|
|
if (isConnected!) {
|
|
isDisconnecting = true;
|
|
connection?.dispose();
|
|
connection = null;
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
//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
|
|
Future<void> getPairedDevices() async {
|
|
List<BluetoothDevice> devices = [];
|
|
try {
|
|
devices = await _bluetooth.getBondedDevices();
|
|
} on PlatformException {
|
|
debugPrint("Error");
|
|
}
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_devicesList = devices;
|
|
});
|
|
}
|
|
|
|
//ui
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return 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((_) {
|
|
show('Daftar perangkat diperbarui');
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
body: Column(
|
|
mainAxisSize: MainAxisSize.max,
|
|
children: <Widget>[
|
|
Visibility(
|
|
visible: _isLoading,
|
|
child: LinearProgressIndicator(
|
|
backgroundColor: Colors.yellow,
|
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
|
|
),
|
|
),
|
|
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(
|
|
'Nyalakan 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: Center(
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
const Text(
|
|
'Perangkat:',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
SizedBox(width: 10),
|
|
DropdownButton(
|
|
items: _getDeviceItems(),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_device = value;
|
|
_isButtonUnavailable = false;
|
|
});
|
|
},
|
|
value: _devicesList.isNotEmpty ? _device : null,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Container(
|
|
color: Colors.blue,
|
|
),
|
|
],
|
|
),
|
|
Expanded(
|
|
child: Center(
|
|
child: ElevatedButton(
|
|
onPressed: _device != null ? _handleUnlock : null,
|
|
child: const Icon(Icons.lock_open, size: 100),
|
|
style: ElevatedButton.styleFrom(
|
|
shape: CircleBorder(),
|
|
padding: EdgeInsets.all(40),
|
|
backgroundColor: Color.fromARGB(255, 251, 252, 255),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
const Text(
|
|
"CATATAN: Jika Anda tidak dapat menemukan perangkat dalam daftar, silakan pasangkan perangkat melalui pengaturan Bluetooth",
|
|
style: TextStyle(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.red,
|
|
),
|
|
),
|
|
const SizedBox(height: 15),
|
|
ElevatedButton(
|
|
child: const Text("Pengaturan Bluetooth"),
|
|
onPressed: () {
|
|
FlutterBluetoothSerial.instance.openSettings();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
//Membuat daftar item dropdown dari perangkat Bluetooth yang tersedia.
|
|
List<DropdownMenuItem<BluetoothDevice>> _getDeviceItems() {
|
|
List<DropdownMenuItem<BluetoothDevice>> items = [];
|
|
if (_devicesList.isEmpty) {
|
|
items.add(const DropdownMenuItem(
|
|
child: Text('TIDAK ADA'),
|
|
));
|
|
} else {
|
|
for (var device in _devicesList) {
|
|
items.add(DropdownMenuItem(
|
|
value: device,
|
|
child: Text(device.name!),
|
|
));
|
|
}
|
|
}
|
|
return items;
|
|
}
|
|
|
|
//Menghubungkan ke perangkat Bluetooth yang dipilih.
|
|
Future<void> _connect() async {
|
|
if (_device == null) {
|
|
show('Tidak ada perangkat yang dipilih');
|
|
setState(() {
|
|
_isButtonUnavailable = false;
|
|
_isLoading = false;
|
|
});
|
|
} else {
|
|
if (!isConnected!) {
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
|
|
try {
|
|
connection = await BluetoothConnection.toAddress(_device!.address)
|
|
.timeout(Duration(seconds: 5));
|
|
|
|
debugPrint('Terhubung ke perangkat');
|
|
setState(() {
|
|
_connected = true;
|
|
_isButtonUnavailable = false;
|
|
_isLoading = false;
|
|
});
|
|
|
|
connection?.input?.listen((Uint8List data) {
|
|
final message = utf8.decode(data);
|
|
debugPrint('Data masuk: $message');
|
|
setState(() {
|
|
_deviceState = data.isNotEmpty ? data.last : 0;
|
|
});
|
|
}).onDone(() {
|
|
debugPrint('Terputus oleh permintaan jarak jauh');
|
|
if (isDisconnecting) {
|
|
debugPrint('Memutuskan secara lokal!');
|
|
} else {
|
|
show('Terputus dari jarak jauh');
|
|
}
|
|
if (mounted) {
|
|
setState(() {});
|
|
}
|
|
});
|
|
} catch (error) {
|
|
debugPrint('Tidak dapat terhubung, terjadi pengecualian');
|
|
debugPrint(error.toString());
|
|
setState(() {
|
|
_isButtonUnavailable = false;
|
|
_isLoading = false;
|
|
});
|
|
show('Tidak dapat terhubung dalam waktu 5 detik');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Memutuskan koneksi Bluetooth.
|
|
void _disconnect() async {
|
|
setState(() {
|
|
_isButtonUnavailable = true;
|
|
_deviceState = 0;
|
|
});
|
|
|
|
await connection?.close();
|
|
show('Perangkat terputus');
|
|
if (!connection!.isConnected) {
|
|
setState(() {
|
|
_connected = false;
|
|
_isButtonUnavailable = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
//Mengirim pesan untuk membuka pintu ke perangkat Bluetooth.
|
|
void _sendUnlockMessage() async {
|
|
final jsonData = jsonEncode({
|
|
'action': 'open_door',
|
|
'name': 'User',
|
|
'address': _device?.address ?? 'unknown_address'
|
|
});
|
|
connection?.output.add(Uint8List.fromList(utf8.encode(jsonData)));
|
|
await connection?.output.allSent;
|
|
show('Sinyal buka kunci dikirim');
|
|
setState(() {
|
|
_deviceState = 1; // State unlocked
|
|
});
|
|
|
|
Future.delayed(Duration(seconds: 5), _disconnect);
|
|
}
|
|
|
|
//Mengelola proses pembukaan pintu
|
|
void _handleUnlock() async {
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
|
|
if (!_connected) {
|
|
await _connect();
|
|
}
|
|
|
|
if (_connected) {
|
|
_sendUnlockMessage();
|
|
}
|
|
}
|
|
|
|
//Menampilkan pesan menggunakan SnackBar.
|
|
Future show(String message,
|
|
{Duration duration = const Duration(seconds: 3)}) async {
|
|
await Future.delayed(const Duration(milliseconds: 100));
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(message),
|
|
duration: duration,
|
|
),
|
|
);
|
|
}
|
|
}
|