From 29f7a2c444d60fa155451d7e7f65cf637a1b7f41 Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Wed, 25 Mar 2026 17:22:28 +0100
Subject: [PATCH] fix: M1 M2 M6 M7 L3 L5 - subnet batching, scroll debounce, error logging, typing timeout, remove unused deps
---
windows/flutter/generated_plugins.cmake | 1
windows/flutter/generated_plugin_registrant.cc | 3 +
lib/providers/providers.dart | 4 +
ios/Podfile.lock | 19 ++----
macos/Flutter/GeneratedPluginRegistrant.swift | 6 -
TODO-appstore.md | 12 ++--
lib/services/mqtt_service.dart | 29 +++++----
pubspec.lock | 48 ----------------
lib/screens/chat_screen.dart | 20 +++++-
pubspec.yaml | 2
10 files changed, 55 insertions(+), 89 deletions(-)
diff --git a/TODO-appstore.md b/TODO-appstore.md
index 4f73b46..252f1aa 100644
--- a/TODO-appstore.md
+++ b/TODO-appstore.md
@@ -20,21 +20,21 @@
## MEDIUM (Improve before submission)
-- [ ] **M1: Subnet scan hammers 254 hosts** — Could trigger IDS. Detect subnet mask or cap range
-- [ ] **M2: No loadMore debounce** — Scroll pagination fires repeatedly on iOS bounce. Add isLoading guard
+- [x] **M1: Subnet scan hammers 254 hosts** — Batched in groups of 20 with early exit *(fixed 2026-03-25)*
+- [x] **M2: No loadMore debounce** — Added isLoadingMore guard *(fixed 2026-03-25)*
- [ ] **M3: NavigateNotifier global singleton** — Mutable static, stale reference risk. Move to Riverpod provider
- [ ] **M4: Unbounded _seenSeqs set** — O(n log n) eviction. Use FIFO deque instead
- [ ] **M5: Screenshots bloat iCloud backup** — Store images as files, not base64 in JSON. Or exclude from backup
-- [ ] **M6: Unused import** — `server_config.dart` import in chat_screen.dart suppressed with ignore comment
-- [ ] **M7: Silent error swallowing** — ServerConfig _load() catches all exceptions silently. Add debugPrint
+- [x] **M6: Unused import** — Already removed *(fixed 2026-03-25)*
+- [x] **M7: Silent error swallowing** — Added debugPrint on config load failure *(fixed 2026-03-25)*
## LOW (Nice-to-haves)
- [ ] **L1: PrivacyInfo.xcprivacy** — Required since 2024 for UserDefaults and FileTimestamp APIs
- [ ] **L2: Privacy policy URL** — Required for microphone/camera access apps
-- [ ] **L3: Unused dependencies** — Remove `web_socket_channel` and `wakelock_plus` from pubspec.yaml
+- [x] **L3: Unused dependencies** — Removed web_socket_channel and wakelock_plus *(fixed 2026-03-25)*
- [x] **L4: Unnecessary _http._tcp** — Removed from NSBonjourServices *(fixed 2026-03-25)*
-- [ ] **L5: Typing indicator timeout** — Can get stuck if typing_end missed during background. Auto-clear after 10s
+- [x] **L5: Typing indicator timeout** — Auto-clear after 10s *(fixed 2026-03-25)*
- [ ] **L6: Version number** — Default 1.0.0+1, set correctly before submission
- [ ] **L7: App icon** — Verify meets Apple guidelines (no alpha channel, correct sizes)
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index b24639a..65029b7 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -2,6 +2,9 @@
- audioplayers_darwin (0.0.1):
- Flutter
- FlutterMacOS
+ - bonsoir_darwin (0.0.1):
+ - Flutter
+ - FlutterMacOS
- device_info_plus (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.9):
@@ -43,8 +46,6 @@
- Flutter
- image_picker_ios (0.0.1):
- Flutter
- - package_info_plus (0.4.5):
- - Flutter
- permission_handler_apple (9.3.0):
- Flutter
- record_ios (1.2.0):
@@ -60,23 +61,20 @@
- SwiftyGif (5.4.5)
- vibration (1.7.5):
- Flutter
- - wakelock_plus (0.0.1):
- - Flutter
DEPENDENCIES:
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
+ - bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- record_ios (from `.symlinks/plugins/record_ios/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- vibration (from `.symlinks/plugins/vibration/ios`)
- - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS:
trunk:
@@ -88,6 +86,8 @@
EXTERNAL SOURCES:
audioplayers_darwin:
:path: ".symlinks/plugins/audioplayers_darwin/darwin"
+ bonsoir_darwin:
+ :path: ".symlinks/plugins/bonsoir_darwin/darwin"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
@@ -98,8 +98,6 @@
:path: ".symlinks/plugins/flutter_secure_storage/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
- package_info_plus:
- :path: ".symlinks/plugins/package_info_plus/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
record_ios:
@@ -110,11 +108,10 @@
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
vibration:
:path: ".symlinks/plugins/vibration/ios"
- wakelock_plus:
- :path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5
+ bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
@@ -122,7 +119,6 @@
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
- package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
record_ios: 412daca2350b228e698fffcd08f1f94ceb1e3844
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
@@ -130,7 +126,6 @@
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
vibration: 8e2f50fc35bb736f9eecb7dd9f7047fbb6a6e888
- wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart
index fce1edf..7254e3f 100644
--- a/lib/providers/providers.dart
+++ b/lib/providers/providers.dart
@@ -39,7 +39,9 @@
if (json != null) {
state = ServerConfig.fromJson(jsonDecode(json) as Map<String, dynamic>);
}
- } catch (_) {}
+ } catch (e) {
+ debugPrint('ServerConfig load failed: $e');
+ }
}
Future<void> save(ServerConfig config) async {
diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart
index 3c03072..5017f31 100644
--- a/lib/screens/chat_screen.dart
+++ b/lib/screens/chat_screen.dart
@@ -1,3 +1,4 @@
+import 'dart:async';
import 'dart:convert';
import 'dart:io';
@@ -63,6 +64,7 @@
final List<Map<String, dynamic>> _pendingMessages = [];
final Map<String, List<Message>> _catchUpPending = {};
List<String>? _cachedSessionOrder;
+ Timer? _typingTimer;
@override
void initState() {
@@ -132,10 +134,13 @@
}
}
+ bool _isLoadingMore = false;
void _onScroll() {
- if (_scrollController.position.pixels >=
- _scrollController.position.maxScrollExtent - 100) {
- ref.read(messagesProvider.notifier).loadMore();
+ if (!_isLoadingMore &&
+ _scrollController.position.pixels >=
+ _scrollController.position.maxScrollExtent - 100) {
+ _isLoadingMore = true;
+ ref.read(messagesProvider.notifier).loadMore().then((_) => _isLoadingMore = false);
}
}
@@ -247,6 +252,15 @@
// Strict: only show typing for the ACTIVE session, ignore all others
if (activeId != null && typingSession == activeId) {
ref.read(isTypingProvider.notifier).state = typing;
+ // Auto-clear after 10s in case typing_end is missed
+ if (typing) {
+ _typingTimer?.cancel();
+ _typingTimer = Timer(const Duration(seconds: 10), () {
+ if (mounted) ref.read(isTypingProvider.notifier).state = false;
+ });
+ } else {
+ _typingTimer?.cancel();
+ }
}
case 'typing_end':
final endSession = msg['sessionId'] as String?;
diff --git a/lib/services/mqtt_service.dart b/lib/services/mqtt_service.dart
index f0c6d21..7625e7b 100644
--- a/lib/services/mqtt_service.dart
+++ b/lib/services/mqtt_service.dart
@@ -228,19 +228,22 @@
final subnet = '${parts[0]}.${parts[1]}.${parts[2]}';
_mqttLog('MQTT: scanning $subnet.0/24 on ${iface.name}');
- // Probe all hosts in parallel — 1s timeout each, runs concurrently
- final futures = <Future<String?>>[];
- for (int i = 1; i <= 254; i++) {
- final probe = '$subnet.$i';
- if (probe == addr.address) continue; // skip self
- futures.add(_probeHost(probe, config.port));
- }
-
- final results = await Future.wait(futures);
- final found = results.firstWhere((r) => r != null, orElse: () => null);
- if (found != null) {
- _mqttLog('MQTT: subnet scan found broker at $found');
- return found;
+ // Probe in batches of 20 to avoid flooding the network.
+ // Early exit on first hit.
+ for (int batch = 1; batch <= 254; batch += 20) {
+ final end = (batch + 19).clamp(1, 254);
+ final futures = <Future<String?>>[];
+ for (int i = batch; i <= end; i++) {
+ final probe = '$subnet.$i';
+ if (probe == addr.address) continue;
+ futures.add(_probeHost(probe, config.port));
+ }
+ final results = await Future.wait(futures);
+ final found = results.firstWhere((r) => r != null, orElse: () => null);
+ if (found != null) {
+ _mqttLog('MQTT: subnet scan found broker at $found');
+ return found;
+ }
}
}
}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 67a6ae9..2bcd467 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -6,25 +6,23 @@
import Foundation
import audioplayers_darwin
+import bonsoir_darwin
import device_info_plus
import file_picker
import file_selector_macos
import flutter_secure_storage_macos
-import package_info_plus
import record_macos
import share_plus
import shared_preferences_foundation
-import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
+ SwiftBonsoirPlugin.register(with: registry.registrar(forPlugin: "SwiftBonsoirPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
- FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
- WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
}
diff --git a/pubspec.lock b/pubspec.lock
index 911513c..28a7a73 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -608,22 +608,6 @@
url: "https://pub.dev"
source: hosted
version: "9.3.0"
- package_info_plus:
- dependency: transitive
- description:
- name: package_info_plus
- sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d
- url: "https://pub.dev"
- source: hosted
- version: "9.0.0"
- package_info_plus_platform_interface:
- dependency: transitive
- description:
- name: package_info_plus_platform_interface
- sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
- url: "https://pub.dev"
- source: hosted
- version: "3.2.1"
path:
dependency: transitive
description:
@@ -1069,22 +1053,6 @@
url: "https://pub.dev"
source: hosted
version: "15.0.2"
- wakelock_plus:
- dependency: "direct main"
- description:
- name: wakelock_plus
- sha256: "8b12256f616346910c519a35606fb69b1fe0737c06b6a447c6df43888b097f39"
- url: "https://pub.dev"
- source: hosted
- version: "1.5.1"
- wakelock_plus_platform_interface:
- dependency: transitive
- description:
- name: wakelock_plus_platform_interface
- sha256: "24b84143787220a403491c2e5de0877fbbb87baf3f0b18a2a988973863db4b03"
- url: "https://pub.dev"
- source: hosted
- version: "1.4.0"
web:
dependency: transitive
description:
@@ -1093,22 +1061,6 @@
url: "https://pub.dev"
source: hosted
version: "1.1.1"
- web_socket:
- dependency: transitive
- description:
- name: web_socket
- sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
- url: "https://pub.dev"
- source: hosted
- version: "1.0.1"
- web_socket_channel:
- dependency: "direct main"
- description:
- name: web_socket_channel
- sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
- url: "https://pub.dev"
- source: hosted
- version: "3.0.3"
win32:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 10bdb1b..621f726 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -13,7 +13,6 @@
flutter_riverpod: ^2.6.1
riverpod_annotation: ^2.6.1
go_router: ^14.8.1
- web_socket_channel: ^3.0.2
path_provider: ^2.1.0
shared_preferences: ^2.5.3
record: ^6.2.0
@@ -21,7 +20,6 @@
permission_handler: ^11.4.0
image_picker: ^1.1.2
flutter_secure_storage: ^9.2.4
- wakelock_plus: ^1.2.8
vibration: ^2.0.1
share_plus: ^12.0.1
udp: ^5.0.3
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index 94dc16a..5b79aa2 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -7,6 +7,7 @@
#include "generated_plugin_registrant.h"
#include <audioplayers_windows/audioplayers_windows_plugin.h>
+#include <bonsoir_windows/bonsoir_windows_plugin_c_api.h>
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
@@ -17,6 +18,8 @@
void RegisterPlugins(flutter::PluginRegistry* registry) {
AudioplayersWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
+ BonsoirWindowsPluginCApiRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("BonsoirWindowsPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index c6f03dd..2d751af 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows
+ bonsoir_windows
file_selector_windows
flutter_secure_storage_windows
permission_handler_windows
--
Gitblit v1.3.1