This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
class LasertagUUIDs {
|
||||
static const String base = "03afe2cf-6c64-4a22-9289-c3ae820c";
|
||||
static const String provService = "${base}1000";
|
||||
static const String provNameChar = "${base}1001";
|
||||
static const String typeChar = "${base}1008";
|
||||
|
||||
// Gerätetypen aus deiner ble_mgmt.h
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
import '../../constants.dart';
|
||||
|
||||
class LasertagDevice {
|
||||
@@ -5,11 +6,13 @@ class LasertagDevice {
|
||||
final String name;
|
||||
final int type;
|
||||
final bool isConnected;
|
||||
final BluetoothDevice btDevice;
|
||||
|
||||
LasertagDevice({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.type,
|
||||
required this.btDevice,
|
||||
this.isConnected = false,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
import 'dart:convert';
|
||||
import '../models/device_model.dart';
|
||||
import '../constants.dart';
|
||||
|
||||
@@ -12,6 +13,66 @@ class DeviceProvider extends ChangeNotifier {
|
||||
List<LasertagDevice> get leaders => _discoveredLeaders;
|
||||
List<LasertagDevice> get peripherals => _discoveredPeripherals;
|
||||
|
||||
Future<void> updateDeviceNameOnHardware(
|
||||
LasertagDevice ltDevice,
|
||||
String newName,
|
||||
) async {
|
||||
try {
|
||||
await ltDevice.btDevice.connect();
|
||||
List<BluetoothService> services = await ltDevice.btDevice
|
||||
.discoverServices();
|
||||
|
||||
var service = services.firstWhere(
|
||||
(s) => s.uuid == Guid(LasertagUUIDs.provService),
|
||||
);
|
||||
var characteristic = service.characteristics.firstWhere(
|
||||
(c) => c.uuid == Guid(LasertagUUIDs.provNameChar),
|
||||
);
|
||||
|
||||
// 1. Auf Hardware schreiben (UTF-8)
|
||||
await characteristic.write(utf8.encode(newName));
|
||||
|
||||
// 2. Lokal in der App-Liste aktualisieren
|
||||
updateDeviceName(ltDevice.id, newName);
|
||||
|
||||
await ltDevice.btDevice.disconnect();
|
||||
} catch (e) {
|
||||
debugPrint("Hardware-Fehler: $e");
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
void updateDeviceName(String id, String newName) {
|
||||
// In den Leadern suchen
|
||||
int leaderIndex = _discoveredLeaders.indexWhere((d) => d.id == id);
|
||||
if (leaderIndex != -1) {
|
||||
final old = _discoveredLeaders[leaderIndex];
|
||||
_discoveredLeaders[leaderIndex] = LasertagDevice(
|
||||
id: old.id,
|
||||
name: newName,
|
||||
type: old.type,
|
||||
btDevice: old.btDevice,
|
||||
isConnected: old.isConnected,
|
||||
);
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
// In der Ausrüstung suchen
|
||||
int peripheralIndex = _discoveredPeripherals.indexWhere((d) => d.id == id);
|
||||
if (peripheralIndex != -1) {
|
||||
final old = _discoveredPeripherals[peripheralIndex];
|
||||
_discoveredPeripherals[peripheralIndex] = LasertagDevice(
|
||||
id: old.id,
|
||||
name: newName,
|
||||
type: old.type,
|
||||
btDevice: old.btDevice,
|
||||
isConnected: old.isConnected,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void startScan() async {
|
||||
// Listen leeren für neuen Scan
|
||||
_discoveredLeaders.clear();
|
||||
@@ -19,21 +80,27 @@ class DeviceProvider extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
|
||||
// 1. Bluetooth-Status prüfen
|
||||
await FlutterBluePlus.adapterState.where((s) => s == BluetoothAdapterState.on).first;
|
||||
await FlutterBluePlus.adapterState
|
||||
.where((s) => s == BluetoothAdapterState.on)
|
||||
.first;
|
||||
|
||||
// 2. Scan-Ergebnisse verarbeiten
|
||||
_scanSubscription?.cancel();
|
||||
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
|
||||
for (ScanResult r in results) {
|
||||
final mfgData = r.advertisementData.manufacturerData[65535]; // Unsere ID 0xFFFF
|
||||
|
||||
final mfgData =
|
||||
r.advertisementData.manufacturerData[65535]; // Unsere ID 0xFFFF
|
||||
|
||||
if (mfgData != null && mfgData.isNotEmpty) {
|
||||
int type = mfgData[0]; // Typ-Byte vom nRF52
|
||||
|
||||
|
||||
final device = LasertagDevice(
|
||||
id: r.device.remoteId.toString(),
|
||||
name: r.device.platformName.isEmpty ? "Unbekannt" : r.device.platformName,
|
||||
name: r.device.platformName.isEmpty
|
||||
? "Unbekannt"
|
||||
: r.device.platformName,
|
||||
type: type,
|
||||
btDevice: r.device,
|
||||
isConnected: false,
|
||||
);
|
||||
|
||||
@@ -82,4 +149,4 @@ class DeviceProvider extends ChangeNotifier {
|
||||
_scanSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
||||
import '../../models/device_model.dart';
|
||||
import '../../providers/device_provider.dart';
|
||||
import '../../constants.dart';
|
||||
import '../widgets/rename_dialog.dart';
|
||||
import 'package:lasertag_app/l10n/app_localizations.dart';
|
||||
|
||||
class DeviceSelectionScreen extends StatefulWidget {
|
||||
@@ -24,7 +25,7 @@ class _DeviceSelectionScreenState extends State<DeviceSelectionScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Hier "lauscht" der Screen jetzt auf Änderungen im Provider
|
||||
final provider = context.watch<DeviceProvider>();
|
||||
final provider = context.watch<DeviceProvider>();
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
@@ -78,8 +79,11 @@ class _DeviceSelectionScreenState extends State<DeviceSelectionScreen> {
|
||||
subtitle: Text("BTLE-ID: ${device.id}"),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
debugPrint("${device.name} ausgewählt für Konfiguration");
|
||||
},
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => RenameDialog(device: device),
|
||||
);
|
||||
},
|
||||
);
|
||||
}, childCount: devices.length),
|
||||
);
|
||||
@@ -99,4 +103,55 @@ class _DeviceSelectionScreenState extends State<DeviceSelectionScreen> {
|
||||
return Icons.device_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
void _showNameEditDialog(BuildContext context, LasertagDevice device) {
|
||||
final TextEditingController nameController = TextEditingController(
|
||||
text: device.name,
|
||||
);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
_getIconForType(device.type),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const Text("Gerät umbenennen"),
|
||||
],
|
||||
),
|
||||
content: TextField(
|
||||
controller: nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Anzeigename",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
autofocus: true,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text("Abbrechen"),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (nameController.text.trim().isNotEmpty) {
|
||||
// Provider aktualisieren
|
||||
context.read<DeviceProvider>().updateDeviceName(
|
||||
device.id,
|
||||
nameController.text.trim(),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: const Text("Speichern"),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
66
software/app/lib/ui/widgets/rename_dialog.dart
Normal file
66
software/app/lib/ui/widgets/rename_dialog.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../models/device_model.dart';
|
||||
import '../../providers/device_provider.dart';
|
||||
|
||||
class RenameDialog extends StatefulWidget {
|
||||
final LasertagDevice device;
|
||||
const RenameDialog({super.key, required this.device});
|
||||
|
||||
@override
|
||||
State<RenameDialog> createState() => _RenameDialogState();
|
||||
}
|
||||
|
||||
class _RenameDialogState extends State<RenameDialog> {
|
||||
late TextEditingController _controller;
|
||||
bool _isSaving = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TextEditingController(text: widget.device.name);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text("${widget.device.name} umbenennen"),
|
||||
content: TextField(
|
||||
controller: _controller,
|
||||
decoration: const InputDecoration(labelText: "Neuer Name"),
|
||||
enabled: !_isSaving,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _isSaving ? null : () => Navigator.pop(context),
|
||||
child: const Text("Abbrechen"),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _isSaving ? null : _save,
|
||||
child: _isSaving
|
||||
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))
|
||||
: const Text("Speichern"),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _save() async {
|
||||
setState(() => _isSaving = true);
|
||||
try {
|
||||
await context.read<DeviceProvider>().updateDeviceNameOnHardware(
|
||||
widget.device,
|
||||
_controller.text.trim()
|
||||
);
|
||||
if (mounted) Navigator.pop(context);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("Fehler: $e"), backgroundColor: Colors.red),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _isSaving = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user