some cleanup and adding functions
This commit is contained in:
parent
65a1ce14d1
commit
1d16f01cc3
|
@ -42,3 +42,65 @@ app.*.map.json
|
||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
## custom
|
||||||
|
|
||||||
|
**/ios/**
|
||||||
|
**/macos/**
|
||||||
|
**/windows/**
|
||||||
|
**/linux/**
|
||||||
|
|
||||||
|
## https://github.com/flutter/flutter/blob/master/.gitignore
|
||||||
|
|
||||||
|
# iOS/XCode related
|
||||||
|
**/ios/**/*.mode1v3
|
||||||
|
**/ios/**/*.mode2v3
|
||||||
|
**/ios/**/*.moved-aside
|
||||||
|
**/ios/**/*.pbxuser
|
||||||
|
**/ios/**/*.perspectivev3
|
||||||
|
**/ios/**/*sync/
|
||||||
|
**/ios/**/.sconsign.dblite
|
||||||
|
**/ios/**/.tags*
|
||||||
|
**/ios/**/.vagrant/
|
||||||
|
**/ios/**/DerivedData/
|
||||||
|
**/ios/**/Icon?
|
||||||
|
**/ios/**/Pods/
|
||||||
|
**/ios/**/.symlinks/
|
||||||
|
**/ios/**/profile
|
||||||
|
**/ios/**/xcuserdata
|
||||||
|
**/ios/.generated/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
**/ios/Flutter/App.framework
|
||||||
|
**/ios/Flutter/Flutter.framework
|
||||||
|
**/ios/Flutter/Flutter.podspec
|
||||||
|
**/ios/Flutter/Generated.xcconfig
|
||||||
|
**/ios/Flutter/ephemeral
|
||||||
|
**/ios/Flutter/app.flx
|
||||||
|
**/ios/Flutter/app.zip
|
||||||
|
**/ios/Flutter/flutter_assets/
|
||||||
|
**/ios/Flutter/flutter_export_environment.sh
|
||||||
|
**/ios/ServiceDefinitions.json
|
||||||
|
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
**/Flutter/ephemeral/
|
||||||
|
**/Pods/
|
||||||
|
**/macos/Flutter/GeneratedPluginRegistrant.swift
|
||||||
|
**/macos/Flutter/ephemeral
|
||||||
|
**/xcuserdata/
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
**/windows/flutter/generated_plugin_registrant.cc
|
||||||
|
**/windows/flutter/generated_plugin_registrant.h
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
**/linux/flutter/generated_plugin_registrant.cc
|
||||||
|
**/linux/flutter/generated_plugin_registrant.h
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!**/ios/**/default.mode1v3
|
||||||
|
!**/ios/**/default.mode2v3
|
||||||
|
!**/ios/**/default.pbxuser
|
||||||
|
!**/ios/**/default.perspectivev3
|
||||||
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
!/dev/ci/**/Gemfile.lock
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
|
<!--<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>-->
|
||||||
<!-- Needed to communicate with already-paired Bluetooth devices. (Android 12 upwards)-->
|
<!-- Needed to communicate with already-paired Bluetooth devices. (Android 12 upwards)-->
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
<application
|
<application
|
||||||
|
|
|
@ -11,6 +11,32 @@ Future<Map<String, dynamic>> readJson() async {
|
||||||
return data_;
|
return data_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*Future<void> checkNameAvailability(String input) async {
|
||||||
|
// await cloudServiceAPI.loadConfig();
|
||||||
|
List<dynamic> devices = await cloudServiceAPI.getDevices();
|
||||||
|
for (Map<String, dynamic> selected in devices) {
|
||||||
|
if (selected["id"] == input) {
|
||||||
|
await showNameAvailabilityStatus(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await showNameAvailabilityStatus(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showNameAvailabilityStatus(bool status) async {
|
||||||
|
String statusText = status
|
||||||
|
? "die eingegebene ID ist verfügbar"
|
||||||
|
: "die eingegebene ID ist nicht verfügbar";
|
||||||
|
Fluttertoast.showToast(
|
||||||
|
msg: statusText,
|
||||||
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
timeInSecForIosWeb: 2,
|
||||||
|
backgroundColor: Colors.grey[200],
|
||||||
|
textColor: Colors.black,
|
||||||
|
fontSize: 16.0);
|
||||||
|
}*/
|
||||||
|
|
||||||
class CloudServiceAPI {
|
class CloudServiceAPI {
|
||||||
static late final Map<String, dynamic> credentials;
|
static late final Map<String, dynamic> credentials;
|
||||||
static late Future loadJson;
|
static late Future loadJson;
|
||||||
|
@ -50,22 +76,22 @@ class CloudServiceAPI {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Future<List> getDevices() async {
|
Future<List> getDevices() async {
|
||||||
var url = Uri.https(address, '/api/devices');
|
Uri url = Uri.https(address, '/api/devices');
|
||||||
Response r = await get(url, headers: headers);
|
Response r = await get(url, headers: headers);
|
||||||
return json.decode(r.body) as List<dynamic>;
|
return json.decode(r.body) as List<dynamic>;
|
||||||
}
|
}
|
||||||
Future<Map<String, dynamic>> getDeviceInfo(deviceID) async {
|
Future<Map<String, dynamic>> getDeviceInfo(deviceID) async {
|
||||||
var url = Uri.https(address, '/api/devices/$deviceID');
|
Uri url = Uri.https(address, '/api/devices/$deviceID');
|
||||||
Response r = await get(url, headers: headers);
|
Response r = await get(url, headers: headers);
|
||||||
return json.decode(r.body) as Map<String, dynamic>;
|
return json.decode(r.body) as Map<String, dynamic>;
|
||||||
}
|
}
|
||||||
Future<Map<String, dynamic>> getInformation() async {
|
Future<Map<String, dynamic>> getInformation() async {
|
||||||
var url = Uri.https(address, '/api/app');
|
Uri url = Uri.https(address, '/api/app');
|
||||||
Response r = await get(url, headers: headers);
|
Response r = await get(url, headers: headers);
|
||||||
return json.decode(r.body) as Map<String, dynamic>;
|
return json.decode(r.body) as Map<String, dynamic>;
|
||||||
}
|
}
|
||||||
Future<bool> createDevice(id, primaryThumbprint, secondaryThumbprint) async{
|
Future<bool> createDevice(id, primaryThumbprint, secondaryThumbprint) async{
|
||||||
var url = Uri.https(address, '/api/devices');
|
Uri url = Uri.https(address, '/api/devices');
|
||||||
Response r = await post(
|
Response r = await post(
|
||||||
url,
|
url,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
|
||||||
|
|
||||||
|
class BluetoothDeviceListEntry extends ListTile {
|
||||||
|
BluetoothDeviceListEntry({super.key,
|
||||||
|
required BluetoothDevice device,
|
||||||
|
int? rssi,
|
||||||
|
GestureTapCallback? onTap,
|
||||||
|
GestureLongPressCallback? onLongPress,
|
||||||
|
bool enabled = true,
|
||||||
|
}) : super(
|
||||||
|
onTap: onTap,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
enabled: enabled,
|
||||||
|
leading: Icon(device.isConnected ? Icons.bluetooth_connected : Icons.bluetooth),
|
||||||
|
title: Text(device.name ?? "~ UNKNOWN_DEVICE ~"),
|
||||||
|
subtitle: Text(device.address.toString()),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
rssi != null
|
||||||
|
? Container(
|
||||||
|
margin: const EdgeInsets.all(8.0),
|
||||||
|
child: DefaultTextStyle(
|
||||||
|
style: _computeTextStyle(rssi),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(rssi.toString()),
|
||||||
|
const Text('dBm'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox(width: 0, height: 0),
|
||||||
|
device.isBonded
|
||||||
|
? const Icon(Icons.link)
|
||||||
|
: const SizedBox(width: 0, height: 0),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.settings)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
static TextStyle _computeTextStyle(int rssi) {
|
||||||
|
/**/ if (rssi >= -35) {
|
||||||
|
return TextStyle(color: Colors.greenAccent[700]);
|
||||||
|
} else if (rssi >= -45) {
|
||||||
|
return TextStyle(
|
||||||
|
color: Color.lerp(
|
||||||
|
Colors.greenAccent[700], Colors.lightGreen, -(rssi + 35) / 10));
|
||||||
|
} else if (rssi >= -55) {
|
||||||
|
return TextStyle(
|
||||||
|
color: Color.lerp(
|
||||||
|
Colors.lightGreen, Colors.lime[600], -(rssi + 45) / 10));
|
||||||
|
} else if (rssi >= -65) {
|
||||||
|
return TextStyle(
|
||||||
|
color: Color.lerp(Colors.lime[600], Colors.amber, -(rssi + 55) / 10));
|
||||||
|
} else if (rssi >= -75) {
|
||||||
|
return TextStyle(
|
||||||
|
color: Color.lerp(
|
||||||
|
Colors.amber, Colors.deepOrangeAccent, -(rssi + 65) / 10));
|
||||||
|
} else if (rssi >= -85) {
|
||||||
|
return TextStyle(
|
||||||
|
color: Color.lerp(
|
||||||
|
Colors.deepOrangeAccent, Colors.redAccent, -(rssi + 75) / 10));
|
||||||
|
} else {
|
||||||
|
return const TextStyle(color: Colors.redAccent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +1,369 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:typed_data';
|
||||||
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
|
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_provisioning_for_iot/widgets/bluetooth_discovery.dart';
|
|
||||||
import 'package:flutter_provisioning_for_iot/objects/cloud_service_api.dart';
|
|
||||||
import 'package:flutter_provisioning_for_iot/widgets/switch_widget.dart';
|
|
||||||
|
|
||||||
|
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_provisioning_for_iot/objects/cloud_service_api.dart';
|
||||||
|
import 'package:flutter_provisioning_for_iot/screens/bluetooth_device_list_entry.dart';
|
||||||
|
|
||||||
|
import 'package:app_settings/app_settings.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
|
|
||||||
class BluetoothScreen extends StatefulWidget {
|
class BluetoothScreen extends StatefulWidget {
|
||||||
|
final bool start = true;
|
||||||
|
|
||||||
const BluetoothScreen({super.key});
|
const BluetoothScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<BluetoothScreen> createState() => _BluetoothScreen();
|
State<BluetoothScreen> createState() => _BluetoothScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// debugPrint("r: ${r.device.name} ${r.device.address} ${r.rssi}");
|
||||||
class _BluetoothScreen extends State<BluetoothScreen> {
|
class _BluetoothScreen extends State<BluetoothScreen> {
|
||||||
|
|
||||||
final textFieldValueHolder = TextEditingController();
|
|
||||||
CloudServiceAPI cloudServiceAPI = CloudServiceAPI();
|
CloudServiceAPI cloudServiceAPI = CloudServiceAPI();
|
||||||
List<BluetoothDiscoveryResult> results =
|
//BluetoothManager bluetoothManager = BluetoothManager();
|
||||||
|
|
||||||
|
late Stream<BluetoothDiscoveryResult> _stream;
|
||||||
|
late StreamSubscription<BluetoothDiscoveryResult> _streamSubscription;
|
||||||
|
late Stream<Uint8List> _connectionStream;
|
||||||
|
late StreamSubscription<Uint8List> _connectionStreamSubscription;
|
||||||
|
BluetoothDevice? _bluetoothDevice;
|
||||||
|
BluetoothConnection? _bluetoothConnection;
|
||||||
|
final List<BluetoothDiscoveryResult> _discoveryResults =
|
||||||
List<BluetoothDiscoveryResult>.empty(growable: true);
|
List<BluetoothDiscoveryResult>.empty(growable: true);
|
||||||
|
String _messageBuffer = "";
|
||||||
|
bool isDiscovering = false;
|
||||||
|
bool isConnecting = false;
|
||||||
|
bool isDisconnecting = false;
|
||||||
|
bool isConnected = false;
|
||||||
|
|
||||||
ButtonStyle buttonStyle = ElevatedButton.styleFrom(
|
ButtonStyle buttonStyle = ElevatedButton.styleFrom(
|
||||||
foregroundColor: Colors.black,
|
foregroundColor: Colors.black,
|
||||||
backgroundColor: const Color(0xFFFDE100), // Text Color (Foreground color)
|
backgroundColor: const Color(0xFFFDE100), // Text Color (Foreground color)
|
||||||
);
|
);
|
||||||
String inputName = "";
|
|
||||||
bool initScan = true;
|
|
||||||
bool scanState = false;
|
|
||||||
bool widgetScanState = false;
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
isDiscovering = widget.start;
|
||||||
|
if (isDiscovering) {
|
||||||
|
_startDiscovery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// Avoid memory leak (`setState` after dispose) and cancel discovery
|
||||||
|
_streamSubscription.cancel();
|
||||||
|
if (isConnected) {
|
||||||
|
isDisconnecting = true;
|
||||||
|
_bluetoothConnection?.dispose();
|
||||||
|
_bluetoothConnection = null;
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _disconnectDevice(BluetoothDevice device) async {
|
||||||
|
if (_bluetoothConnection != null) {
|
||||||
|
await _bluetoothConnection!.finish();
|
||||||
|
_bluetoothConnection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _connectDevice(BluetoothDevice device) async {
|
||||||
|
String address = device.address;
|
||||||
|
if (_bluetoothConnection != null) {
|
||||||
|
await _bluetoothConnection!.finish();
|
||||||
|
_bluetoothConnection = null;
|
||||||
|
}
|
||||||
|
_bluetoothConnection = await BluetoothConnection.toAddress(address);
|
||||||
|
debugPrint("Connected to the device");
|
||||||
|
setState(() {
|
||||||
|
isConnecting = false;
|
||||||
|
isDisconnecting = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
_connectionStream = _bluetoothConnection!.input!;
|
||||||
|
_connectionStreamSubscription = _connectionStream.listen(_connectionOnListen);
|
||||||
|
_connectionStreamSubscription.onDone(_connectionOnDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _connectionOnDone() {
|
||||||
|
/*
|
||||||
|
// Example: Detect which side closed the connection
|
||||||
|
// There should be `isDisconnecting` flag to show are we are (locally)
|
||||||
|
// in middle of disconnecting process, should be set before calling
|
||||||
|
// `dispose`, `finish` or `close`, which all causes to disconnect.
|
||||||
|
// If we except the disconnection, `onDone` should be fired as result.
|
||||||
|
// If we didn't except this (no flag set), it means closing by remote.
|
||||||
|
*/
|
||||||
|
if (isDisconnecting) {
|
||||||
|
debugPrint("Disconnecting locally!");
|
||||||
|
} else {
|
||||||
|
debugPrint("Disconnected remotely!");
|
||||||
|
}
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _connectionOnListen(Uint8List data) {
|
||||||
|
debugPrint("received: $data");
|
||||||
|
debugPrint("received decoded: ${const Utf8Decoder().convert(data)}");
|
||||||
|
// Allocate buffer for parsed data
|
||||||
|
int backspacesCounter = 0;
|
||||||
|
for (var byte in data) {
|
||||||
|
if (byte == 8 || byte == 127) {
|
||||||
|
backspacesCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Uint8List buffer = Uint8List(data.length - backspacesCounter);
|
||||||
|
int bufferIndex = buffer.length;
|
||||||
|
|
||||||
|
// Apply backspace control character
|
||||||
|
backspacesCounter = 0;
|
||||||
|
for (int i = data.length - 1; i >= 0; i--) {
|
||||||
|
if (data[i] == 8 || data[i] == 127) {
|
||||||
|
backspacesCounter++;
|
||||||
|
} else {
|
||||||
|
if (backspacesCounter > 0) {
|
||||||
|
backspacesCounter--;
|
||||||
|
} else {
|
||||||
|
buffer[--bufferIndex] = data[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create message if there is new line character
|
||||||
|
String dataString = String.fromCharCodes(buffer);
|
||||||
|
int index = buffer.indexOf(13);
|
||||||
|
if (~index != 0) {
|
||||||
|
setState(() {
|
||||||
|
_messageBuffer = dataString.substring(index);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_messageBuffer = (backspacesCounter > 0
|
||||||
|
? _messageBuffer.substring(
|
||||||
|
0, _messageBuffer.length - backspacesCounter)
|
||||||
|
: _messageBuffer + dataString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startDiscovery() async {
|
||||||
|
_stream = FlutterBluetoothSerial.instance.startDiscovery();
|
||||||
|
_streamSubscription = _stream.listen(_discoveryOnListen);
|
||||||
|
_streamSubscription.onDone(_discoveryOnDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _discoveryOnDone() {
|
||||||
|
setState(() {
|
||||||
|
isDiscovering = false;
|
||||||
|
//debugPrint(isDiscovering as String?);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _discoveryOnListen(BluetoothDiscoveryResult event) {
|
||||||
|
setState(() {
|
||||||
|
final int existingIndex = _discoveryResults.indexWhere(
|
||||||
|
(element) => element.device.address == event.device.address);
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
_discoveryResults[existingIndex] = event;
|
||||||
|
} else {
|
||||||
|
_discoveryResults.add(event);
|
||||||
|
}
|
||||||
|
debugPrint("event: ${event.device.address} ${event.device.name}");
|
||||||
|
|
||||||
|
String deviceAddress = "64:BC:58:61:56:B0";
|
||||||
|
if (event.device.address == deviceAddress) {
|
||||||
|
_bluetoothDevice = event.device;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _restartDiscovery() async {
|
||||||
|
setState(() {
|
||||||
|
_discoveryResults.clear();
|
||||||
|
isDiscovering = true;
|
||||||
|
});
|
||||||
|
_startDiscovery();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _sendData() async {
|
||||||
|
String output = "0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ !§%&()=?#!?";
|
||||||
|
_bluetoothConnection!.output
|
||||||
|
.add(Uint8List.fromList(const Utf8Encoder().convert("$output \r\n")));
|
||||||
|
await _bluetoothConnection!.output.allSent;
|
||||||
|
debugPrint("sent: $output");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
//bool isDark = true;
|
||||||
|
return Theme(
|
||||||
|
data: ThemeData.dark(), //isDark ? ThemeData.dark() : ThemeData.light(),
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: isDiscovering
|
||||||
|
? const Text('Bluetooth Devices (searching...)')
|
||||||
|
: const Text('Bluetooth Devices'),
|
||||||
|
actions: <Widget>[
|
||||||
|
isDiscovering
|
||||||
|
? FittedBox(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(16.0),
|
||||||
|
child: const CircularProgressIndicator(
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: IconButton(
|
||||||
|
icon: const Icon(Icons.replay),
|
||||||
|
onPressed: _restartDiscovery,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
body: // Center(
|
||||||
|
/*child:*/ SingleChildScrollView(
|
||||||
|
physics: const ScrollPhysics(),
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
_SingleSection(
|
||||||
|
title: "Setup",
|
||||||
|
children: [
|
||||||
|
const _CustomListTile(
|
||||||
|
title: "Please Enable Bluetooth",
|
||||||
|
icon: Icons.info_outline_rounded,
|
||||||
|
),
|
||||||
|
_CustomListTile(
|
||||||
|
title: "Bluetooth Settings",
|
||||||
|
icon: Icons.bluetooth_connected,
|
||||||
|
onTap: () async {
|
||||||
|
await AppSettings.openBluetoothSettings();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await Permission.bluetoothConnect.request();
|
||||||
|
await Permission.bluetoothScan.request();
|
||||||
|
if (await Permission.bluetoothScan.request().isGranted) {
|
||||||
|
// Either the permission was already granted before or the user just granted it.
|
||||||
|
debugPrint("Location Permission is granted");
|
||||||
|
} else {
|
||||||
|
debugPrint("Location Permission is denied.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: buttonStyle,
|
||||||
|
child: const Text("Request Permissions"),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
_restartDiscovery();
|
||||||
|
},
|
||||||
|
style: buttonStyle,
|
||||||
|
child: const Text("Restart Scan"),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
_sendData();
|
||||||
|
},
|
||||||
|
style: buttonStyle,
|
||||||
|
child: const Text("Send Data"),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
const _SingleSection(
|
||||||
|
title: "Bluetooth Devices",
|
||||||
|
children: [],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ListView.builder(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: _discoveryResults.length,
|
||||||
|
itemBuilder: (BuildContext context, index) {
|
||||||
|
BluetoothDiscoveryResult result = _discoveryResults[index];
|
||||||
|
final device = result.device;
|
||||||
|
final address = device.address;
|
||||||
|
return BluetoothDeviceListEntry(
|
||||||
|
device: device,
|
||||||
|
rssi: result.rssi,
|
||||||
|
onTap: () {
|
||||||
|
debugPrint(_bluetoothConnection?.isConnected.toString());
|
||||||
|
if (_bluetoothConnection?.isConnected ?? false) {
|
||||||
|
_disconnectDevice(device);
|
||||||
|
} else {
|
||||||
|
_connectDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Navigator.of(context).pop(result.device);
|
||||||
|
},
|
||||||
|
onLongPress: () async {
|
||||||
|
try {
|
||||||
|
bool bonded = false;
|
||||||
|
if (device.isBonded) {
|
||||||
|
debugPrint('Unbonding from ${device.address}...');
|
||||||
|
await FlutterBluetoothSerial.instance
|
||||||
|
.removeDeviceBondWithAddress(address);
|
||||||
|
debugPrint(
|
||||||
|
'Unbonding from ${device.address} has succed');
|
||||||
|
} else {
|
||||||
|
debugPrint('Bonding with ${device.address}...');
|
||||||
|
bonded = (await FlutterBluetoothSerial.instance
|
||||||
|
.bondDeviceAtAddress(address))!;
|
||||||
|
debugPrint(
|
||||||
|
'Bonding with ${device.address} has ${bonded ? 'succed' : 'failed'}.');
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_discoveryResults[_discoveryResults.indexOf(result)] =
|
||||||
|
BluetoothDiscoveryResult(
|
||||||
|
device: BluetoothDevice(
|
||||||
|
name: device.name ?? '',
|
||||||
|
address: address,
|
||||||
|
type: device.type,
|
||||||
|
bondState: bonded
|
||||||
|
? BluetoothBondState.bonded
|
||||||
|
: BluetoothBondState.none,
|
||||||
|
),
|
||||||
|
rssi: result.rssi);
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Error occured while bonding'),
|
||||||
|
content: Text(ex.toString()),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: const Text("Close"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
//),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/*
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -60,6 +392,19 @@ class _BluetoothScreen extends State<BluetoothScreen> {
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
const Text(
|
||||||
|
"General",
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
_CustomListTile(
|
||||||
|
title: "Dark Mode",
|
||||||
|
icon: CupertinoIcons.moon,
|
||||||
|
trailing:
|
||||||
|
CupertinoSwitch(value: false, onChanged: (value) {})),
|
||||||
|
]
|
||||||
|
),
|
||||||
TextField(
|
TextField(
|
||||||
controller: textFieldValueHolder,
|
controller: textFieldValueHolder,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
|
@ -81,6 +426,39 @@ class _BluetoothScreen extends State<BluetoothScreen> {
|
||||||
child: const Text("check name"),
|
child: const Text("check name"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
//_startDiscovery();
|
||||||
|
initScan();
|
||||||
|
},
|
||||||
|
style: buttonStyle,
|
||||||
|
child: const Text("discover devices"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
//_startDiscovery();
|
||||||
|
_sendData();
|
||||||
|
},
|
||||||
|
style: buttonStyle,
|
||||||
|
child: const Text("send data"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
//_startDiscovery();
|
||||||
|
dispose();
|
||||||
|
},
|
||||||
|
style: buttonStyle,
|
||||||
|
child: const Text("dispose"),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -114,37 +492,64 @@ class _BluetoothScreen extends State<BluetoothScreen> {
|
||||||
),
|
),
|
||||||
//),
|
//),
|
||||||
),
|
),
|
||||||
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> checkNameAvailability(String input) async {
|
class _CustomListTile extends StatelessWidget {
|
||||||
// await cloudServiceAPI.loadConfig();
|
final String title;
|
||||||
List<dynamic> devices = await cloudServiceAPI.getDevices();
|
final IconData icon;
|
||||||
for (Map<String, dynamic> selected in devices) {
|
final VoidCallback? onTap;
|
||||||
if (selected["id"] == input) {
|
//final Widget? trailing;
|
||||||
await showNameAvailabilityStatus(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await showNameAvailabilityStatus(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> showNameAvailabilityStatus(bool status) async {
|
const _CustomListTile({
|
||||||
String statusText = status
|
Key? key,
|
||||||
? "die eingegebene ID ist verfügbar"
|
required this.title,
|
||||||
: "die eingegebene ID ist nicht verfügbar";
|
required this.icon,
|
||||||
Fluttertoast.showToast(
|
this.onTap,
|
||||||
msg: statusText,
|
//this.trailing,
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
}) : super(key: key);
|
||||||
gravity: ToastGravity.BOTTOM,
|
|
||||||
timeInSecForIosWeb: 2,
|
@override
|
||||||
backgroundColor: Colors.grey[200],
|
Widget build(BuildContext context) {
|
||||||
textColor: Colors.black,
|
return ListTile(
|
||||||
fontSize: 16.0);
|
title: Text(title),
|
||||||
|
leading: Icon(icon),
|
||||||
|
onTap: onTap,
|
||||||
|
//trailing: trailing,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BluetoothScan{
|
class _SingleSection extends StatelessWidget {
|
||||||
|
final String? title;
|
||||||
|
final List<Widget> children;
|
||||||
|
|
||||||
|
const _SingleSection({
|
||||||
|
Key? key,
|
||||||
|
this.title,
|
||||||
|
required this.children,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (title != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
title!,
|
||||||
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
|
||||||
|
|
||||||
|
import './bluetooth_device_list_entry.dart';
|
||||||
|
|
||||||
|
class BluetoothScreen extends StatefulWidget {
|
||||||
|
/// If true, discovery starts on page start, otherwise user must press action button.
|
||||||
|
final bool start;
|
||||||
|
|
||||||
|
const BluetoothScreen({this.start = true});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_BluetoothScreen createState() => new _BluetoothScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BluetoothScreen extends State<BluetoothScreen> {
|
||||||
|
StreamSubscription<BluetoothDiscoveryResult>? _streamSubscription;
|
||||||
|
List<BluetoothDiscoveryResult> results =
|
||||||
|
List<BluetoothDiscoveryResult>.empty(growable: true);
|
||||||
|
bool isDiscovering = false;
|
||||||
|
|
||||||
|
_BluetoothScreen();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
isDiscovering = widget.start;
|
||||||
|
if (isDiscovering) {
|
||||||
|
_startDiscovery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _restartDiscovery() {
|
||||||
|
setState(() {
|
||||||
|
results.clear();
|
||||||
|
isDiscovering = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
_startDiscovery();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startDiscovery() {
|
||||||
|
_streamSubscription =
|
||||||
|
FlutterBluetoothSerial.instance.startDiscovery().listen((r) {
|
||||||
|
setState(() {
|
||||||
|
final existingIndex = results.indexWhere(
|
||||||
|
(element) => element.device.address == r.device.address);
|
||||||
|
if (existingIndex >= 0)
|
||||||
|
results[existingIndex] = r;
|
||||||
|
else
|
||||||
|
results.add(r);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_streamSubscription!.onDone(() {
|
||||||
|
setState(() {
|
||||||
|
isDiscovering = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO . One day there should be `_pairDevice` on long tap on something... ;)
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// Avoid memory leak (`setState` after dispose) and cancel discovery
|
||||||
|
_streamSubscription?.cancel();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: isDiscovering
|
||||||
|
? Text('Discovering devices')
|
||||||
|
: Text('Discovered devices'),
|
||||||
|
actions: <Widget>[
|
||||||
|
isDiscovering
|
||||||
|
? FittedBox(
|
||||||
|
child: Container(
|
||||||
|
margin: new EdgeInsets.all(16.0),
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: IconButton(
|
||||||
|
icon: Icon(Icons.replay),
|
||||||
|
onPressed: _restartDiscovery,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: ListView.builder(
|
||||||
|
itemCount: results.length,
|
||||||
|
itemBuilder: (BuildContext context, index) {
|
||||||
|
BluetoothDiscoveryResult result = results[index];
|
||||||
|
final device = result.device;
|
||||||
|
final address = device.address;
|
||||||
|
return BluetoothDeviceListEntry(
|
||||||
|
device: device,
|
||||||
|
rssi: result.rssi,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop(result.device);
|
||||||
|
},
|
||||||
|
onLongPress: () async {
|
||||||
|
try {
|
||||||
|
bool bonded = false;
|
||||||
|
if (device.isBonded) {
|
||||||
|
print('Unbonding from ${device.address}...');
|
||||||
|
await FlutterBluetoothSerial.instance
|
||||||
|
.removeDeviceBondWithAddress(address);
|
||||||
|
print('Unbonding from ${device.address} has succed');
|
||||||
|
} else {
|
||||||
|
print('Bonding with ${device.address}...');
|
||||||
|
bonded = (await FlutterBluetoothSerial.instance
|
||||||
|
.bondDeviceAtAddress(address))!;
|
||||||
|
print(
|
||||||
|
'Bonding with ${device.address} has ${bonded ? 'succed' : 'failed'}.');
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
results[results.indexOf(result)] = BluetoothDiscoveryResult(
|
||||||
|
device: BluetoothDevice(
|
||||||
|
name: device.name ?? '',
|
||||||
|
address: address,
|
||||||
|
type: device.type,
|
||||||
|
bondState: bonded
|
||||||
|
? BluetoothBondState.bonded
|
||||||
|
: BluetoothBondState.none,
|
||||||
|
),
|
||||||
|
rssi: result.rssi);
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Error occured while bonding'),
|
||||||
|
content: Text("${ex.toString()}"),
|
||||||
|
actions: <Widget>[
|
||||||
|
new TextButton(
|
||||||
|
child: new Text("Close"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
50
pubspec.lock
50
pubspec.lock
|
@ -1,6 +1,14 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
app_settings:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: app_settings
|
||||||
|
sha256: "7a5b880e2dd41dba8877108180380a1d28d874c231f7c0f9022127a4061b88e1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.8"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -224,6 +232,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
|
permission_handler:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: permission_handler
|
||||||
|
sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.2.0"
|
||||||
|
permission_handler_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_android
|
||||||
|
sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.2.0"
|
||||||
|
permission_handler_apple:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_apple
|
||||||
|
sha256: "9c370ef6a18b1c4b2f7f35944d644a56aa23576f23abee654cf73968de93f163"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.0.7"
|
||||||
|
permission_handler_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_platform_interface
|
||||||
|
sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.9.0"
|
||||||
|
permission_handler_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_windows
|
||||||
|
sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -415,4 +463,4 @@ packages:
|
||||||
version: "0.2.0+2"
|
version: "0.2.0+2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.18.0 <4.0.0"
|
dart: ">=2.18.0 <4.0.0"
|
||||||
flutter: ">=3.0.0"
|
flutter: ">=3.0.1"
|
||||||
|
|
|
@ -41,6 +41,8 @@ dependencies:
|
||||||
shared_preferences: ^2.0.13
|
shared_preferences: ^2.0.13
|
||||||
http: ^0.13.5
|
http: ^0.13.5
|
||||||
fluttertoast: ^8.1.1
|
fluttertoast: ^8.1.1
|
||||||
|
app_settings: ^4.1.8
|
||||||
|
permission_handler: ^10.2.0
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
Loading…
Reference in New Issue