diff --git a/software/app/lib/l10n/app_de.arb b/software/app/lib/l10n/app_de.arb index e5af879..cbd4b8b 100644 --- a/software/app/lib/l10n/app_de.arb +++ b/software/app/lib/l10n/app_de.arb @@ -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" } diff --git a/software/app/lib/l10n/app_localizations.dart b/software/app/lib/l10n/app_localizations.dart index b68916e..0c263fd 100644 --- a/software/app/lib/l10n/app_localizations.dart +++ b/software/app/lib/l10n/app_localizations.dart @@ -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 diff --git a/software/app/lib/l10n/app_localizations_de.dart b/software/app/lib/l10n/app_localizations_de.dart index f109bea..823cb2e 100644 --- a/software/app/lib/l10n/app_localizations_de.dart +++ b/software/app/lib/l10n/app_localizations_de.dart @@ -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'; } diff --git a/software/app/lib/providers/device_provider.dart b/software/app/lib/providers/device_provider.dart index 58b9123..360faea 100644 --- a/software/app/lib/providers/device_provider.dart +++ b/software/app/lib/providers/device_provider.dart @@ -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 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 _discoveredLeaders = []; + final List _discoveredPeripherals = []; + StreamSubscription? _scanSubscription; - List 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 get leaders => _discoveredLeaders; + List 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 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(); + } } \ No newline at end of file diff --git a/software/app/lib/ui/screens/device_selection_screen.dart b/software/app/lib/ui/screens/device_selection_screen.dart index a3cd4b8..60bfa75 100644 --- a/software/app/lib/ui/screens/device_selection_screen.dart +++ b/software/app/lib/ui/screens/device_selection_screen.dart @@ -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 devices, {required bool isLeader}) { + Widget _buildDeviceList( + BuildContext context, + List 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), ); } -} \ No newline at end of file + + 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; + } + } +}