sync
All checks were successful
Deploy Docs / build-and-deploy (push) Successful in 21s

This commit is contained in:
2026-01-12 12:21:00 +01:00
parent 9d5dad0e8d
commit 6b1bbca992
5 changed files with 201 additions and 9 deletions

View File

@@ -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

View File

@@ -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,
});

View File

@@ -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();
}
}
}

View File

@@ -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"),
),
],
);
},
);
}
}

View 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);
}
}
}