Bluetooth-Try
Some checks failed
Deploy Docs / build-and-deploy (push) Failing after 35s

This commit is contained in:
2026-01-12 11:45:58 +01:00
parent e460aac7a1
commit a6bedb6b79
5 changed files with 139 additions and 40 deletions

View File

@@ -3,8 +3,9 @@
"appTitle": "Lasertag Mission Control",
"selectLeader": "Leader auswählen",
"searching": "Suche nach Knoten...",
"typeLeader": "Spiel-Leiter",
"listTypeLeader": "Gameleadergeräte",
"typeWeapon": "Waffe",
"typeVest": "Weste",
"typeBeacon": "Beacon"
"typeBeacon": "Beacon",
"listTypeEquipment": "Ausrüstungsgeräte"
}

View File

@@ -112,11 +112,11 @@ abstract class AppLocalizations {
/// **'Suche nach Knoten...'**
String get searching;
/// No description provided for @typeLeader.
/// No description provided for @listTypeLeader.
///
/// In de, this message translates to:
/// **'Spiel-Leiter'**
String get typeLeader;
/// **'Gameleadergeräte'**
String get listTypeLeader;
/// No description provided for @typeWeapon.
///
@@ -135,6 +135,12 @@ abstract class AppLocalizations {
/// In de, this message translates to:
/// **'Beacon'**
String get typeBeacon;
/// No description provided for @listTypeEquipment.
///
/// In de, this message translates to:
/// **'Ausrüstungsgeräte'**
String get listTypeEquipment;
}
class _AppLocalizationsDelegate

View File

@@ -18,7 +18,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get searching => 'Suche nach Knoten...';
@override
String get typeLeader => 'Spiel-Leiter';
String get listTypeLeader => 'Gameleadergeräte';
@override
String get typeWeapon => 'Waffe';
@@ -28,4 +28,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get typeBeacon => 'Beacon';
@override
String get listTypeEquipment => 'Ausrüstungsgeräte';
}

View File

@@ -1,17 +1,85 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import '../models/device_model.dart';
import '../constants.dart';
class DeviceProvider extends ChangeNotifier {
// Unsere Dummy-Listen
List<LasertagDevice> get dummyLeaders => [
LasertagDevice(id: "L-01", name: "EdiLeader Alpha", type: LasertagUUIDs.typeLeader),
LasertagDevice(id: "L-02", name: "Säntis Funker", type: LasertagUUIDs.typeLeader),
];
final List<LasertagDevice> _discoveredLeaders = [];
final List<LasertagDevice> _discoveredPeripherals = [];
StreamSubscription? _scanSubscription;
List<LasertagDevice> get dummyEquip => [
LasertagDevice(id: "W-01", name: "Blaster 1", type: LasertagUUIDs.typeWeapon),
LasertagDevice(id: "V-01", name: "Weste Blau", type: LasertagUUIDs.typeVest),
LasertagDevice(id: "B-01", name: "Basis Mitte", type: LasertagUUIDs.typeBeacon),
];
List<LasertagDevice> get leaders => _discoveredLeaders;
List<LasertagDevice> get peripherals => _discoveredPeripherals;
void startScan() async {
// Listen leeren für neuen Scan
_discoveredLeaders.clear();
_discoveredPeripherals.clear();
notifyListeners();
// 1. Bluetooth-Status prüfen
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
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,
type: type,
isConnected: false,
);
_addDeviceToLists(device);
}
}
});
// 3. Scan starten (gefiltert nach unserem Service)
try {
await FlutterBluePlus.startScan(
withServices: [Guid(LasertagUUIDs.provService)], //
timeout: const Duration(seconds: 15),
);
} catch (e) {
debugPrint("Scan-Fehler: $e");
}
}
void _addDeviceToLists(LasertagDevice device) {
if (device.isLeader) {
if (!_discoveredLeaders.any((d) => d.id == device.id)) {
_discoveredLeaders.add(device);
_sortList(_discoveredLeaders);
notifyListeners();
}
} else {
if (!_discoveredPeripherals.any((d) => d.id == device.id)) {
_discoveredPeripherals.add(device);
_sortList(_discoveredPeripherals);
notifyListeners();
}
}
}
void _sortList(List<LasertagDevice> list) {
list.sort((a, b) {
int typeComp = a.type.compareTo(b.type);
if (typeComp != 0) return typeComp;
return a.name.toLowerCase().compareTo(b.name.toLowerCase());
});
}
@override
void dispose() {
_scanSubscription?.cancel();
super.dispose();
}
}

View File

@@ -12,17 +12,19 @@ class DeviceSelectionScreen extends StatelessWidget {
final provider = DeviceProvider(); // Später via "Provider"-Paket
final l10n = AppLocalizations.of(context)!;
provider.startScan(); // Scan starten beim Laden des Bildschirms
return Scaffold(
appBar: AppBar(title: Text(l10n.appTitle)),
body: CustomScrollView(
slivers: [
// SEKTION 1: SPIEL-LEITER
_buildHeader(context, l10n.typeLeader),
_buildDeviceList(context, provider.dummyLeaders, isLeader: true),
_buildHeader(context, l10n.listTypeLeader),
_buildDeviceList(context, provider.leaders , isLeader: true),
// SEKTION 2: AUSRÜSTUNG (Waffen, Westen, etc.)
_buildHeader(context, "Meine Ausrüstung"), // Später in ARB übersetzen
_buildDeviceList(context, provider.dummyEquip, isLeader: false),
_buildHeader(context, l10n.listTypeEquipment),
_buildDeviceList(context, provider.peripherals, isLeader: false),
],
),
);
@@ -45,26 +47,45 @@ class DeviceSelectionScreen extends StatelessWidget {
);
}
Widget _buildDeviceList(BuildContext context, List<LasertagDevice> devices, {required bool isLeader}) {
Widget _buildDeviceList(
BuildContext context,
List<LasertagDevice> devices, {
required bool isLeader,
}) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final device = devices[index];
return ListTile(
leading: Icon(
isLeader ? Icons.hub : Icons.radar,
color: Theme.of(context).colorScheme.primary,
),
title: Text(device.name, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text("ID: ${device.id}"),
trailing: const Icon(Icons.chevron_right),
onTap: () {
debugPrint("${device.name} ausgewählt für Konfiguration");
},
);
},
childCount: devices.length,
),
delegate: SliverChildBuilderDelegate((context, index) {
final device = devices[index];
return ListTile(
leading: Icon(
_getIconForType(device.type), // Dynamisches Icon
color: Theme.of(context).colorScheme.primary,
),
title: Text(
device.name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text("ID: ${device.id}"),
trailing: const Icon(Icons.chevron_right),
onTap: () {
debugPrint("${device.name} ausgewählt für Konfiguration");
},
);
}, childCount: devices.length),
);
}
}
IconData _getIconForType(int type) {
switch (type) {
case LasertagUUIDs.typeLeader:
return Icons.hub;
case LasertagUUIDs.typeWeapon:
return Icons.flash_on; // Blitz für Waffe
case LasertagUUIDs.typeVest:
return Icons.accessibility_new; // Person für Weste
case LasertagUUIDs.typeBeacon:
return Icons.flag; // Flagge für Basis
default:
return Icons.device_unknown;
}
}
}