This commit is contained in:
@@ -1,18 +1,22 @@
|
||||
class LasertagUUIDs {
|
||||
class LasertagConfig {
|
||||
// BLE UUID for services and characteristics
|
||||
static const String base = "03afe2cf-6c64-4a22-9289-c3ae820c";
|
||||
static const String provService = "${base}1000";
|
||||
static const String provNameChar = "${base}1001";
|
||||
// static const String provPanIdChar = "${base}1002";
|
||||
// static const String provChanChar = "${base}1003";
|
||||
// static const String provExtPanIdChar = "${base}1004";
|
||||
// static const String provNetKeyChar = "${base}1005";
|
||||
// static const String provNetNameChar = "${base}1006";
|
||||
static const String provTypeChar = "${base}1008";
|
||||
static const String provConfigChar = "${base}100c";
|
||||
|
||||
// Gerätetypen aus deiner ble_mgmt.h
|
||||
// Device types from ble_mgmt.h
|
||||
static const int typeLeader = 0x01;
|
||||
static const int typeWeapon = 0x02;
|
||||
static const int typeVest = 0x03;
|
||||
static const int typeBeacon = 0x04;
|
||||
|
||||
// System states from game_mgmt.h
|
||||
static const int sysStateNoChange = 0x00;
|
||||
static const int sysStateIdle = 0x01;
|
||||
static const int sysStateLobby = 0x02;
|
||||
static const int sysStateStarting = 0x03;
|
||||
static const int sysStateRunning = 0x04;
|
||||
static const int sysStatePostGame = 0x05;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:math';
|
||||
|
||||
class DeviceConfig {
|
||||
final int systemState;
|
||||
@@ -22,9 +23,30 @@ class DeviceConfig {
|
||||
required this.nodeName,
|
||||
});
|
||||
|
||||
static BigInt generateRandom64BitId() {
|
||||
final rand = Random.secure();
|
||||
final low = rand.nextInt(0xFFFFFFFF);
|
||||
final high = rand.nextInt(0xFFFFFFFF);
|
||||
return (BigInt.from(high) << 32) | BigInt.from(low);
|
||||
}
|
||||
|
||||
// Hilfsmethode für eine leere/Standard-Config
|
||||
factory DeviceConfig.defaultConfig() {
|
||||
return DeviceConfig(
|
||||
systemState: 1, // IDLE
|
||||
gameId: BigInt.from(0),
|
||||
panId: 0xABCD,
|
||||
channel: 15,
|
||||
extPanId: "0011223344556677",
|
||||
networkKey: "00112233445566778899AABBCCDDEEFF",
|
||||
networkName: "LasertagMesh",
|
||||
nodeName: "New Device",
|
||||
);
|
||||
}
|
||||
|
||||
factory DeviceConfig.fromBytes(List<int> bytes) {
|
||||
final data = ByteData.sublistView(Uint8List.fromList(bytes));
|
||||
|
||||
|
||||
// Manuelles Decoding von 64-Bit (2x 32-Bit), da getUint64 nicht Standard ist
|
||||
final low = data.getUint32(1, Endian.little);
|
||||
final high = data.getUint32(5, Endian.little);
|
||||
@@ -47,14 +69,18 @@ class DeviceConfig {
|
||||
final data = ByteData.view(bytes.buffer);
|
||||
|
||||
data.setUint8(0, systemState);
|
||||
|
||||
|
||||
// Encoding von 64-Bit BigInt in zwei 32-Bit Segmente
|
||||
data.setUint32(1, (gameId & BigInt.from(0xFFFFFFFF)).toInt(), Endian.little);
|
||||
data.setUint32(
|
||||
1,
|
||||
(gameId & BigInt.from(0xFFFFFFFF)).toInt(),
|
||||
Endian.little,
|
||||
);
|
||||
data.setUint32(5, (gameId >> 32).toInt(), Endian.little);
|
||||
|
||||
data.setUint16(9, panId, Endian.little);
|
||||
data.setUint8(11, channel);
|
||||
|
||||
|
||||
_writeHexToBuffer(bytes, 12, extPanId);
|
||||
_writeHexToBuffer(bytes, 20, networkKey);
|
||||
_writeStringToBuffer(bytes, 36, 17, networkName);
|
||||
@@ -65,10 +91,17 @@ class DeviceConfig {
|
||||
|
||||
static String _decodeString(List<int> bytes) {
|
||||
int nullIdx = bytes.indexOf(0);
|
||||
return utf8.decode(nullIdx == -1 ? bytes : bytes.sublist(0, nullIdx)).trim();
|
||||
return utf8
|
||||
.decode(nullIdx == -1 ? bytes : bytes.sublist(0, nullIdx))
|
||||
.trim();
|
||||
}
|
||||
|
||||
static void _writeStringToBuffer(Uint8List buffer, int offset, int maxLen, String val) {
|
||||
static void _writeStringToBuffer(
|
||||
Uint8List buffer,
|
||||
int offset,
|
||||
int maxLen,
|
||||
String val,
|
||||
) {
|
||||
final encoded = utf8.encode(val);
|
||||
for (int i = 0; i < maxLen - 1 && i < encoded.length; i++) {
|
||||
buffer[offset + i] = encoded[i];
|
||||
@@ -76,12 +109,36 @@ class DeviceConfig {
|
||||
// Buffer ist bereits mit 0 initialisiert, Terminator steht also am Ende
|
||||
}
|
||||
|
||||
static String _bytesToHex(List<int> bytes) =>
|
||||
bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join().toUpperCase();
|
||||
static String _bytesToHex(List<int> bytes) => bytes
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||
.join()
|
||||
.toUpperCase();
|
||||
|
||||
static void _writeHexToBuffer(Uint8List buffer, int offset, String hex) {
|
||||
for (int i = 0; i < hex.length; i += 2) {
|
||||
buffer[offset + (i ~/ 2)] = int.parse(hex.substring(i, i + 2), radix: 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeviceConfig copyWith({
|
||||
int? systemState,
|
||||
BigInt? gameId,
|
||||
int? panId,
|
||||
int? channel,
|
||||
String? extPanId,
|
||||
String? networkKey,
|
||||
String? networkName,
|
||||
String? nodeName,
|
||||
}) {
|
||||
return DeviceConfig(
|
||||
systemState: systemState ?? this.systemState,
|
||||
gameId: gameId ?? this.gameId,
|
||||
panId: panId ?? this.panId,
|
||||
channel: channel ?? this.channel,
|
||||
extPanId: extPanId ?? this.extPanId,
|
||||
networkKey: networkKey ?? this.networkKey,
|
||||
networkName: networkName ?? this.networkName,
|
||||
nodeName: nodeName ?? this.nodeName,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class LasertagDevice {
|
||||
this.isNameVerified = false,
|
||||
});
|
||||
|
||||
bool get isLeader => type == LasertagUUIDs.typeLeader;
|
||||
bool get isLeader => type == LasertagConfig.typeLeader;
|
||||
|
||||
LasertagDevice copyWith({String? name, bool? isNameVerified}) {
|
||||
return LasertagDevice(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
@@ -14,11 +13,29 @@ class DeviceProvider extends ChangeNotifier {
|
||||
final List<LasertagDevice> _discoveredLeaders = [];
|
||||
final List<LasertagDevice> _discoveredPeripherals = [];
|
||||
StreamSubscription? _scanSubscription;
|
||||
DeviceConfig? _activeConfig;
|
||||
DeviceConfig? get activeConfig => _activeConfig;
|
||||
|
||||
List<LasertagDevice> get leaders => _discoveredLeaders;
|
||||
List<LasertagDevice> get peripherals => _discoveredPeripherals;
|
||||
|
||||
// ... startScan bleibt gleich ...
|
||||
void setActiveConfig(DeviceConfig config) {
|
||||
_activeConfig = config;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> provisionWithActiveConfig(LasertagDevice device, {bool keepName = true}) async {
|
||||
if (_activeConfig == null) throw Exception("Keine aktive Konfiguration vorhanden");
|
||||
DeviceConfig configToUse = _activeConfig!;
|
||||
if (keepName) {
|
||||
configToUse = configToUse.copyWith(
|
||||
nodeName: device.name,
|
||||
systemState: LasertagConfig.sysStateLobby,
|
||||
);
|
||||
}
|
||||
await provisionDevice(device, configToUse);
|
||||
}
|
||||
|
||||
void startScan() async {
|
||||
_discoveredLeaders.clear();
|
||||
_discoveredPeripherals.clear();
|
||||
@@ -28,7 +45,7 @@ class DeviceProvider extends ChangeNotifier {
|
||||
_scanSubscription?.cancel();
|
||||
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
|
||||
for (ScanResult r in results) {
|
||||
bool hasService = r.advertisementData.serviceUuids.contains(Guid(LasertagUUIDs.provService));
|
||||
bool hasService = r.advertisementData.serviceUuids.contains(Guid(LasertagConfig.provService));
|
||||
if (!hasService) continue;
|
||||
final mfgData = r.advertisementData.manufacturerData[65535];
|
||||
if (mfgData != null && mfgData.isNotEmpty) {
|
||||
@@ -53,8 +70,8 @@ class DeviceProvider extends ChangeNotifier {
|
||||
await ltDevice.btDevice.connect(license: License.free, timeout: const Duration(seconds: 5));
|
||||
}
|
||||
List<BluetoothService> srv = await ltDevice.btDevice.discoverServices();
|
||||
var c = srv.firstWhere((s) => s.uuid == Guid(LasertagUUIDs.provService))
|
||||
.characteristics.firstWhere((c) => c.uuid == Guid(LasertagUUIDs.provNameChar));
|
||||
var c = srv.firstWhere((s) => s.uuid == Guid(LasertagConfig.provService))
|
||||
.characteristics.firstWhere((c) => c.uuid == Guid(LasertagConfig.provNameChar));
|
||||
List<int> value = await c.read();
|
||||
String hardwareName = utf8.decode(value);
|
||||
updateDeviceName(ltDevice.id, hardwareName, verified: true);
|
||||
@@ -66,8 +83,8 @@ class DeviceProvider extends ChangeNotifier {
|
||||
try {
|
||||
await ltDevice.btDevice.connect(license: License.free, timeout: const Duration(seconds: 10));
|
||||
List<BluetoothService> srv = await ltDevice.btDevice.discoverServices();
|
||||
var c = srv.firstWhere((s) => s.uuid == Guid(LasertagUUIDs.provService))
|
||||
.characteristics.firstWhere((c) => c.uuid == Guid(LasertagUUIDs.provNameChar));
|
||||
var c = srv.firstWhere((s) => s.uuid == Guid(LasertagConfig.provService))
|
||||
.characteristics.firstWhere((c) => c.uuid == Guid(LasertagConfig.provNameChar));
|
||||
await c.write(utf8.encode(newName));
|
||||
updateDeviceName(ltDevice.id, newName, verified: true);
|
||||
} finally { await ltDevice.btDevice.disconnect(); }
|
||||
@@ -80,20 +97,19 @@ class DeviceProvider extends ChangeNotifier {
|
||||
await device.btDevice.connect(license: License.free, timeout: const Duration(seconds: 10));
|
||||
}
|
||||
List<BluetoothService> srv = await device.btDevice.discoverServices();
|
||||
var c = srv.firstWhere((s) => s.uuid == Guid(LasertagUUIDs.provService))
|
||||
.characteristics.firstWhere((c) => c.uuid == Guid(LasertagUUIDs.provConfigChar));
|
||||
var c = srv.firstWhere((s) => s.uuid == Guid(LasertagConfig.provService))
|
||||
.characteristics.firstWhere((c) => c.uuid == Guid(LasertagConfig.provConfigChar));
|
||||
final bytes = await c.read();
|
||||
return DeviceConfig.fromBytes(bytes);
|
||||
} finally { await device.btDevice.disconnect(); }
|
||||
}
|
||||
|
||||
Future<void> provisionDevice(LasertagDevice device, DeviceConfig config) async {
|
||||
Future<void> provisionDevice(LasertagDevice device, DeviceConfig config, {bool keepConnected = false}) async {
|
||||
try {
|
||||
if (!device.btDevice.isConnected) {
|
||||
await device.btDevice.connect(license: License.free, timeout: const Duration(seconds: 10));
|
||||
}
|
||||
|
||||
// MTU nur auf Android anfordern, auf macOS/iOS macht das das OS automatisch
|
||||
if (Platform.isAndroid) {
|
||||
try {
|
||||
await device.btDevice.requestMtu(250);
|
||||
@@ -102,13 +118,23 @@ class DeviceProvider extends ChangeNotifier {
|
||||
debugPrint("MTU Request failed (Android only): $e");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
List<BluetoothService> srv = await device.btDevice.discoverServices();
|
||||
var c = srv.firstWhere((s) => s.uuid == Guid(LasertagUUIDs.provService))
|
||||
.characteristics.firstWhere((c) => c.uuid == Guid(LasertagUUIDs.provConfigChar));
|
||||
var c = srv
|
||||
.firstWhere((s) => s.uuid == Guid(LasertagConfig.provService))
|
||||
.characteristics
|
||||
.firstWhere((c) => c.uuid == Guid(LasertagConfig.provConfigChar));
|
||||
|
||||
await c.write(config.toBytes(), allowLongWrite: true);
|
||||
updateDeviceName(device.id, config.nodeName, verified: true);
|
||||
} finally { await device.btDevice.disconnect(); }
|
||||
} catch (e) {
|
||||
await device.btDevice.disconnect();
|
||||
rethrow;
|
||||
} finally {
|
||||
if (!keepConnected) {
|
||||
await device.btDevice.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ... Hilfsmethoden updateDeviceName, _addDeviceToLists, _verifyNameInBackground (mit license!), _sortList etc. bleiben ...
|
||||
@@ -131,8 +157,8 @@ class DeviceProvider extends ChangeNotifier {
|
||||
try {
|
||||
await device.btDevice.connect(license: License.free, timeout: const Duration(seconds: 5));
|
||||
List<BluetoothService> services = await device.btDevice.discoverServices();
|
||||
var characteristic = services.firstWhere((s) => s.uuid == Guid(LasertagUUIDs.provService))
|
||||
.characteristics.firstWhere((c) => c.uuid == Guid(LasertagUUIDs.provNameChar));
|
||||
var characteristic = services.firstWhere((s) => s.uuid == Guid(LasertagConfig.provService))
|
||||
.characteristics.firstWhere((c) => c.uuid == Guid(LasertagConfig.provNameChar));
|
||||
List<int> value = await characteristic.read();
|
||||
updateDeviceName(device.id, utf8.decode(value), verified: true);
|
||||
} catch (e) {
|
||||
|
||||
@@ -115,13 +115,13 @@ class _DeviceSelectionScreenState extends State<DeviceSelectionScreen> {
|
||||
|
||||
IconData _getIconForType(int type) {
|
||||
switch (type) {
|
||||
case LasertagUUIDs.typeLeader:
|
||||
case LasertagConfig.typeLeader:
|
||||
return Icons.hub;
|
||||
case LasertagUUIDs.typeWeapon:
|
||||
case LasertagConfig.typeWeapon:
|
||||
return Icons.flash_on; // Blitz für Waffe
|
||||
case LasertagUUIDs.typeVest:
|
||||
case LasertagConfig.typeVest:
|
||||
return Icons.accessibility_new; // Person für Weste
|
||||
case LasertagUUIDs.typeBeacon:
|
||||
case LasertagConfig.typeBeacon:
|
||||
return Icons.flag; // Flagge für Basis
|
||||
default:
|
||||
return Icons.device_unknown;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:lasertag_app/constants.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'dart:math';
|
||||
import '../../models/device_model.dart';
|
||||
import '../../models/device_config_model.dart';
|
||||
import '../../providers/device_provider.dart';
|
||||
import "../screens/lobby_screen.dart";
|
||||
|
||||
class LeaderConfigScreen extends StatefulWidget {
|
||||
final LasertagDevice device;
|
||||
@@ -204,8 +206,10 @@ class _LeaderConfigScreenState extends State<LeaderConfigScreen> {
|
||||
try {
|
||||
// Neues Model-Objekt erstellen
|
||||
final newConfig = DeviceConfig(
|
||||
systemState: _currentConfig.systemState, // Unverändert übernehmen
|
||||
gameId: _currentConfig.gameId, // Unverändert übernehmen
|
||||
systemState: LasertagConfig.sysStateLobby, // Immer Lobby setzen
|
||||
gameId: _currentConfig.gameId == BigInt.from(0)
|
||||
? _generateRandom64BitId()
|
||||
: _currentConfig.gameId,
|
||||
nodeName: _nameCtrl.text.trim(),
|
||||
networkName: _netNameCtrl.text.trim(),
|
||||
channel: _selectedChannel,
|
||||
@@ -214,14 +218,20 @@ class _LeaderConfigScreenState extends State<LeaderConfigScreen> {
|
||||
networkKey: _netKeyCtrl.text.trim(),
|
||||
);
|
||||
|
||||
// Optimierte Methode im Provider aufrufen
|
||||
await context.read<DeviceProvider>().provisionDevice(widget.device, newConfig);
|
||||
|
||||
await context.read<DeviceProvider>().provisionDevice(
|
||||
widget.device,
|
||||
newConfig,
|
||||
keepConnected: true,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text("Konfiguration erfolgreich übertragen!")),
|
||||
// Navigation zum Lobby Screen statt pop
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LobbyScreen(leaderDevice: widget.device),
|
||||
),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
@@ -241,6 +251,14 @@ class _LeaderConfigScreenState extends State<LeaderConfigScreen> {
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
BigInt _generateRandom64BitId() {
|
||||
final random = Random.secure(); // Sicherer Zufall
|
||||
// Generiere zwei 32-Bit Werte und kombiniere sie
|
||||
final low = random.nextInt(0xFFFFFFFF);
|
||||
final high = random.nextInt(0xFFFFFFFF);
|
||||
return (BigInt.from(high) << 32) | BigInt.from(low);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameCtrl.dispose();
|
||||
|
||||
111
software/app/lib/ui/screens/lobby_screen.dart
Normal file
111
software/app/lib/ui/screens/lobby_screen.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
// lib/ui/screens/lobby_screen.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../models/device_model.dart';
|
||||
import '../widgets/leader_info_dialog.dart';
|
||||
|
||||
class LobbyScreen extends StatelessWidget {
|
||||
final LasertagDevice leaderDevice;
|
||||
const LobbyScreen({super.key, required this.leaderDevice});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Spiel-Lobby"),
|
||||
// elevation: 0 sorgt dafür, dass AppBar und Header nahtlos ineinander übergehen
|
||||
elevation: 0,
|
||||
backgroundColor: colorScheme.primaryContainer,
|
||||
foregroundColor: colorScheme.onPrimaryContainer,
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
// Header-Sektion: Volle Breite, Hintergrundfarbe aus dem Theme
|
||||
Material(
|
||||
color: colorScheme.primaryContainer,
|
||||
child: InkWell(
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => LeaderInfoDialog(device: leaderDevice),
|
||||
),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
leading: Icon(
|
||||
Icons.hub,
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
size: 32,
|
||||
),
|
||||
title: Text(
|
||||
leaderDevice.name,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
"Status: LOBBY",
|
||||
style: TextStyle(
|
||||
color: colorScheme.onPrimaryContainer.withValues(alpha: 0.8),
|
||||
),
|
||||
),
|
||||
// Das Info-Icon als visueller Hinweis auf die Klickbarkeit
|
||||
trailing: Icon(
|
||||
Icons.info_outline,
|
||||
color: colorScheme.onPrimaryContainer.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Keine Divider mehr hier für einen cleanen Übergang
|
||||
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
SizedBox(height: 16),
|
||||
Text("Suche Teilnehmer im Thread-Netzwerk..."),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Action Buttons am unteren Rand
|
||||
_buildBottomActions(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomActions(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text("Abbrechen"),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: null, // Deaktiviert, bis Nodes gefunden werden
|
||||
child: const Text("Match starten"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
68
software/app/lib/ui/widgets/leader_info_dialog.dart
Normal file
68
software/app/lib/ui/widgets/leader_info_dialog.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../models/device_model.dart';
|
||||
import '../../models/device_config_model.dart';
|
||||
import '../../providers/device_provider.dart';
|
||||
|
||||
class LeaderInfoDialog extends StatelessWidget {
|
||||
final LasertagDevice device;
|
||||
const LeaderInfoDialog({super.key, required this.device});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Wir holen uns die Daten direkt aus dem Cache des Providers
|
||||
final activeConfig = context.read<DeviceProvider>().activeConfig;
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text("Leader Konfiguration"),
|
||||
content: activeConfig == null
|
||||
? const Text("Keine Konfigurationsdaten im Cache gefunden.")
|
||||
: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildInfoRow("Game ID", "0x${activeConfig.gameId.toRadixString(16).toUpperCase()}"),
|
||||
_buildInfoRow("Status", _stateToString(activeConfig.systemState)),
|
||||
const Divider(),
|
||||
const Text("Thread Mesh", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
_buildInfoRow("Netzwerk", activeConfig.networkName),
|
||||
_buildInfoRow("Kanal", activeConfig.channel.toString()),
|
||||
_buildInfoRow("PAN ID", "0x${activeConfig.panId.toRadixString(16).toUpperCase()}"),
|
||||
_buildInfoRow("Ext PAN", activeConfig.extPanId),
|
||||
const Divider(),
|
||||
const Text("Security Key", style: TextStyle(fontSize: 10, color: Colors.grey)),
|
||||
SelectableText(
|
||||
activeConfig.networkKey,
|
||||
style: const TextStyle(fontFamily: 'monospace', fontSize: 10)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text("Schließen"),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: const TextStyle(color: Colors.grey)),
|
||||
Text(value, style: const TextStyle(fontFamily: 'monospace', fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _stateToString(int state) {
|
||||
const states = {1: "IDLE", 2: "LOBBY", 3: "STARTING", 4: "RUNNING", 5: "POST_GAME"};
|
||||
return states[state] ?? "Unbekannt ($state)";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user