Matthias Nott
2026-03-25 29f7a2c444d60fa155451d7e7f65cf637a1b7f41
fix: M1 M2 M6 M7 L3 L5 - subnet batching, scroll debounce, error logging, typing timeout, remove unused deps
10 files modified
changed files
TODO-appstore.md patch | view | blame | history
ios/Podfile.lock patch | view | blame | history
lib/providers/providers.dart patch | view | blame | history
lib/screens/chat_screen.dart patch | view | blame | history
lib/services/mqtt_service.dart patch | view | blame | history
macos/Flutter/GeneratedPluginRegistrant.swift patch | view | blame | history
pubspec.lock patch | view | blame | history
pubspec.yaml patch | view | blame | history
windows/flutter/generated_plugin_registrant.cc patch | view | blame | history
windows/flutter/generated_plugins.cmake patch | view | blame | history
TODO-appstore.md
....@@ -20,21 +20,21 @@
2020
2121 ## MEDIUM (Improve before submission)
2222
23
-- [ ] **M1: Subnet scan hammers 254 hosts** — Could trigger IDS. Detect subnet mask or cap range
24
-- [ ] **M2: No loadMore debounce** — Scroll pagination fires repeatedly on iOS bounce. Add isLoading guard
23
+- [x] **M1: Subnet scan hammers 254 hosts** — Batched in groups of 20 with early exit *(fixed 2026-03-25)*
24
+- [x] **M2: No loadMore debounce** — Added isLoadingMore guard *(fixed 2026-03-25)*
2525 - [ ] **M3: NavigateNotifier global singleton** — Mutable static, stale reference risk. Move to Riverpod provider
2626 - [ ] **M4: Unbounded _seenSeqs set** — O(n log n) eviction. Use FIFO deque instead
2727 - [ ] **M5: Screenshots bloat iCloud backup** — Store images as files, not base64 in JSON. Or exclude from backup
28
-- [ ] **M6: Unused import** — `server_config.dart` import in chat_screen.dart suppressed with ignore comment
29
-- [ ] **M7: Silent error swallowing** — ServerConfig _load() catches all exceptions silently. Add debugPrint
28
+- [x] **M6: Unused import** — Already removed *(fixed 2026-03-25)*
29
+- [x] **M7: Silent error swallowing** — Added debugPrint on config load failure *(fixed 2026-03-25)*
3030
3131 ## LOW (Nice-to-haves)
3232
3333 - [ ] **L1: PrivacyInfo.xcprivacy** — Required since 2024 for UserDefaults and FileTimestamp APIs
3434 - [ ] **L2: Privacy policy URL** — Required for microphone/camera access apps
35
-- [ ] **L3: Unused dependencies** — Remove `web_socket_channel` and `wakelock_plus` from pubspec.yaml
35
+- [x] **L3: Unused dependencies** — Removed web_socket_channel and wakelock_plus *(fixed 2026-03-25)*
3636 - [x] **L4: Unnecessary _http._tcp** — Removed from NSBonjourServices *(fixed 2026-03-25)*
37
-- [ ] **L5: Typing indicator timeout** — Can get stuck if typing_end missed during background. Auto-clear after 10s
37
+- [x] **L5: Typing indicator timeout** — Auto-clear after 10s *(fixed 2026-03-25)*
3838 - [ ] **L6: Version number** — Default 1.0.0+1, set correctly before submission
3939 - [ ] **L7: App icon** — Verify meets Apple guidelines (no alpha channel, correct sizes)
4040
ios/Podfile.lock
....@@ -2,6 +2,9 @@
22 - audioplayers_darwin (0.0.1):
33 - Flutter
44 - FlutterMacOS
5
+ - bonsoir_darwin (0.0.1):
6
+ - Flutter
7
+ - FlutterMacOS
58 - device_info_plus (0.0.1):
69 - Flutter
710 - DKImagePickerController/Core (4.3.9):
....@@ -43,8 +46,6 @@
4346 - Flutter
4447 - image_picker_ios (0.0.1):
4548 - Flutter
46
- - package_info_plus (0.4.5):
47
- - Flutter
4849 - permission_handler_apple (9.3.0):
4950 - Flutter
5051 - record_ios (1.2.0):
....@@ -60,23 +61,20 @@
6061 - SwiftyGif (5.4.5)
6162 - vibration (1.7.5):
6263 - Flutter
63
- - wakelock_plus (0.0.1):
64
- - Flutter
6564
6665 DEPENDENCIES:
6766 - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
67
+ - bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
6868 - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
6969 - file_picker (from `.symlinks/plugins/file_picker/ios`)
7070 - Flutter (from `Flutter`)
7171 - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
7272 - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
73
- - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
7473 - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
7574 - record_ios (from `.symlinks/plugins/record_ios/ios`)
7675 - share_plus (from `.symlinks/plugins/share_plus/ios`)
7776 - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
7877 - vibration (from `.symlinks/plugins/vibration/ios`)
79
- - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
8078
8179 SPEC REPOS:
8280 trunk:
....@@ -88,6 +86,8 @@
8886 EXTERNAL SOURCES:
8987 audioplayers_darwin:
9088 :path: ".symlinks/plugins/audioplayers_darwin/darwin"
89
+ bonsoir_darwin:
90
+ :path: ".symlinks/plugins/bonsoir_darwin/darwin"
9191 device_info_plus:
9292 :path: ".symlinks/plugins/device_info_plus/ios"
9393 file_picker:
....@@ -98,8 +98,6 @@
9898 :path: ".symlinks/plugins/flutter_secure_storage/ios"
9999 image_picker_ios:
100100 :path: ".symlinks/plugins/image_picker_ios/ios"
101
- package_info_plus:
102
- :path: ".symlinks/plugins/package_info_plus/ios"
103101 permission_handler_apple:
104102 :path: ".symlinks/plugins/permission_handler_apple/ios"
105103 record_ios:
....@@ -110,11 +108,10 @@
110108 :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
111109 vibration:
112110 :path: ".symlinks/plugins/vibration/ios"
113
- wakelock_plus:
114
- :path: ".symlinks/plugins/wakelock_plus/ios"
115111
116112 SPEC CHECKSUMS:
117113 audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5
114
+ bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
118115 device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
119116 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
120117 DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
....@@ -122,7 +119,6 @@
122119 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
123120 flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
124121 image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
125
- package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
126122 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
127123 record_ios: 412daca2350b228e698fffcd08f1f94ceb1e3844
128124 SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
....@@ -130,7 +126,6 @@
130126 shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
131127 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
132128 vibration: 8e2f50fc35bb736f9eecb7dd9f7047fbb6a6e888
133
- wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
134129
135130 PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
136131
lib/providers/providers.dart
....@@ -39,7 +39,9 @@
3939 if (json != null) {
4040 state = ServerConfig.fromJson(jsonDecode(json) as Map<String, dynamic>);
4141 }
42
- } catch (_) {}
42
+ } catch (e) {
43
+ debugPrint('ServerConfig load failed: $e');
44
+ }
4345 }
4446
4547 Future<void> save(ServerConfig config) async {
lib/screens/chat_screen.dart
....@@ -1,3 +1,4 @@
1
+import 'dart:async';
12 import 'dart:convert';
23 import 'dart:io';
34
....@@ -63,6 +64,7 @@
6364 final List<Map<String, dynamic>> _pendingMessages = [];
6465 final Map<String, List<Message>> _catchUpPending = {};
6566 List<String>? _cachedSessionOrder;
67
+ Timer? _typingTimer;
6668
6769 @override
6870 void initState() {
....@@ -132,10 +134,13 @@
132134 }
133135 }
134136
137
+ bool _isLoadingMore = false;
135138 void _onScroll() {
136
- if (_scrollController.position.pixels >=
137
- _scrollController.position.maxScrollExtent - 100) {
138
- ref.read(messagesProvider.notifier).loadMore();
139
+ if (!_isLoadingMore &&
140
+ _scrollController.position.pixels >=
141
+ _scrollController.position.maxScrollExtent - 100) {
142
+ _isLoadingMore = true;
143
+ ref.read(messagesProvider.notifier).loadMore().then((_) => _isLoadingMore = false);
139144 }
140145 }
141146
....@@ -247,6 +252,15 @@
247252 // Strict: only show typing for the ACTIVE session, ignore all others
248253 if (activeId != null && typingSession == activeId) {
249254 ref.read(isTypingProvider.notifier).state = typing;
255
+ // Auto-clear after 10s in case typing_end is missed
256
+ if (typing) {
257
+ _typingTimer?.cancel();
258
+ _typingTimer = Timer(const Duration(seconds: 10), () {
259
+ if (mounted) ref.read(isTypingProvider.notifier).state = false;
260
+ });
261
+ } else {
262
+ _typingTimer?.cancel();
263
+ }
250264 }
251265 case 'typing_end':
252266 final endSession = msg['sessionId'] as String?;
lib/services/mqtt_service.dart
....@@ -228,19 +228,22 @@
228228 final subnet = '${parts[0]}.${parts[1]}.${parts[2]}';
229229 _mqttLog('MQTT: scanning $subnet.0/24 on ${iface.name}');
230230
231
- // Probe all hosts in parallel — 1s timeout each, runs concurrently
232
- final futures = <Future<String?>>[];
233
- for (int i = 1; i <= 254; i++) {
234
- final probe = '$subnet.$i';
235
- if (probe == addr.address) continue; // skip self
236
- futures.add(_probeHost(probe, config.port));
237
- }
238
-
239
- final results = await Future.wait(futures);
240
- final found = results.firstWhere((r) => r != null, orElse: () => null);
241
- if (found != null) {
242
- _mqttLog('MQTT: subnet scan found broker at $found');
243
- return found;
231
+ // Probe in batches of 20 to avoid flooding the network.
232
+ // Early exit on first hit.
233
+ for (int batch = 1; batch <= 254; batch += 20) {
234
+ final end = (batch + 19).clamp(1, 254);
235
+ final futures = <Future<String?>>[];
236
+ for (int i = batch; i <= end; i++) {
237
+ final probe = '$subnet.$i';
238
+ if (probe == addr.address) continue;
239
+ futures.add(_probeHost(probe, config.port));
240
+ }
241
+ final results = await Future.wait(futures);
242
+ final found = results.firstWhere((r) => r != null, orElse: () => null);
243
+ if (found != null) {
244
+ _mqttLog('MQTT: subnet scan found broker at $found');
245
+ return found;
246
+ }
244247 }
245248 }
246249 }
macos/Flutter/GeneratedPluginRegistrant.swift
....@@ -6,25 +6,23 @@
66 import Foundation
77
88 import audioplayers_darwin
9
+import bonsoir_darwin
910 import device_info_plus
1011 import file_picker
1112 import file_selector_macos
1213 import flutter_secure_storage_macos
13
-import package_info_plus
1414 import record_macos
1515 import share_plus
1616 import shared_preferences_foundation
17
-import wakelock_plus
1817
1918 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
2019 AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
20
+ SwiftBonsoirPlugin.register(with: registry.registrar(forPlugin: "SwiftBonsoirPlugin"))
2121 DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
2222 FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
2323 FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
2424 FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
25
- FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
2625 RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
2726 SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
2827 SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
29
- WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
3028 }
pubspec.lock
....@@ -608,22 +608,6 @@
608608 url: "https://pub.dev"
609609 source: hosted
610610 version: "9.3.0"
611
- package_info_plus:
612
- dependency: transitive
613
- description:
614
- name: package_info_plus
615
- sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d
616
- url: "https://pub.dev"
617
- source: hosted
618
- version: "9.0.0"
619
- package_info_plus_platform_interface:
620
- dependency: transitive
621
- description:
622
- name: package_info_plus_platform_interface
623
- sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
624
- url: "https://pub.dev"
625
- source: hosted
626
- version: "3.2.1"
627611 path:
628612 dependency: transitive
629613 description:
....@@ -1069,22 +1053,6 @@
10691053 url: "https://pub.dev"
10701054 source: hosted
10711055 version: "15.0.2"
1072
- wakelock_plus:
1073
- dependency: "direct main"
1074
- description:
1075
- name: wakelock_plus
1076
- sha256: "8b12256f616346910c519a35606fb69b1fe0737c06b6a447c6df43888b097f39"
1077
- url: "https://pub.dev"
1078
- source: hosted
1079
- version: "1.5.1"
1080
- wakelock_plus_platform_interface:
1081
- dependency: transitive
1082
- description:
1083
- name: wakelock_plus_platform_interface
1084
- sha256: "24b84143787220a403491c2e5de0877fbbb87baf3f0b18a2a988973863db4b03"
1085
- url: "https://pub.dev"
1086
- source: hosted
1087
- version: "1.4.0"
10881056 web:
10891057 dependency: transitive
10901058 description:
....@@ -1093,22 +1061,6 @@
10931061 url: "https://pub.dev"
10941062 source: hosted
10951063 version: "1.1.1"
1096
- web_socket:
1097
- dependency: transitive
1098
- description:
1099
- name: web_socket
1100
- sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
1101
- url: "https://pub.dev"
1102
- source: hosted
1103
- version: "1.0.1"
1104
- web_socket_channel:
1105
- dependency: "direct main"
1106
- description:
1107
- name: web_socket_channel
1108
- sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
1109
- url: "https://pub.dev"
1110
- source: hosted
1111
- version: "3.0.3"
11121064 win32:
11131065 dependency: transitive
11141066 description:
pubspec.yaml
....@@ -13,7 +13,6 @@
1313 flutter_riverpod: ^2.6.1
1414 riverpod_annotation: ^2.6.1
1515 go_router: ^14.8.1
16
- web_socket_channel: ^3.0.2
1716 path_provider: ^2.1.0
1817 shared_preferences: ^2.5.3
1918 record: ^6.2.0
....@@ -21,7 +20,6 @@
2120 permission_handler: ^11.4.0
2221 image_picker: ^1.1.2
2322 flutter_secure_storage: ^9.2.4
24
- wakelock_plus: ^1.2.8
2523 vibration: ^2.0.1
2624 share_plus: ^12.0.1
2725 udp: ^5.0.3
windows/flutter/generated_plugin_registrant.cc
....@@ -7,6 +7,7 @@
77 #include "generated_plugin_registrant.h"
88
99 #include <audioplayers_windows/audioplayers_windows_plugin.h>
10
+#include <bonsoir_windows/bonsoir_windows_plugin_c_api.h>
1011 #include <file_selector_windows/file_selector_windows.h>
1112 #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
1213 #include <permission_handler_windows/permission_handler_windows_plugin.h>
....@@ -17,6 +18,8 @@
1718 void RegisterPlugins(flutter::PluginRegistry* registry) {
1819 AudioplayersWindowsPluginRegisterWithRegistrar(
1920 registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
21
+ BonsoirWindowsPluginCApiRegisterWithRegistrar(
22
+ registry->GetRegistrarForPlugin("BonsoirWindowsPluginCApi"));
2023 FileSelectorWindowsRegisterWithRegistrar(
2124 registry->GetRegistrarForPlugin("FileSelectorWindows"));
2225 FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
windows/flutter/generated_plugins.cmake
....@@ -4,6 +4,7 @@
44
55 list(APPEND FLUTTER_PLUGIN_LIST
66 audioplayers_windows
7
+ bonsoir_windows
78 file_selector_windows
89 flutter_secure_storage_windows
910 permission_handler_windows