BLE Scan implemented
All checks were successful
Deploy Docs / build-and-deploy (push) Successful in 12s

This commit is contained in:
2026-01-12 11:57:47 +01:00
parent a6bedb6b79
commit 9d5dad0e8d
5 changed files with 49 additions and 153 deletions

View File

@@ -1,128 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:lasertag_app/l10n/app_localizations.dart';
import 'constants.dart';
class LeaderSelectionScreen extends StatefulWidget {
const LeaderSelectionScreen({super.key});
@override
State<LeaderSelectionScreen> createState() => _LeaderSelectionScreenState();
}
class _LeaderSelectionScreenState extends State<LeaderSelectionScreen> {
// Map, um Geräte und ihren erkannten Typ zu speichern
Map<BluetoothDevice, int> discoveredDevices = {};
@override
void initState() {
super.initState();
_startDiscovery();
}
void _startDiscovery() async {
FlutterBluePlus.scanResults.listen((results) {
for (ScanResult r in results) {
// 1. Prüfen, ob Manufacturer Data für unsere Test-ID (0xFFFF / 65535) da ist
final mfgData = r.advertisementData.manufacturerData[65535];
if (mfgData != null && mfgData.isNotEmpty) {
int detectedType = mfgData[0]; // Das Typ-Byte vom nRF52
// 2. Nur hinzufügen, wenn es wirklich ein Leader (0x01) ist
if (detectedType == LasertagUUIDs.typeLeader) {
if (!discoveredDevices.containsKey(r.device)) {
setState(() {
discoveredDevices[r.device] = detectedType;
});
}
}
}
}
});
// 2. Warten, bis der Bluetooth-Adapter wirklich "on" ist
await FlutterBluePlus.adapterState
.where((s) => s == BluetoothAdapterState.on)
.first;
// 3. Den Scan tatsächlich starten
try {
// Filtert nach der Service-UUID ...1000 aus ble_mgmt.c
await FlutterBluePlus.startScan(
withServices: [Guid(LasertagUUIDs.provService)],
timeout: const Duration(seconds: 15),
);
} catch (e) {
debugPrint("Scan-Fehler: $e");
}
} // <--- Hier endet _startDiscovery korrekt
// Hilfsfunktion zur Übersetzung des Typs
String _getLocalizedTypeName(BuildContext context, int type) {
final l10n = AppLocalizations.of(context)!;
switch (type) {
case LasertagUUIDs.typeLeader:
return l10n.typeLeader;
case LasertagUUIDs.typeWeapon:
return l10n.typeWeapon;
case LasertagUUIDs.typeVest:
return l10n.typeVest;
case LasertagUUIDs.typeBeacon:
return l10n.typeBeacon;
default:
return "Unknown";
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.selectLeader)),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(l10n.searching),
),
Expanded(
child: ListView(
children: discoveredDevices.entries.map((entry) {
final device = entry.key;
final type = entry.value;
return ListTile(
leading: Icon(
Icons.hub,
// Holt sich die Akzentfarbe direkt vom Betriebssystem via Theme-Context
color: Theme.of(context).colorScheme.primary,
size: 32,
),
title: Text(
device.platformName.isEmpty
? "Lasertag Device"
: device.platformName,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
"${_getLocalizedTypeName(context, type)} (${device.remoteId})",
// Nutzt eine dezentere Farbe für den Untertitel
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
onTap: () => _onLeaderSelected(device),
);
}).toList(),
),
),
],
),
);
}
void _onLeaderSelected(BluetoothDevice device) {
debugPrint("Leader ausgewählt: ${device.platformName}");
// Hier öffnen wir als nächstes den Config-Dialog
}
}

View File

@@ -1,11 +1,19 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; // Neu
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:lasertag_app/l10n/app_localizations.dart';
import 'ui/screens/device_selection_screen.dart';
import 'providers/device_provider.dart'; // Neu
void main() {
runApp(const LasertagApp());
runApp(
// Den Provider hier für die ganze App bereitstellen
ChangeNotifierProvider(
create: (context) => DeviceProvider(),
child: const LasertagApp(),
),
);
}
class LasertagApp extends StatelessWidget {
@@ -13,24 +21,13 @@ class LasertagApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Der Builder holt die Systemfarben (Accent Color)
return DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
return MaterialApp(
debugShowCheckedModeBanner: false,
// --- Lokalisierung ---
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('de')],
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
locale: const Locale('de'),
// --- Theme-Logik ---
// Falls das OS Farben liefert, nutzen wir diese, sonst ein Standard-Blau
theme: ThemeData(
colorScheme: lightDynamic ?? ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
@@ -42,9 +39,8 @@ class LasertagApp extends StatelessWidget {
),
useMaterial3: true,
),
themeMode: ThemeMode.system, // Folgt macOS/Android Hell/Dunkel Modus
home: DeviceSelectionScreen(),
themeMode: ThemeMode.system,
home: const DeviceSelectionScreen(),
);
},
);

View File

@@ -1,28 +1,39 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../models/device_model.dart';
import '../../providers/device_provider.dart';
import '../../constants.dart';
import 'package:lasertag_app/l10n/app_localizations.dart';
class DeviceSelectionScreen extends StatelessWidget {
class DeviceSelectionScreen extends StatefulWidget {
const DeviceSelectionScreen({super.key});
@override
State<DeviceSelectionScreen> createState() => _DeviceSelectionScreenState();
}
class _DeviceSelectionScreenState extends State<DeviceSelectionScreen> {
@override
void initState() {
super.initState();
// Scan beim Starten genau einmal triggern
// "listen: false" ist wichtig in initState
Provider.of<DeviceProvider>(context, listen: false).startScan();
}
@override
Widget build(BuildContext context) {
final provider = DeviceProvider(); // Später via "Provider"-Paket
// Hier "lauscht" der Screen jetzt auf Änderungen im Provider
final provider = context.watch<DeviceProvider>();
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.listTypeLeader),
_buildDeviceList(context, provider.leaders , isLeader: true),
_buildDeviceList(context, provider.leaders, isLeader: true),
// SEKTION 2: AUSRÜSTUNG (Waffen, Westen, etc.)
_buildHeader(context, l10n.listTypeEquipment),
_buildDeviceList(context, provider.peripherals, isLeader: false),
],
@@ -64,7 +75,7 @@ class DeviceSelectionScreen extends StatelessWidget {
device.name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text("ID: ${device.id}"),
subtitle: Text("BTLE-ID: ${device.id}"),
trailing: const Icon(Icons.chevron_right),
onTap: () {
debugPrint("${device.name} ausgewählt für Konfiguration");