TODO-appstore.md
.. .. @@ -20,21 +20,21 @@ 20 20 21 21 ## MEDIUM (Improve before submission) 22 22 23 -- [ ] **M1: Subnet scan hammers 254 hosts** — Could trigger IDS. Detect subnet mask or cap range24 -- [ ] **M2: No loadMore debounce** — Scroll pagination fires repeatedly on iOS bounce. Add isLoading guard23 +- [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)*25 25 - [ ] **M3: NavigateNotifier global singleton** — Mutable static, stale reference risk. Move to Riverpod provider 26 26 - [ ] **M4: Unbounded _seenSeqs set** — O(n log n) eviction. Use FIFO deque instead 27 27 - [ ] **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 comment29 -- [ ] **M7: Silent error swallowing** — ServerConfig _load() catches all exceptions silently. Add debugPrint28 +- [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)*30 30 31 31 ## LOW (Nice-to-haves) 32 32 33 33 - [ ] **L1: PrivacyInfo.xcprivacy** — Required since 2024 for UserDefaults and FileTimestamp APIs 34 34 - [ ] **L2: Privacy policy URL** — Required for microphone/camera access apps 35 -- [ ] **L3: Unused dependencies** — Remove `web_socket_channel` and `wakelock_plus` from pubspec.yaml35 +- [x] **L3: Unused dependencies** — Removed web_socket_channel and wakelock_plus *(fixed 2026-03-25)*36 36 - [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 10s37 +- [x] **L5: Typing indicator timeout** — Auto-clear after 10s *(fixed 2026-03-25)*38 38 - [ ] **L6: Version number** — Default 1.0.0+1, set correctly before submission 39 39 - [ ] **L7: App icon** — Verify meets Apple guidelines (no alpha channel, correct sizes) 40 40 ios/Podfile.lock
.. .. @@ -2,6 +2,9 @@ 2 2 - audioplayers_darwin (0.0.1): 3 3 - Flutter 4 4 - FlutterMacOS 5 + - bonsoir_darwin (0.0.1):6 + - Flutter7 + - FlutterMacOS5 8 - device_info_plus (0.0.1): 6 9 - Flutter 7 10 - DKImagePickerController/Core (4.3.9): .. .. @@ -43,8 +46,6 @@ 43 46 - Flutter 44 47 - image_picker_ios (0.0.1): 45 48 - Flutter 46 - - package_info_plus (0.4.5):47 - - Flutter48 49 - permission_handler_apple (9.3.0): 49 50 - Flutter 50 51 - record_ios (1.2.0): .. .. @@ -60,23 +61,20 @@ 60 61 - SwiftyGif (5.4.5) 61 62 - vibration (1.7.5): 62 63 - Flutter 63 - - wakelock_plus (0.0.1):64 - - Flutter65 64 66 65 DEPENDENCIES: 67 66 - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`) 67 + - bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)68 68 - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) 69 69 - file_picker (from `.symlinks/plugins/file_picker/ios`) 70 70 - Flutter (from `Flutter`) 71 71 - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) 72 72 - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) 73 - - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)74 73 - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) 75 74 - record_ios (from `.symlinks/plugins/record_ios/ios`) 76 75 - share_plus (from `.symlinks/plugins/share_plus/ios`) 77 76 - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) 78 77 - vibration (from `.symlinks/plugins/vibration/ios`) 79 - - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)80 78 81 79 SPEC REPOS: 82 80 trunk: .. .. @@ -88,6 +86,8 @@ 88 86 EXTERNAL SOURCES: 89 87 audioplayers_darwin: 90 88 :path: ".symlinks/plugins/audioplayers_darwin/darwin" 89 + bonsoir_darwin:90 + :path: ".symlinks/plugins/bonsoir_darwin/darwin"91 91 device_info_plus: 92 92 :path: ".symlinks/plugins/device_info_plus/ios" 93 93 file_picker: .. .. @@ -98,8 +98,6 @@ 98 98 :path: ".symlinks/plugins/flutter_secure_storage/ios" 99 99 image_picker_ios: 100 100 :path: ".symlinks/plugins/image_picker_ios/ios" 101 - package_info_plus:102 - :path: ".symlinks/plugins/package_info_plus/ios"103 101 permission_handler_apple: 104 102 :path: ".symlinks/plugins/permission_handler_apple/ios" 105 103 record_ios: .. .. @@ -110,11 +108,10 @@ 110 108 :path: ".symlinks/plugins/shared_preferences_foundation/darwin" 111 109 vibration: 112 110 :path: ".symlinks/plugins/vibration/ios" 113 - wakelock_plus:114 - :path: ".symlinks/plugins/wakelock_plus/ios"115 111 116 112 SPEC CHECKSUMS: 117 113 audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5 114 + bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e118 115 device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe 119 116 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c 120 117 DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 .. .. @@ -122,7 +119,6 @@ 122 119 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 123 120 flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 124 121 image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 125 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499126 122 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d 127 123 record_ios: 412daca2350b228e698fffcd08f1f94ceb1e3844 128 124 SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf .. .. @@ -130,7 +126,6 @@ 130 126 shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb 131 127 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 132 128 vibration: 8e2f50fc35bb736f9eecb7dd9f7047fbb6a6e888 133 - wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556134 129 135 130 PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e 136 131 lib/providers/providers.dart
.. .. @@ -39,7 +39,9 @@ 39 39 if (json != null) { 40 40 state = ServerConfig.fromJson(jsonDecode(json) as Map<String, dynamic>); 41 41 } 42 - } catch (_) {}42 + } catch (e) {43 + debugPrint('ServerConfig load failed: $e');44 + }43 45 } 44 46 45 47 Future<void> save(ServerConfig config) async { lib/screens/chat_screen.dart
.. .. @@ -1,3 +1,4 @@ 1 +import 'dart:async';1 2 import 'dart:convert'; 2 3 import 'dart:io'; 3 4 .. .. @@ -63,6 +64,7 @@ 63 64 final List<Map<String, dynamic>> _pendingMessages = []; 64 65 final Map<String, List<Message>> _catchUpPending = {}; 65 66 List<String>? _cachedSessionOrder; 67 + Timer? _typingTimer;66 68 67 69 @override 68 70 void initState() { .. .. @@ -132,10 +134,13 @@ 132 134 } 133 135 } 134 136 137 + bool _isLoadingMore = false;135 138 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);139 144 } 140 145 } 141 146 .. .. @@ -247,6 +252,15 @@ 247 252 // Strict: only show typing for the ACTIVE session, ignore all others 248 253 if (activeId != null && typingSession == activeId) { 249 254 ref.read(isTypingProvider.notifier).state = typing; 255 + // Auto-clear after 10s in case typing_end is missed256 + 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 + }250 264 } 251 265 case 'typing_end': 252 266 final endSession = msg['sessionId'] as String?; lib/services/mqtt_service.dart
.. .. @@ -228,19 +228,22 @@ 228 228 final subnet = '${parts[0]}.${parts[1]}.${parts[2]}'; 229 229 _mqttLog('MQTT: scanning $subnet.0/24 on ${iface.name}'); 230 230 231 - // Probe all hosts in parallel — 1s timeout each, runs concurrently232 - final futures = <Future<String?>>[];233 - for (int i = 1; i <= 254; i++) {234 - final probe = '$subnet.$i';235 - if (probe == addr.address) continue; // skip self236 - 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 + }244 247 } 245 248 } 246 249 } macos/Flutter/GeneratedPluginRegistrant.swift
.. .. @@ -6,25 +6,23 @@ 6 6 import Foundation 7 7 8 8 import audioplayers_darwin 9 +import bonsoir_darwin9 10 import device_info_plus 10 11 import file_picker 11 12 import file_selector_macos 12 13 import flutter_secure_storage_macos 13 -import package_info_plus14 14 import record_macos 15 15 import share_plus 16 16 import shared_preferences_foundation 17 -import wakelock_plus18 17 19 18 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 20 19 AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) 20 + SwiftBonsoirPlugin.register(with: registry.registrar(forPlugin: "SwiftBonsoirPlugin"))21 21 DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) 22 22 FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) 23 23 FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 24 24 FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) 25 - FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))26 25 RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin")) 27 26 SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 28 27 SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 29 - WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))30 28 } pubspec.lock
.. .. @@ -608,22 +608,6 @@ 608 608 url: "https://pub.dev" 609 609 source: hosted 610 610 version: "9.3.0" 611 - package_info_plus:612 - dependency: transitive613 - description:614 - name: package_info_plus615 - sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d616 - url: "https://pub.dev"617 - source: hosted618 - version: "9.0.0"619 - package_info_plus_platform_interface:620 - dependency: transitive621 - description:622 - name: package_info_plus_platform_interface623 - sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"624 - url: "https://pub.dev"625 - source: hosted626 - version: "3.2.1"627 611 path: 628 612 dependency: transitive 629 613 description: .. .. @@ -1069,22 +1053,6 @@ 1069 1053 url: "https://pub.dev" 1070 1054 source: hosted 1071 1055 version: "15.0.2" 1072 - wakelock_plus:1073 - dependency: "direct main"1074 - description:1075 - name: wakelock_plus1076 - sha256: "8b12256f616346910c519a35606fb69b1fe0737c06b6a447c6df43888b097f39"1077 - url: "https://pub.dev"1078 - source: hosted1079 - version: "1.5.1"1080 - wakelock_plus_platform_interface:1081 - dependency: transitive1082 - description:1083 - name: wakelock_plus_platform_interface1084 - sha256: "24b84143787220a403491c2e5de0877fbbb87baf3f0b18a2a988973863db4b03"1085 - url: "https://pub.dev"1086 - source: hosted1087 - version: "1.4.0"1088 1056 web: 1089 1057 dependency: transitive 1090 1058 description: .. .. @@ -1093,22 +1061,6 @@ 1093 1061 url: "https://pub.dev" 1094 1062 source: hosted 1095 1063 version: "1.1.1" 1096 - web_socket:1097 - dependency: transitive1098 - description:1099 - name: web_socket1100 - sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"1101 - url: "https://pub.dev"1102 - source: hosted1103 - version: "1.0.1"1104 - web_socket_channel:1105 - dependency: "direct main"1106 - description:1107 - name: web_socket_channel1108 - sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c81109 - url: "https://pub.dev"1110 - source: hosted1111 - version: "3.0.3"1112 1064 win32: 1113 1065 dependency: transitive 1114 1066 description: pubspec.yaml
.. .. @@ -13,7 +13,6 @@ 13 13 flutter_riverpod: ^2.6.1 14 14 riverpod_annotation: ^2.6.1 15 15 go_router: ^14.8.1 16 - web_socket_channel: ^3.0.217 16 path_provider: ^2.1.0 18 17 shared_preferences: ^2.5.3 19 18 record: ^6.2.0 .. .. @@ -21,7 +20,6 @@ 21 20 permission_handler: ^11.4.0 22 21 image_picker: ^1.1.2 23 22 flutter_secure_storage: ^9.2.4 24 - wakelock_plus: ^1.2.825 23 vibration: ^2.0.1 26 24 share_plus: ^12.0.1 27 25 udp: ^5.0.3 windows/flutter/generated_plugin_registrant.cc
.. .. @@ -7,6 +7,7 @@ 7 7 #include "generated_plugin_registrant.h" 8 8 9 9 #include <audioplayers_windows/audioplayers_windows_plugin.h> 10 +#include <bonsoir_windows/bonsoir_windows_plugin_c_api.h>10 11 #include <file_selector_windows/file_selector_windows.h> 11 12 #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> 12 13 #include <permission_handler_windows/permission_handler_windows_plugin.h> .. .. @@ -17,6 +18,8 @@ 17 18 void RegisterPlugins(flutter::PluginRegistry* registry) { 18 19 AudioplayersWindowsPluginRegisterWithRegistrar( 19 20 registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); 21 + BonsoirWindowsPluginCApiRegisterWithRegistrar(22 + registry->GetRegistrarForPlugin("BonsoirWindowsPluginCApi"));20 23 FileSelectorWindowsRegisterWithRegistrar( 21 24 registry->GetRegistrarForPlugin("FileSelectorWindows")); 22 25 FlutterSecureStorageWindowsPluginRegisterWithRegistrar( windows/flutter/generated_plugins.cmake
.. .. @@ -4,6 +4,7 @@ 4 4 5 5 list(APPEND FLUTTER_PLUGIN_LIST 6 6 audioplayers_windows 7 + bonsoir_windows7 8 file_selector_windows 8 9 flutter_secure_storage_windows 9 10 permission_handler_windows