Use backend rescan summary in group editor

This commit is contained in:
Artem Kokos
2026-05-16 10:59:31 +07:00
parent 70fedb6134
commit 83d946558b
3 changed files with 76 additions and 38 deletions

View File

@@ -80,39 +80,19 @@ class _GroupEditScreenState extends ConsumerState<GroupEditScreen> {
} }
Future<void> _rescan() async { Future<void> _rescan() async {
final beforeIds = ref
.read(devicesProvider)
.data
.map((device) => device.groupMemberId)
.toSet();
setState(() => _rescanning = true); setState(() => _rescanning = true);
try { try {
await ref.read(apiProvider).rescanNetwork(); final response = await ref.read(apiProvider).rescanNetwork();
var changed = false;
for (var attempt = 0; attempt < 6; attempt++) {
await Future.delayed(const Duration(seconds: 1));
await ref.read(devicesProvider.notifier).load(); await ref.read(devicesProvider.notifier).load();
final currentIds = ref final summary = response.data is Map
.read(devicesProvider) ? Map<String, dynamic>.from(response.data as Map)
.data : const <String, dynamic>{};
.map((device) => device.groupMemberId) final message = formatRescanSummary(summary);
.toSet();
if (!_sameSet(beforeIds, currentIds)) {
changed = true;
break;
}
}
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(message),
changed
? 'Список устройств обновился'
: 'Сканирование завершилось, но новых устройств пока не видно',
),
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
), ),
); );
@@ -427,17 +407,31 @@ class _GroupEditScreenState extends ConsumerState<GroupEditScreen> {
); );
} }
bool _sameSet(Set<String> left, Set<String> right) { }
if (left.length != right.length) {
return false; int _summaryInt(Map<String, dynamic> summary, String key) {
final value = summary[key];
if (value is int) {
return value;
} }
for (final item in left) { return int.tryParse(value?.toString() ?? '') ?? 0;
if (!right.contains(item)) { }
return false;
String formatRescanSummary(Map<String, dynamic> summary) {
final found = _summaryInt(summary, 'found');
final added = _summaryInt(summary, 'added');
final updated = _summaryInt(summary, 'updated');
final removed = _summaryInt(summary, 'removed_offline');
if (added == 0 && updated == 0 && removed == 0 && found == 0) {
return 'Сканирование завершено: устройства не найдены';
} }
if (added == 0 && removed == 0) {
return 'Сканирование завершено: найдено $found, обновлено $updated';
} }
return true;
} return 'Сканирование завершено: найдено $found, новых $added, обновлено $updated, убрано $removed';
} }
class _ConflictBanner extends StatelessWidget { class _ConflictBanner extends StatelessWidget {

View File

@@ -82,6 +82,39 @@ void main() {
expect(api.createdGroupMacs, ['AA:BB']); expect(api.createdGroupMacs, ['AA:BB']);
}); });
testWidgets('group edit screen shows backend rescan summary', (
tester,
) async {
final api = FakeIgnisApi(
devicesData: {
'devices': [
{'mac': 'AA:BB', 'name': 'Лампа 1'},
],
},
groupsData: <Object>[],
)..rescanNetworkData = {
'status': 'ok',
'found': 3,
'added': 1,
'updated': 2,
'removed_offline': 1,
'pending_removal': 0,
'online': 3,
};
await pumpTestApp(tester, child: const GroupEditScreen(), api: api);
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Пересканировать сеть'));
await tester.pumpAndSettle();
expect(
find.text('Сканирование завершено: найдено 3, новых 1, обновлено 2, убрано 1'),
findsOneWidget,
);
expect(api.rescanCalls, 1);
});
testWidgets('api keys screen validates and shows created key banner', ( testWidgets('api keys screen validates and shows created key banner', (
tester, tester,
) async { ) async {

View File

@@ -36,6 +36,7 @@ class FakeIgnisApi extends IgnisApi {
Object? eventLogData; Object? eventLogData;
Object? apiKeysData; Object? apiKeysData;
Object? authData; Object? authData;
Object? rescanNetworkData;
Object? devicesError; Object? devicesError;
Object? scenesError; Object? scenesError;
@@ -329,7 +330,17 @@ class FakeIgnisApi extends IgnisApi {
if (error != null) throw error; if (error != null) throw error;
return Response( return Response(
requestOptions: RequestOptions(path: '/devices/rescan'), requestOptions: RequestOptions(path: '/devices/rescan'),
data: <String, dynamic>{'ok': true}, data:
rescanNetworkData ??
<String, dynamic>{
'status': 'ok',
'found': 0,
'added': 0,
'updated': 0,
'removed_offline': 0,
'pending_removal': 0,
'online': 0,
},
); );
} }
} }