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', () {