Compare commits
2 Commits
c13b6d73c9
...
65688b7b99
| Author | SHA1 | Date | |
|---|---|---|---|
| 65688b7b99 | |||
| 395d577b78 |
@@ -46,6 +46,7 @@ LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL);
|
||||
/* --- Global Variables --- */
|
||||
static uint8_t device_role = 0; // Store device type for provisioning
|
||||
static uint8_t adv_enabled = 0; // Track advertising state
|
||||
static struct k_work_delayable adv_restart_work;
|
||||
|
||||
/* --- GATT Callbacks --- */
|
||||
|
||||
@@ -105,11 +106,8 @@ static ssize_t write_lasertag_val(struct bt_conn *conn, const struct bt_gatt_att
|
||||
if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NAME_CHAR) == 0)
|
||||
{
|
||||
rc = lasertag_set_device_name(buf, len);
|
||||
if (rc == 0 && adv_enabled) {
|
||||
LOG_INF("Stopping advertising to update device name");
|
||||
ble_mgmt_adv_stop();
|
||||
LOG_INF("Restarting advertising with new device name");
|
||||
ble_mgmt_adv_start();
|
||||
if (rc == 0 ) {
|
||||
bt_set_name(lasertag_get_device_name());
|
||||
}
|
||||
}
|
||||
else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_PANID_CHAR) == 0)
|
||||
@@ -221,10 +219,24 @@ static const struct bt_data ad[] = {
|
||||
BT_DATA(BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof(mfg_data)),
|
||||
};
|
||||
|
||||
static void adv_restart_work_handler(struct k_work *work)
|
||||
{
|
||||
if (adv_enabled == 0)
|
||||
{
|
||||
int err = ble_mgmt_adv_start();
|
||||
if (err) {
|
||||
LOG_ERR("Fehler beim verzögerten Neustarten des Advertisings (err %d)", err);
|
||||
} else {
|
||||
LOG_INF("Advertising nach Verzögerung erfolgreich neu gestartet");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ble_mgmt_init(uint8_t device_type)
|
||||
{
|
||||
device_role = device_type;
|
||||
|
||||
k_work_init_delayable(&adv_restart_work, adv_restart_work_handler);
|
||||
|
||||
int err = bt_enable(NULL);
|
||||
if (err)
|
||||
return err;
|
||||
@@ -269,4 +281,25 @@ int ble_mgmt_adv_stop(void)
|
||||
adv_enabled = 0;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
static void connected(struct bt_conn *conn, uint8_t err)
|
||||
{
|
||||
if (err) {
|
||||
LOG_ERR("Verbindung fehlgeschlagen (err %u)", err);
|
||||
} else {
|
||||
LOG_INF("Host verbunden");
|
||||
adv_enabled = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void disconnected(struct bt_conn *conn, uint8_t reason)
|
||||
{
|
||||
LOG_INF("Verbindung getrennt (Grund %u)", reason);
|
||||
k_work_reschedule(&adv_restart_work, K_MSEC(100));
|
||||
}
|
||||
|
||||
BT_CONN_CB_DEFINE(conn_callbacks) = {
|
||||
.connected = connected,
|
||||
.disconnected = disconnected,
|
||||
};
|
||||
@@ -3,9 +3,9 @@
|
||||
"appTitle": "Lasertag Mission Control",
|
||||
"selectLeader": "Leader auswählen",
|
||||
"searching": "Suche nach Knoten...",
|
||||
"listTypeLeader": "Gameleadergeräte",
|
||||
"listTypeLeader": "Game LEader",
|
||||
"typeWeapon": "Waffe",
|
||||
"typeVest": "Weste",
|
||||
"typeBeacon": "Beacon",
|
||||
"listTypeEquipment": "Ausrüstungsgeräte"
|
||||
"listTypeEquipment": "Sonstiges"
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ abstract class AppLocalizations {
|
||||
/// No description provided for @listTypeLeader.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Gameleadergeräte'**
|
||||
/// **'Game LEader'**
|
||||
String get listTypeLeader;
|
||||
|
||||
/// No description provided for @typeWeapon.
|
||||
@@ -139,7 +139,7 @@ abstract class AppLocalizations {
|
||||
/// No description provided for @listTypeEquipment.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Ausrüstungsgeräte'**
|
||||
/// **'Sonstiges'**
|
||||
String get listTypeEquipment;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get searching => 'Suche nach Knoten...';
|
||||
|
||||
@override
|
||||
String get listTypeLeader => 'Gameleadergeräte';
|
||||
String get listTypeLeader => 'Game LEader';
|
||||
|
||||
@override
|
||||
String get typeWeapon => 'Waffe';
|
||||
@@ -30,5 +30,5 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get typeBeacon => 'Beacon';
|
||||
|
||||
@override
|
||||
String get listTypeEquipment => 'Ausrüstungsgeräte';
|
||||
String get listTypeEquipment => 'Sonstiges';
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
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:flutter/material.dart';
|
||||
import 'package:lasertag_app/l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'providers/device_provider.dart';
|
||||
import 'ui/screens/device_selection_screen.dart';
|
||||
import 'providers/device_provider.dart'; // Neu
|
||||
|
||||
void main() {
|
||||
runApp(
|
||||
// Den Provider hier für die ganze App bereitstellen
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => DeviceProvider(),
|
||||
child: const LasertagApp(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
|
||||
import '../../constants.dart';
|
||||
|
||||
class LasertagDevice {
|
||||
@@ -7,6 +8,7 @@ class LasertagDevice {
|
||||
final int type;
|
||||
final bool isConnected;
|
||||
final BluetoothDevice btDevice;
|
||||
final bool isNameVerified;
|
||||
|
||||
LasertagDevice({
|
||||
required this.id,
|
||||
@@ -14,8 +16,19 @@ class LasertagDevice {
|
||||
required this.type,
|
||||
required this.btDevice,
|
||||
this.isConnected = false,
|
||||
this.isNameVerified = false,
|
||||
});
|
||||
|
||||
// Hilfsmethode: Ist es ein Leader?
|
||||
bool get isLeader => type == LasertagUUIDs.typeLeader;
|
||||
|
||||
LasertagDevice copyWith({String? name, bool? isNameVerified}) {
|
||||
return LasertagDevice(
|
||||
id: id,
|
||||
type: type,
|
||||
btDevice: btDevice,
|
||||
name: name ?? this.name,
|
||||
isNameVerified: isNameVerified ?? this.isNameVerified,
|
||||
isConnected: isConnected,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
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';
|
||||
import '../models/device_model.dart';
|
||||
|
||||
class DeviceProvider extends ChangeNotifier {
|
||||
final List<LasertagDevice> _discoveredLeaders = [];
|
||||
@@ -13,6 +15,49 @@ class DeviceProvider extends ChangeNotifier {
|
||||
List<LasertagDevice> get leaders => _discoveredLeaders;
|
||||
List<LasertagDevice> get peripherals => _discoveredPeripherals;
|
||||
|
||||
// Öffentlicher API-Entry: Scan starten
|
||||
void startScan() async {
|
||||
_discoveredLeaders.clear();
|
||||
_discoveredPeripherals.clear();
|
||||
notifyListeners();
|
||||
|
||||
await FlutterBluePlus.stopScan(); // Scan sauber stoppen vor Neustart
|
||||
|
||||
await FlutterBluePlus.adapterState
|
||||
.where((s) => s == BluetoothAdapterState.on)
|
||||
.first;
|
||||
|
||||
_scanSubscription?.cancel();
|
||||
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
|
||||
for (ScanResult r in results) {
|
||||
bool hasService = r.advertisementData.serviceUuids.contains(Guid(LasertagUUIDs.provService));
|
||||
if (!hasService) continue;
|
||||
|
||||
final mfgData = r.advertisementData.manufacturerData[65535];
|
||||
|
||||
if (mfgData != null && mfgData.isNotEmpty) {
|
||||
final device = LasertagDevice(
|
||||
id: r.device.remoteId.toString(),
|
||||
name: r.device.platformName.isEmpty ? "Unbekannt" : r.device.platformName,
|
||||
type: mfgData[0],
|
||||
btDevice: r.device,
|
||||
isConnected: false,
|
||||
isNameVerified: false, // Initial immer unversichert (Kursiv)
|
||||
);
|
||||
|
||||
_addDeviceToLists(device);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
|
||||
} catch (e) {
|
||||
debugPrint("Scan-Fehler: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// Öffentlicher API-Entry: Namen von der Hardware lesen
|
||||
Future<String> readDeviceNameFromHardware(LasertagDevice ltDevice) async {
|
||||
try {
|
||||
if (!ltDevice.btDevice.isConnected) {
|
||||
@@ -22,21 +67,14 @@ class DeviceProvider extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
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));
|
||||
|
||||
// Namen von der Hardware lesen
|
||||
List<int> value = await characteristic.read();
|
||||
String hardwareName = utf8.decode(value);
|
||||
|
||||
// Lokalen Cache in der App ebenfalls aktualisieren, um Caching zu umgehen
|
||||
updateDeviceName(ltDevice.id, hardwareName);
|
||||
updateDeviceName(ltDevice.id, hardwareName, verified: true);
|
||||
|
||||
return hardwareName;
|
||||
} catch (e) {
|
||||
@@ -45,10 +83,8 @@ class DeviceProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateDeviceNameOnHardware(
|
||||
LasertagDevice ltDevice,
|
||||
String newName,
|
||||
) async {
|
||||
// Öffentlicher API-Entry: Namen auf Hardware schreiben
|
||||
Future<void> updateDeviceNameOnHardware(LasertagDevice ltDevice, String newName) async {
|
||||
try {
|
||||
await ltDevice.btDevice.connect(
|
||||
license: License.free,
|
||||
@@ -58,21 +94,13 @@ class DeviceProvider extends ChangeNotifier {
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
List<BluetoothService> services = await ltDevice.btDevice
|
||||
.discoverServices();
|
||||
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));
|
||||
|
||||
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);
|
||||
updateDeviceName(ltDevice.id, newName, verified: true);
|
||||
|
||||
await ltDevice.btDevice.disconnect();
|
||||
} catch (e) {
|
||||
@@ -81,98 +109,51 @@ class DeviceProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
_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) {
|
||||
bool hasService = r.advertisementData.serviceUuids.contains(
|
||||
Guid(LasertagUUIDs.provService),
|
||||
);
|
||||
if (!hasService) continue;
|
||||
|
||||
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,
|
||||
btDevice: r.device,
|
||||
isConnected: false,
|
||||
);
|
||||
|
||||
_addDeviceToLists(device);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Bluetooth-Scan starten
|
||||
try {
|
||||
await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
|
||||
} catch (e) {
|
||||
debugPrint("Scan-Fehler: $e");
|
||||
}
|
||||
// Öffentlicher API-Entry: Lokale Liste aktualisieren
|
||||
void updateDeviceName(String id, String newName, {bool verified = false}) {
|
||||
_updateDeviceInLists(id, (old) => old.copyWith(name: newName, isNameVerified: verified));
|
||||
}
|
||||
|
||||
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);
|
||||
List<LasertagDevice> list = device.isLeader ? _discoveredLeaders : _discoveredPeripherals;
|
||||
|
||||
if (!list.any((d) => d.id == device.id)) {
|
||||
list.add(device);
|
||||
_sortList(list);
|
||||
notifyListeners();
|
||||
_verifyNameInBackground(device); // Hintergrund-Check anstoßen
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _verifyNameInBackground(LasertagDevice device) async {
|
||||
if (device.isNameVerified) return;
|
||||
|
||||
try {
|
||||
await device.btDevice.connect(license: License.free, timeout: const Duration(seconds: 5));
|
||||
|
||||
List<BluetoothService> services = await device.btDevice.discoverServices();
|
||||
var service = services.firstWhere((s) => s.uuid == Guid(LasertagUUIDs.provService));
|
||||
var characteristic = service.characteristics.firstWhere((c) => c.uuid == Guid(LasertagUUIDs.provNameChar));
|
||||
|
||||
List<int> value = await characteristic.read();
|
||||
String realName = utf8.decode(value);
|
||||
|
||||
updateDeviceName(device.id, realName, verified: true);
|
||||
|
||||
await device.btDevice.disconnect();
|
||||
} catch (e) {
|
||||
debugPrint("Background Sync fehlgeschlagen für ${device.id}: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void _updateDeviceInLists(String id, LasertagDevice Function(LasertagDevice) updateFn) {
|
||||
for (var list in [_discoveredLeaders, _discoveredPeripherals]) {
|
||||
int index = list.indexWhere((d) => d.id == id);
|
||||
if (index != -1) {
|
||||
list[index] = updateFn(list[index]);
|
||||
_sortList(list);
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,4 +171,4 @@ class DeviceProvider extends ChangeNotifier {
|
||||
_scanSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:lasertag_app/l10n/app_localizations.dart';
|
||||
|
||||
import '../../constants.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 {
|
||||
const DeviceSelectionScreen({super.key});
|
||||
@@ -29,7 +30,15 @@ class _DeviceSelectionScreenState extends State<DeviceSelectionScreen> {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(l10n.appTitle)),
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.appTitle),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => context.read<DeviceProvider>().startScan(),
|
||||
icon: Icon(Icons.refresh),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
_buildHeader(context, l10n.listTypeLeader),
|
||||
@@ -74,16 +83,24 @@ class _DeviceSelectionScreenState extends State<DeviceSelectionScreen> {
|
||||
),
|
||||
title: Text(
|
||||
device.name,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
// fontStyle: device.isNameVerified
|
||||
// ? FontStyle.normal
|
||||
// : FontStyle.italic, // Kursiv, wenn nicht verifiziert
|
||||
color: device.isNameVerified
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
subtitle: Text("BTLE-ID: ${device.id}"),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => RenameDialog(device: device),
|
||||
);
|
||||
},
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => RenameDialog(device: device),
|
||||
);
|
||||
},
|
||||
);
|
||||
}, childCount: devices.length),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../models/device_model.dart';
|
||||
import '../../providers/device_provider.dart';
|
||||
|
||||
@@ -20,7 +21,12 @@ class _RenameDialogState extends State<RenameDialog> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TextEditingController(text: widget.device.name);
|
||||
_fetchHardwareName(); // Namen beim Öffnen abfragen
|
||||
if (!widget.device.isNameVerified) {
|
||||
// Wenn der Name bereits verifiziert ist, nicht von der Hardware lesen
|
||||
_isReading = false;
|
||||
} else {
|
||||
_fetchHardwareName(); // Namen beim Öffnen abfragen
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchHardwareName() async {
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:lasertag_app/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user