mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 08:20:36 +00:00
Bump iOS project version
This commit is contained in:
@@ -489,7 +489,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 112;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
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 = 112;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
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 = 112;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
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 = 112;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
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 = 112;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
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 = 112;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>112</string>
|
||||
<string>113</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
|
||||
@@ -5,22 +5,22 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.00026">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000231">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.640279">
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.67336">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="104.207919">
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="113.383916">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="3: upload_to_app_store" time="184.441335">
|
||||
<testcase classname="fastlane.lanes" name="3: upload_to_app_store" time="221.37558">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -59,6 +59,24 @@ class _DirectMessageRouteSession {
|
||||
/// Main App Provider - coordinates all other providers
|
||||
class AppProvider with ChangeNotifier {
|
||||
static const int _maxDirectPayloadHops = 3;
|
||||
@visibleForTesting
|
||||
static bool isDeletedChannelInfo(
|
||||
int channelIdx,
|
||||
String channelName,
|
||||
Uint8List secret,
|
||||
) {
|
||||
return channelIdx != 0 &&
|
||||
channelName.isEmpty &&
|
||||
secret.every((byte) => byte == 0);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static String channelContactName(int channelIdx, String channelName) {
|
||||
return channelName.isEmpty && channelIdx != 0
|
||||
? 'Channel $channelIdx'
|
||||
: channelName;
|
||||
}
|
||||
|
||||
final ConnectionProvider connectionProvider;
|
||||
final ContactsProvider contactsProvider;
|
||||
final MessagesProvider messagesProvider;
|
||||
@@ -736,8 +754,20 @@ class AppProvider with ChangeNotifier {
|
||||
'🔔 [AppProvider] onChannelInfoReceived called: idx=$channelIdx, name="$channelName"',
|
||||
);
|
||||
|
||||
// Check if this is a channel deletion (empty name)
|
||||
if (channelName.isEmpty && channelIdx != 0) {
|
||||
final isDeletedChannel = AppProvider.isDeletedChannelInfo(
|
||||
channelIdx,
|
||||
channelName,
|
||||
secret,
|
||||
);
|
||||
final contactChannelName = AppProvider.channelContactName(
|
||||
channelIdx,
|
||||
channelName,
|
||||
);
|
||||
|
||||
// Only treat the slot as deleted when firmware returns an empty
|
||||
// name and a zeroed secret. Protected channels may still have an
|
||||
// empty display name but remain fully configured.
|
||||
if (isDeletedChannel) {
|
||||
debugPrint(
|
||||
' 🗑️ Channel $channelIdx deleted - removing from providers',
|
||||
);
|
||||
@@ -772,10 +802,10 @@ class AppProvider with ChangeNotifier {
|
||||
// Also add as Contact to ContactsProvider (for UI display)
|
||||
// Skip if it's public channel (already exists)
|
||||
debugPrint(
|
||||
'📻 [AppProvider] Channel $channelIdx: "$channelName" (isEmpty: ${channelName.isEmpty}, isHashChannel: ${channelName.startsWith('#')})',
|
||||
'📻 [AppProvider] Channel $channelIdx: "$contactChannelName" (rawNameEmpty: ${channelName.isEmpty}, isHashChannel: ${channelName.startsWith('#')})',
|
||||
);
|
||||
|
||||
if (channelName.isNotEmpty && channelIdx != 0) {
|
||||
if (channelIdx != 0) {
|
||||
debugPrint(
|
||||
' ✅ Adding channel $channelIdx to ContactsProvider as Contact',
|
||||
);
|
||||
@@ -795,7 +825,7 @@ class AppProvider with ChangeNotifier {
|
||||
flags: flags ?? 0,
|
||||
outPathLen: -1, // Flood mode for channels
|
||||
outPath: Uint8List(0), // Empty path for channels
|
||||
advName: channelName,
|
||||
advName: contactChannelName,
|
||||
lastAdvert: now,
|
||||
advLat: 0, // Channels don't have location
|
||||
advLon: 0,
|
||||
@@ -809,7 +839,7 @@ class AppProvider with ChangeNotifier {
|
||||
);
|
||||
} else {
|
||||
debugPrint(
|
||||
' ⏭️ Skipping channel $channelIdx (empty: ${channelName.isEmpty}, isPublic: ${channelIdx == 0})',
|
||||
' ⏭️ Skipping channel $channelIdx (isPublic: ${channelIdx == 0})',
|
||||
);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
|
||||
@@ -825,6 +825,20 @@ class ConnectionProvider with ChangeNotifier {
|
||||
/// This should be set by AppProvider to query ChannelsProvider
|
||||
Function(int channelIdx)? getChannelInfo;
|
||||
|
||||
@visibleForTesting
|
||||
static bool channelHasConfiguredSecret(Uint8List secret) {
|
||||
return secret.any((byte) => byte != 0);
|
||||
}
|
||||
|
||||
bool _channelSlotIsOccupied(Object channel) {
|
||||
final channelName = (channel as dynamic).name as String?;
|
||||
final secret = (channel as dynamic).secret;
|
||||
final hasConfiguredSecret =
|
||||
secret is Uint8List && channelHasConfiguredSecret(secret);
|
||||
return (channelName != null && channelName.isNotEmpty) ||
|
||||
hasConfiguredSecret;
|
||||
}
|
||||
|
||||
/// Check if a specific channel slot is empty
|
||||
Future<bool> isChannelSlotEmpty(int channelIdx) async {
|
||||
if (!_activeService.isConnected) {
|
||||
@@ -836,8 +850,7 @@ class ConnectionProvider with ChangeNotifier {
|
||||
if (getChannelInfo != null) {
|
||||
final channel = getChannelInfo!(channelIdx);
|
||||
if (channel != null) {
|
||||
final channelName = (channel as dynamic).name as String?;
|
||||
return channelName == null || channelName.isEmpty;
|
||||
return !_channelSlotIsOccupied(channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -849,8 +862,7 @@ class ConnectionProvider with ChangeNotifier {
|
||||
if (getChannelInfo != null) {
|
||||
final channel = getChannelInfo!(channelIdx);
|
||||
if (channel != null) {
|
||||
final channelName = (channel as dynamic).name as String?;
|
||||
return channelName == null || channelName.isEmpty;
|
||||
return !_channelSlotIsOccupied(channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -900,8 +912,7 @@ class ConnectionProvider with ChangeNotifier {
|
||||
continue;
|
||||
}
|
||||
|
||||
final channelName = (channel as dynamic).name as String?;
|
||||
if (channelName != null && channelName.isNotEmpty) {
|
||||
if (_channelSlotIsOccupied(channel)) {
|
||||
usedIndices.add(i);
|
||||
}
|
||||
}
|
||||
@@ -923,12 +934,13 @@ class ConnectionProvider with ChangeNotifier {
|
||||
// First check cache
|
||||
if (getChannelInfo != null) {
|
||||
final channel = getChannelInfo!(i);
|
||||
if (channel != null) {
|
||||
if (channel != null && _channelSlotIsOccupied(channel)) {
|
||||
final channelName = (channel as dynamic).name as String?;
|
||||
if (channelName != null && channelName.isNotEmpty) {
|
||||
debugPrint(' ⏭️ Slot $i occupied: "$channelName"');
|
||||
continue; // Skip occupied slots
|
||||
}
|
||||
final label = (channelName != null && channelName.isNotEmpty)
|
||||
? channelName
|
||||
: 'Channel $i';
|
||||
debugPrint(' ⏭️ Slot $i occupied: "$label"');
|
||||
continue; // Skip occupied slots
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -636,7 +636,23 @@ class MessagesProvider with ChangeNotifier {
|
||||
}
|
||||
final existingSender = existing.senderKeyShort ?? existing.senderName;
|
||||
final incomingSender = message.senderKeyShort ?? message.senderName;
|
||||
return existingSender == incomingSender;
|
||||
if (existingSender == incomingSender) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// When we send to a channel we add a local "sent" bubble immediately,
|
||||
// then firmware may later sync back the same message as a received
|
||||
// channel item with only the public sender handle. Only fold that replay
|
||||
// into the original bubble after LOG_RX_DATA has already confirmed it as
|
||||
// our own transmitted packet.
|
||||
if (existing.isSentMessage &&
|
||||
existing.echoCount > 0 &&
|
||||
!message.isSentMessage &&
|
||||
existing.senderTimestamp == message.senderTimestamp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -81,14 +81,13 @@ class _BidirectionalRefreshState extends State<BidirectionalRefresh> {
|
||||
try {
|
||||
await widget.onRefresh();
|
||||
} finally {
|
||||
if (!mounted) {
|
||||
return;
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isRefreshing = false;
|
||||
_topPullDistance = 0;
|
||||
_bottomPullDistance = 0;
|
||||
});
|
||||
}
|
||||
setState(() {
|
||||
_isRefreshing = false;
|
||||
_topPullDistance = 0;
|
||||
_bottomPullDistance = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- file_picker (0.0.1):
|
||||
- FlutterMacOS
|
||||
- file_selector_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- flutter_avif_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- flutter_blue_plus_darwin (0.0.2):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -19,10 +23,6 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- nsd_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- ObjectBox (4.4.1)
|
||||
- objectbox_flutter_libs (0.0.1):
|
||||
- FlutterMacOS
|
||||
- ObjectBox (= 4.4.1)
|
||||
- package_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- path_provider_foundation (0.0.1):
|
||||
@@ -35,6 +35,9 @@ PODS:
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
@@ -43,23 +46,21 @@ DEPENDENCIES:
|
||||
- codec2_flutter (from `Flutter/ephemeral/.symlinks/plugins/codec2_flutter/macos`)
|
||||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
|
||||
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
|
||||
- flutter_avif_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_avif_macos/macos`)
|
||||
- flutter_blue_plus_darwin (from `Flutter/ephemeral/.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
|
||||
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin`)
|
||||
- nsd_macos (from `Flutter/ephemeral/.symlinks/plugins/nsd_macos/macos`)
|
||||
- objectbox_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/objectbox_flutter_libs/macos`)
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`)
|
||||
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- ObjectBox
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
audioplayers_darwin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/darwin
|
||||
@@ -69,6 +70,10 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
||||
file_picker:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
|
||||
file_selector_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
|
||||
flutter_avif_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_avif_macos/macos
|
||||
flutter_blue_plus_darwin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_blue_plus_darwin/darwin
|
||||
flutter_local_notifications:
|
||||
@@ -79,8 +84,6 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin
|
||||
nsd_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/nsd_macos/macos
|
||||
objectbox_flutter_libs:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/objectbox_flutter_libs/macos
|
||||
package_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||
path_provider_foundation:
|
||||
@@ -91,28 +94,31 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||
shared_preferences_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||
sqflite_darwin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
audioplayers_darwin: 4f9ca89d92d3d21cec7ec580e78ca888e5fb68bd
|
||||
audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5
|
||||
codec2_flutter: 50d930e6a1a187f05891b89300c6ef589651bfbd
|
||||
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
||||
file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7
|
||||
flutter_avif_macos: 9ed61d67adfbd6964eccb59971020fb55c31fc11
|
||||
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
||||
flutter_local_notifications: 4bf37a31afde695b56091b4ae3e4d9c7a7e6cda0
|
||||
flutter_local_notifications: 1fc7ffb10a83d6a2eeeeddb152d43f1944b0aad0
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||
nsd_macos: a472240e770b92f6c6df1022403aa29c90d012e3
|
||||
ObjectBox: 7da4aceb5013d041bfafdbc6d744a26918b09757
|
||||
objectbox_flutter_libs: f51d18f6a4b5965c218843373b7dc5ed5e3a2008
|
||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||
record_macos: 7f227161b93c49e7e34fe681c5891c8622c8cc8b
|
||||
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
||||
|
||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||
PODFILE CHECKSUM: e3de8f2486e300492cc80a2fe0576e9d269ba22b
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -794,9 +794,11 @@ packages:
|
||||
meshcore_client:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../meshcore_client"
|
||||
relative: true
|
||||
source: path
|
||||
path: "."
|
||||
ref: fbe0803
|
||||
resolved-ref: fbe08032df6a76ebc8339cbcca794a95e976a514
|
||||
url: "https://github.com/dz0ny/meshcore_client.git"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
|
||||
@@ -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.0312.1+27
|
||||
version: 2026.0313.1+28
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
@@ -44,7 +44,7 @@ dependencies:
|
||||
meshcore_client:
|
||||
git:
|
||||
url: https://github.com/dz0ny/meshcore_client.git
|
||||
ref: main
|
||||
ref: "fbe0803"
|
||||
|
||||
# Codec2 ultra-low-bitrate speech codec (FFI plugin)
|
||||
codec2_flutter:
|
||||
@@ -139,9 +139,6 @@ dev_dependencies:
|
||||
fake_async: ^1.3.3
|
||||
|
||||
dependency_overrides:
|
||||
meshcore_client:
|
||||
path: ../meshcore_client
|
||||
|
||||
# path_provider_foundation 2.6.0 pulls in package:objective_c as a native
|
||||
# asset. That framework has been ending up archived with a macOS platform
|
||||
# slice and fails App Store validation for iOS uploads.
|
||||
|
||||
31
test/providers/app_provider_channel_info_test.dart
Normal file
31
test/providers/app_provider_channel_info_test.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meshcore_sar_app/providers/app_provider.dart';
|
||||
|
||||
void main() {
|
||||
group('AppProvider channel info handling', () {
|
||||
test('treats zeroed unnamed non-public channel as deleted', () {
|
||||
expect(
|
||||
AppProvider.isDeletedChannelInfo(2, '', Uint8List(16)),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps unnamed non-public channel when secret is configured', () {
|
||||
final secret = Uint8List.fromList([
|
||||
1,
|
||||
...List<int>.filled(15, 0),
|
||||
]);
|
||||
|
||||
expect(
|
||||
AppProvider.isDeletedChannelInfo(2, '', secret),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
AppProvider.channelContactName(2, ''),
|
||||
'Channel 2',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
21
test/providers/connection_provider_channel_slot_test.dart
Normal file
21
test/providers/connection_provider_channel_slot_test.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meshcore_sar_app/providers/connection_provider.dart';
|
||||
|
||||
void main() {
|
||||
group('ConnectionProvider channel slot occupancy', () {
|
||||
test('treats non-zero secret as configured', () {
|
||||
expect(
|
||||
ConnectionProvider.channelHasConfiguredSecret(Uint8List(16)),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
ConnectionProvider.channelHasConfiguredSecret(
|
||||
Uint8List.fromList([1, ...List<int>.filled(15, 0)]),
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -39,6 +39,46 @@ Message _buildDirectMessage(String id) {
|
||||
);
|
||||
}
|
||||
|
||||
Message _buildSentChannelMessage({
|
||||
required String id,
|
||||
required int senderTimestamp,
|
||||
String text = 'broadcast',
|
||||
int channelIdx = 0,
|
||||
}) {
|
||||
return Message(
|
||||
id: id,
|
||||
messageType: MessageType.channel,
|
||||
senderPublicKeyPrefix: Uint8List.fromList([0, 1, 2, 3, 4, 5]),
|
||||
channelIdx: channelIdx,
|
||||
pathLen: 0,
|
||||
textType: MessageTextType.plain,
|
||||
senderTimestamp: senderTimestamp,
|
||||
text: text,
|
||||
receivedAt: DateTime.now(),
|
||||
deliveryStatus: MessageDeliveryStatus.sending,
|
||||
);
|
||||
}
|
||||
|
||||
Message _buildReceivedChannelReplay({
|
||||
required String id,
|
||||
required int senderTimestamp,
|
||||
String text = 'broadcast',
|
||||
int channelIdx = 0,
|
||||
String senderName = 'Radio Alpha',
|
||||
}) {
|
||||
return Message(
|
||||
id: id,
|
||||
messageType: MessageType.channel,
|
||||
channelIdx: channelIdx,
|
||||
pathLen: 1,
|
||||
textType: MessageTextType.plain,
|
||||
senderTimestamp: senderTimestamp,
|
||||
text: text,
|
||||
senderName: senderName,
|
||||
receivedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
@@ -124,18 +164,7 @@ void main() {
|
||||
test('channel messages are marked sent immediately', () {
|
||||
final provider = MessagesProvider();
|
||||
provider.addSentMessage(
|
||||
Message(
|
||||
id: 'c1',
|
||||
messageType: MessageType.channel,
|
||||
senderPublicKeyPrefix: Uint8List.fromList([0, 1, 2, 3, 4, 5]),
|
||||
channelIdx: 0,
|
||||
pathLen: 0,
|
||||
textType: MessageTextType.plain,
|
||||
senderTimestamp: 1700000000,
|
||||
text: 'broadcast',
|
||||
receivedAt: DateTime.now(),
|
||||
deliveryStatus: MessageDeliveryStatus.sending,
|
||||
),
|
||||
_buildSentChannelMessage(id: 'c1', senderTimestamp: 1700000000),
|
||||
);
|
||||
|
||||
provider.markMessageSent('c1', 0, 0);
|
||||
@@ -146,6 +175,51 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'channel replay is deduped after raw echo detection confirms our send',
|
||||
() {
|
||||
final provider = MessagesProvider();
|
||||
provider.addSentMessage(
|
||||
_buildSentChannelMessage(id: 'c-echo', senderTimestamp: 1700000100),
|
||||
);
|
||||
provider.markMessageSent('c-echo', 0, 0);
|
||||
provider.handleMessageEcho('c-echo', 1, 12, -90);
|
||||
|
||||
provider.addMessage(
|
||||
_buildReceivedChannelReplay(
|
||||
id: 'c-echo-incoming',
|
||||
senderTimestamp: 1700000100,
|
||||
),
|
||||
);
|
||||
|
||||
expect(provider.messages, hasLength(1));
|
||||
expect(provider.messages.single.id, equals('c-echo'));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'channel replay is not deduped before raw echo detection confirms it',
|
||||
() {
|
||||
final provider = MessagesProvider();
|
||||
provider.addSentMessage(
|
||||
_buildSentChannelMessage(
|
||||
id: 'c-no-echo',
|
||||
senderTimestamp: 1700000200,
|
||||
),
|
||||
);
|
||||
provider.markMessageSent('c-no-echo', 0, 0);
|
||||
|
||||
provider.addMessage(
|
||||
_buildReceivedChannelReplay(
|
||||
id: 'c-no-echo-incoming',
|
||||
senderTimestamp: 1700000200,
|
||||
),
|
||||
);
|
||||
|
||||
expect(provider.messages, hasLength(2));
|
||||
},
|
||||
);
|
||||
|
||||
test('missing ACK schedules a delayed retransmission', () {
|
||||
fakeAsync((async) {
|
||||
final provider = MessagesProvider();
|
||||
|
||||
@@ -89,7 +89,7 @@ void main() {
|
||||
unreadCount: 0,
|
||||
unreadCountsByPublicKey: const {},
|
||||
showAllOption: false,
|
||||
onSelect: (_, __) {},
|
||||
onSelect: (selectedRecipient, draftMessage) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user