diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 500be12..064c37d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -489,7 +489,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 126; + CURRENT_PROJECT_VERSION = 128; DEVELOPMENT_TEAM = JND55328G8; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -511,7 +511,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 126; + CURRENT_PROJECT_VERSION = 128; DEVELOPMENT_TEAM = JND55328G8; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; @@ -530,7 +530,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 126; + CURRENT_PROJECT_VERSION = 128; DEVELOPMENT_TEAM = JND55328G8; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; @@ -547,7 +547,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 126; + CURRENT_PROJECT_VERSION = 128; DEVELOPMENT_TEAM = JND55328G8; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; @@ -679,7 +679,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 126; + CURRENT_PROJECT_VERSION = 128; DEVELOPMENT_TEAM = JND55328G8; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -702,7 +702,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 126; + CURRENT_PROJECT_VERSION = 128; DEVELOPMENT_TEAM = JND55328G8; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index e7aadf3..c26b76f 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -43,7 +43,7 @@ CFBundleSignature ???? CFBundleVersion - 126 + 128 LSRequiresIPhoneOS ITSAppUsesNonExemptEncryption diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml index 3e48371..8dfaae6 100644 --- a/ios/fastlane/report.xml +++ b/ios/fastlane/report.xml @@ -5,22 +5,22 @@ - + - + - + - + diff --git a/lib/providers/app_provider.dart b/lib/providers/app_provider.dart index 4579d7f..cb6248a 100644 --- a/lib/providers/app_provider.dart +++ b/lib/providers/app_provider.dart @@ -2879,8 +2879,13 @@ class AppProvider with ChangeNotifier { timestampSeconds: DateTime.now().millisecondsSinceEpoch ~/ 1000, ); debugPrint( - '📍 [AppProvider] Sending fast GPS update ($reason): ' - '${position.latitude}, ${position.longitude} via channel $channelIdx', + '📤 [AppProvider] Fast GPS send ' + 'reason=$reason ' + 'sender=$senderKey6 ' + 'channel=$channelIdx ' + 'lat=${position.latitude} ' + 'lon=${position.longitude} ' + 'ts=${packet.timestampSeconds}', ); try { await connectionProvider.sendChannelData( @@ -2888,11 +2893,32 @@ class AppProvider with ChangeNotifier { dataType: MeshCoreConstants.dataTypeDev, payload: packet.encodeBinary(), ); + debugPrint( + '✅ [AppProvider] Fast GPS sent ' + 'sender=$senderKey6 channel=$channelIdx ts=${packet.timestampSeconds}', + ); } catch (e) { debugPrint('⚠️ [AppProvider] Fast GPS send failed: $e'); } } + Future sendTestFastLocationUpdate() async { + if (!connectionProvider.deviceInfo.isConnected) { + return false; + } + + final position = await locationTrackingService.getCurrentPosition( + timeLimit: const Duration(seconds: 10), + retryCount: 1, + ); + if (position == null) { + return false; + } + + await _sendFastLocationUpdate(position, reason: 'test'); + return true; + } + Contact? _resolveVoiceFetchRequester(VoiceFetchRequest request) { final liveContact = _resolveContactByPrefixHex(request.requesterKey6); if (liveContact != null) { diff --git a/lib/providers/contacts_provider.dart b/lib/providers/contacts_provider.dart index d6311c4..e200322 100644 --- a/lib/providers/contacts_provider.dart +++ b/lib/providers/contacts_provider.dart @@ -1143,13 +1143,34 @@ class ContactsProvider with ChangeNotifier { ), ); + debugPrint( + '📥 [ContactsProvider] Fast GPS received ' + 'sender=${packet.senderKey6} ' + 'contact=${contact.displayName} ' + 'contactKey=${contact.publicKeyHex} ' + 'lat=${packet.latitude} ' + 'lon=${packet.longitude} ' + 'ts=${packet.timestampSeconds}', + ); + final updatedContact = contact.copyWith( telemetry: updatedTelemetry, lastAdvert: packet.timestampSeconds, + lastMod: packet.timestampSeconds, advLat: _coordinateToAdvertMicrodegrees(packet.latitude), advLon: _coordinateToAdvertMicrodegrees(packet.longitude), ); _contacts[contact.publicKeyHex] = updatedContact; + _estimatedLocations[contact.publicKeyHex] = LatLng( + packet.latitude, + packet.longitude, + ); + debugPrint( + '✅ [ContactsProvider] Fast GPS applied ' + 'contact=${updatedContact.displayName} ' + 'contactKey=${updatedContact.publicKeyHex} ' + 'lastAdvert=${updatedContact.lastAdvert}', + ); _persistContacts(); notifyListeners(); } diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index f430e99..9a7992d 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -426,6 +426,23 @@ class _SettingsScreenState extends State { return 'Channel $channelIdx unavailable'; } + Future _sendTestFastLocationUpdate() async { + final appProvider = context.read(); + final sent = await appProvider.sendTestFastLocationUpdate(); + if (!mounted) return; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + sent + ? 'Test fast GPS update sent.' + : 'Unable to send test fast GPS update.', + ), + backgroundColor: sent ? Colors.green : Colors.orange, + ), + ); + } + Future _editFastLocationChannel() async { final channels = List.from(context.read().channels) @@ -1854,6 +1871,15 @@ class _SettingsScreenState extends State { trailing: const Icon(Icons.chevron_right), onTap: _editFastLocationChannel, ), + ListTile( + leading: const Icon(Icons.send), + title: const Text('Test send update'), + subtitle: const Text( + 'Send one fast GPS update immediately to the configured channel.', + ), + trailing: const Icon(Icons.chevron_right), + onTap: _sendTestFastLocationUpdate, + ), ListTile( leading: Icon(Icons.location_on), title: Text(AppLocalizations.of(context)!.locationPermission), diff --git a/pubspec.yaml b/pubspec.yaml index 212cd66..176e1e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2026.0323.2+41 +version: 2026.0323.3+42 environment: sdk: ^3.9.2 diff --git a/test/providers/contacts_provider_test.dart b/test/providers/contacts_provider_test.dart index c9bb1ad..475bc63 100644 --- a/test/providers/contacts_provider_test.dart +++ b/test/providers/contacts_provider_test.dart @@ -781,6 +781,19 @@ void main() { expect(updated.advLat, equals((44.123456 * 1e6).round())); expect(updated.advLon, equals((13.654321 * 1e6).round())); expect(updated.lastAdvert, equals(1700001234)); + expect(updated.lastMod, equals(1700001234)); + expect( + provider.estimatedLocationFor(updated.publicKeyHex), + isNotNull, + ); + expect( + provider.estimatedLocationFor(updated.publicKeyHex)!.latitude, + closeTo(44.123456, 0.000001), + ); + expect( + provider.estimatedLocationFor(updated.publicKeyHex)!.longitude, + closeTo(13.654321, 0.000001), + ); }); test('ignores unknown sender prefix safely', () {