mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Compare commits
3 Commits
v2026.0323
...
v2026.0323
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba0f1fc141 | ||
|
|
28a9235168 | ||
|
|
3e3c9a34d5 |
@@ -489,7 +489,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 125;
|
||||
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 = 125;
|
||||
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 = 125;
|
||||
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 = 125;
|
||||
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 = 125;
|
||||
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 = 125;
|
||||
CURRENT_PROJECT_VERSION = 128;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>125</string>
|
||||
<string>128</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
|
||||
@@ -5,22 +5,22 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000216">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.00073">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="1.086406">
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.659743">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="96.919368">
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="105.547745">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="3: upload_to_app_store" time="563.579992">
|
||||
<testcase classname="fastlane.lanes" name="3: upload_to_app_store" time="252.935458">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -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<bool> 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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -664,6 +664,11 @@ class MessagesProvider with ChangeNotifier {
|
||||
return; // Skip duplicate
|
||||
}
|
||||
|
||||
final matchingSentReplayIndex = _findMatchingSentReplayIndex(finalMessage);
|
||||
if (matchingSentReplayIndex != -1) {
|
||||
_clearChannelSendWarning(_messages[matchingSentReplayIndex].id);
|
||||
}
|
||||
|
||||
_messages.add(finalMessage);
|
||||
if (contactLocationSnapshot != null) {
|
||||
_messageContactLocations[finalMessage.id] = contactLocationSnapshot;
|
||||
@@ -712,7 +717,8 @@ class MessagesProvider with ChangeNotifier {
|
||||
|
||||
for (int index = 0; index < _messages.length; index++) {
|
||||
final existing = _messages[index];
|
||||
if (!_matchesDuplicateScope(existing, message) ||
|
||||
if (existing.isSentMessage ||
|
||||
!_matchesDuplicateScope(existing, message) ||
|
||||
existing.text != message.text) {
|
||||
continue;
|
||||
}
|
||||
@@ -790,6 +796,27 @@ class MessagesProvider with ChangeNotifier {
|
||||
existingSenderName == incomingSenderName;
|
||||
}
|
||||
|
||||
int _findMatchingSentReplayIndex(Message message) {
|
||||
if (!message.isChannelMessage || message.isSentMessage) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int index = 0; index < _messages.length; index++) {
|
||||
final existing = _messages[index];
|
||||
if (!existing.isSentMessage ||
|
||||
!existing.isChannelMessage ||
|
||||
existing.text != message.text) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_matchesDuplicateScope(existing, message)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// Add multiple messages
|
||||
void addMessages(List<Message> messages) {
|
||||
int addedCount = 0;
|
||||
|
||||
@@ -341,7 +341,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Meters',
|
||||
helperText: 'Valid range: 1 to 1000 meters',
|
||||
helperText: 'Valid range: 10 to 1000 meters',
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
@@ -353,7 +353,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
onPressed: () {
|
||||
final parsed = double.tryParse(controller.text.trim());
|
||||
if (parsed == null) return;
|
||||
Navigator.pop(context, parsed.clamp(1.0, 1000.0));
|
||||
Navigator.pop(context, parsed.clamp(10.0, 1000.0));
|
||||
},
|
||||
child: const Text('Save'),
|
||||
),
|
||||
@@ -382,7 +382,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Seconds',
|
||||
helperText: 'Valid range: 5 to 60 seconds',
|
||||
helperText: 'Valid range: 10 to 31 seconds',
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
@@ -394,7 +394,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
onPressed: () {
|
||||
final parsed = int.tryParse(controller.text.trim());
|
||||
if (parsed == null) return;
|
||||
Navigator.pop(context, parsed.clamp(5, 60));
|
||||
Navigator.pop(context, parsed.clamp(10, 31));
|
||||
},
|
||||
child: const Text('Save'),
|
||||
),
|
||||
@@ -426,6 +426,23 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
return 'Channel $channelIdx unavailable';
|
||||
}
|
||||
|
||||
Future<void> _sendTestFastLocationUpdate() async {
|
||||
final appProvider = context.read<AppProvider>();
|
||||
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<void> _editFastLocationChannel() async {
|
||||
final channels =
|
||||
List<Contact>.from(context.read<ContactsProvider>().channels)
|
||||
@@ -1854,6 +1871,15 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
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),
|
||||
|
||||
@@ -144,11 +144,11 @@ class AppConfigSnapshotService {
|
||||
}
|
||||
if (section.fastLocationMovementThresholdMeters != null) {
|
||||
locationTracking.fastLocationMovementThresholdMeters =
|
||||
section.fastLocationMovementThresholdMeters!;
|
||||
section.fastLocationMovementThresholdMeters!.clamp(10.0, 1000.0);
|
||||
}
|
||||
if (section.fastLocationActiveCadenceSeconds != null) {
|
||||
locationTracking.fastLocationActiveCadenceSeconds =
|
||||
section.fastLocationActiveCadenceSeconds!;
|
||||
section.fastLocationActiveCadenceSeconds!.clamp(10, 31);
|
||||
}
|
||||
await locationTracking.saveSettings();
|
||||
await appProvider.reloadProfileScopedSettings();
|
||||
|
||||
@@ -20,6 +20,14 @@ import 'profiles_feature_service.dart';
|
||||
/// - MeshCore mesh network integration
|
||||
/// - Real-time position updates via callbacks
|
||||
class LocationTrackingService {
|
||||
static const double _defaultFastLocationMovementThresholdMeters = 10.0;
|
||||
static const double _minFastLocationMovementThresholdMeters = 10.0;
|
||||
static const int _defaultFastLocationActiveCadenceSeconds = 10;
|
||||
static const int _minFastLocationActiveCadenceSeconds = 10;
|
||||
static const int _maxFastLocationActiveCadenceSeconds = 31;
|
||||
static const Duration _fastLocationMinimumUpdateInterval = Duration(
|
||||
seconds: 31,
|
||||
);
|
||||
// ============================================================================
|
||||
// Singleton Pattern
|
||||
// ============================================================================
|
||||
@@ -75,10 +83,12 @@ class LocationTrackingService {
|
||||
bool fastLocationUpdatesEnabled = false;
|
||||
|
||||
/// Distance threshold for fast GPS updates
|
||||
double fastLocationMovementThresholdMeters = 10.0;
|
||||
double fastLocationMovementThresholdMeters =
|
||||
_defaultFastLocationMovementThresholdMeters;
|
||||
|
||||
/// Cadence for active-use fast GPS updates
|
||||
int fastLocationActiveCadenceSeconds = 10;
|
||||
int fastLocationActiveCadenceSeconds =
|
||||
_defaultFastLocationActiveCadenceSeconds;
|
||||
|
||||
/// Target channel index for fast GPS updates; null means disabled/unset.
|
||||
int? fastLocationChannelIdx;
|
||||
@@ -522,12 +532,18 @@ class LocationTrackingService {
|
||||
}
|
||||
|
||||
Future<void> updateFastLocationMovementThreshold(double meters) async {
|
||||
fastLocationMovementThresholdMeters = meters.clamp(1.0, 1000.0);
|
||||
fastLocationMovementThresholdMeters = meters.clamp(
|
||||
_minFastLocationMovementThresholdMeters,
|
||||
1000.0,
|
||||
);
|
||||
await saveSettings();
|
||||
}
|
||||
|
||||
Future<void> updateFastLocationActiveCadenceSeconds(int seconds) async {
|
||||
fastLocationActiveCadenceSeconds = seconds.clamp(5, 60);
|
||||
fastLocationActiveCadenceSeconds = seconds.clamp(
|
||||
_minFastLocationActiveCadenceSeconds,
|
||||
_maxFastLocationActiveCadenceSeconds,
|
||||
);
|
||||
await saveSettings();
|
||||
_refreshFastLocationTimer();
|
||||
}
|
||||
@@ -583,6 +599,11 @@ class LocationTrackingService {
|
||||
final now = DateTime.now();
|
||||
final previous = _lastFastLocationSentPosition;
|
||||
final previousTime = _lastFastLocationSentAt;
|
||||
if (previousTime != null &&
|
||||
now.difference(previousTime) < _fastLocationMinimumUpdateInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (previous != null && previousTime != null) {
|
||||
final distance = Geolocator.distanceBetween(
|
||||
previous.latitude,
|
||||
@@ -673,12 +694,14 @@ class LocationTrackingService {
|
||||
prefs.getBool(_scopedKey(_prefKeyFastLocationEnabled)) ?? false;
|
||||
fastLocationMovementThresholdMeters =
|
||||
(prefs.getDouble(_scopedKey(_prefKeyFastMovementThreshold)) ??
|
||||
gpsUpdateDistance)
|
||||
.clamp(1.0, 1000.0);
|
||||
_defaultFastLocationMovementThresholdMeters)
|
||||
.clamp(_minFastLocationMovementThresholdMeters, 1000.0);
|
||||
fastLocationActiveCadenceSeconds =
|
||||
(prefs.getInt(_scopedKey(_prefKeyFastActiveCadence)) ?? 10).clamp(
|
||||
5,
|
||||
60,
|
||||
(prefs.getInt(_scopedKey(_prefKeyFastActiveCadence)) ??
|
||||
_defaultFastLocationActiveCadenceSeconds)
|
||||
.clamp(
|
||||
_minFastLocationActiveCadenceSeconds,
|
||||
_maxFastLocationActiveCadenceSeconds,
|
||||
);
|
||||
fastLocationChannelIdx = prefs.getInt(_scopedKey(_prefKeyFastChannelIdx));
|
||||
|
||||
|
||||
@@ -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.0322.2+39
|
||||
version: 2026.0323.3+42
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
|
||||
@@ -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', () {
|
||||
|
||||
@@ -214,7 +214,7 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
test('channel warning clears when replay is deduped into sent bubble', () {
|
||||
test('channel warning clears when replay arrives after send', () {
|
||||
fakeAsync((async) {
|
||||
final provider = MessagesProvider();
|
||||
provider.resolveContactNameCallback = (_) => 'dz0ny (SI)';
|
||||
@@ -239,11 +239,11 @@ void main() {
|
||||
);
|
||||
|
||||
expect(provider.hasChannelSendWarning('c-warn-replay'), isFalse);
|
||||
expect(provider.messages, hasLength(1));
|
||||
expect(provider.messages, hasLength(2));
|
||||
});
|
||||
});
|
||||
|
||||
test('channel replay is deduped for self sender within repeat window', () {
|
||||
test('channel replay is kept separate for self sender within repeat window', () {
|
||||
final provider = MessagesProvider();
|
||||
provider.resolveContactNameCallback = (_) => 'dz0ny (SI)';
|
||||
provider.addSentMessage(
|
||||
@@ -259,9 +259,10 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(provider.messages, hasLength(1));
|
||||
expect(provider.messages.single.id, equals('c-echo'));
|
||||
expect(provider.messages.single.senderName, equals('dz0ny (SI)'));
|
||||
expect(provider.messages, hasLength(2));
|
||||
expect(provider.messages.first.id, equals('c-echo'));
|
||||
expect(provider.messages.last.id, equals('c-echo-incoming'));
|
||||
expect(provider.messages.last.senderName, equals('dz0ny (SI)'));
|
||||
});
|
||||
|
||||
test(
|
||||
@@ -308,7 +309,7 @@ void main() {
|
||||
expect(provider.messages, hasLength(2));
|
||||
});
|
||||
|
||||
test('channel replay can dedupe using lazily resolved self name', () {
|
||||
test('channel replay stays separate using lazily resolved self name', () {
|
||||
final provider = MessagesProvider();
|
||||
provider.addSentMessage(
|
||||
_buildSentChannelMessage(id: 'c-lazy', senderTimestamp: 1700000400),
|
||||
@@ -324,11 +325,12 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(provider.messages, hasLength(1));
|
||||
expect(provider.messages.single.id, equals('c-lazy'));
|
||||
expect(provider.messages, hasLength(2));
|
||||
expect(provider.messages.first.id, equals('c-lazy'));
|
||||
expect(provider.messages.last.id, equals('c-lazy-incoming'));
|
||||
});
|
||||
|
||||
test('channel replay dedupes meshcore-prefixed self sender name', () {
|
||||
test('channel replay stays separate for meshcore-prefixed self sender name', () {
|
||||
final provider = MessagesProvider();
|
||||
provider.resolveContactNameCallback = (_) => 'MeshCore-dz0ny (SI)';
|
||||
provider.addSentMessage(
|
||||
@@ -344,8 +346,9 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(provider.messages, hasLength(1));
|
||||
expect(provider.messages.single.id, equals('c-prefix'));
|
||||
expect(provider.messages, hasLength(2));
|
||||
expect(provider.messages.first.id, equals('c-prefix'));
|
||||
expect(provider.messages.last.id, equals('c-prefix-incoming'));
|
||||
});
|
||||
|
||||
test('duplicate incoming message increments received copy count', () {
|
||||
|
||||
@@ -19,6 +19,13 @@ void main() {
|
||||
service.fastLocationChannelIdx = null;
|
||||
});
|
||||
|
||||
test('loads conservative fast location defaults', () async {
|
||||
await service.loadSettings();
|
||||
|
||||
expect(service.fastLocationMovementThresholdMeters, 10.0);
|
||||
expect(service.fastLocationActiveCadenceSeconds, 10);
|
||||
});
|
||||
|
||||
test('persists and restores fast location channel idx', () async {
|
||||
await service.updateFastLocationChannelIdx(3);
|
||||
|
||||
@@ -37,4 +44,12 @@ void main() {
|
||||
|
||||
expect(service.fastLocationChannelIdx, isNull);
|
||||
});
|
||||
|
||||
test('clamps fast location settings to conservative limits', () async {
|
||||
await service.updateFastLocationMovementThreshold(3);
|
||||
await service.updateFastLocationActiveCadenceSeconds(45);
|
||||
|
||||
expect(service.fastLocationMovementThresholdMeters, 10.0);
|
||||
expect(service.fastLocationActiveCadenceSeconds, 31);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user