mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Bump iOS project version
This commit is contained in:
@@ -489,7 +489,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 112;
|
CURRENT_PROJECT_VERSION = 113;
|
||||||
DEVELOPMENT_TEAM = JND55328G8;
|
DEVELOPMENT_TEAM = JND55328G8;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -511,7 +511,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 112;
|
CURRENT_PROJECT_VERSION = 113;
|
||||||
DEVELOPMENT_TEAM = JND55328G8;
|
DEVELOPMENT_TEAM = JND55328G8;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
@@ -530,7 +530,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 112;
|
CURRENT_PROJECT_VERSION = 113;
|
||||||
DEVELOPMENT_TEAM = JND55328G8;
|
DEVELOPMENT_TEAM = JND55328G8;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
@@ -547,7 +547,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 112;
|
CURRENT_PROJECT_VERSION = 113;
|
||||||
DEVELOPMENT_TEAM = JND55328G8;
|
DEVELOPMENT_TEAM = JND55328G8;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
@@ -679,7 +679,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 112;
|
CURRENT_PROJECT_VERSION = 113;
|
||||||
DEVELOPMENT_TEAM = JND55328G8;
|
DEVELOPMENT_TEAM = JND55328G8;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -702,7 +702,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 112;
|
CURRENT_PROJECT_VERSION = 113;
|
||||||
DEVELOPMENT_TEAM = JND55328G8;
|
DEVELOPMENT_TEAM = JND55328G8;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>112</string>
|
<string>113</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<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>
|
||||||
|
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="2: build_app" time="104.207919">
|
<testcase classname="fastlane.lanes" name="2: build_app" time="113.383916">
|
||||||
|
|
||||||
</testcase>
|
</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>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,24 @@ class _DirectMessageRouteSession {
|
|||||||
/// Main App Provider - coordinates all other providers
|
/// Main App Provider - coordinates all other providers
|
||||||
class AppProvider with ChangeNotifier {
|
class AppProvider with ChangeNotifier {
|
||||||
static const int _maxDirectPayloadHops = 3;
|
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 ConnectionProvider connectionProvider;
|
||||||
final ContactsProvider contactsProvider;
|
final ContactsProvider contactsProvider;
|
||||||
final MessagesProvider messagesProvider;
|
final MessagesProvider messagesProvider;
|
||||||
@@ -736,8 +754,20 @@ class AppProvider with ChangeNotifier {
|
|||||||
'🔔 [AppProvider] onChannelInfoReceived called: idx=$channelIdx, name="$channelName"',
|
'🔔 [AppProvider] onChannelInfoReceived called: idx=$channelIdx, name="$channelName"',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if this is a channel deletion (empty name)
|
final isDeletedChannel = AppProvider.isDeletedChannelInfo(
|
||||||
if (channelName.isEmpty && channelIdx != 0) {
|
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(
|
debugPrint(
|
||||||
' 🗑️ Channel $channelIdx deleted - removing from providers',
|
' 🗑️ Channel $channelIdx deleted - removing from providers',
|
||||||
);
|
);
|
||||||
@@ -772,10 +802,10 @@ class AppProvider with ChangeNotifier {
|
|||||||
// Also add as Contact to ContactsProvider (for UI display)
|
// Also add as Contact to ContactsProvider (for UI display)
|
||||||
// Skip if it's public channel (already exists)
|
// Skip if it's public channel (already exists)
|
||||||
debugPrint(
|
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(
|
debugPrint(
|
||||||
' ✅ Adding channel $channelIdx to ContactsProvider as Contact',
|
' ✅ Adding channel $channelIdx to ContactsProvider as Contact',
|
||||||
);
|
);
|
||||||
@@ -795,7 +825,7 @@ class AppProvider with ChangeNotifier {
|
|||||||
flags: flags ?? 0,
|
flags: flags ?? 0,
|
||||||
outPathLen: -1, // Flood mode for channels
|
outPathLen: -1, // Flood mode for channels
|
||||||
outPath: Uint8List(0), // Empty path for channels
|
outPath: Uint8List(0), // Empty path for channels
|
||||||
advName: channelName,
|
advName: contactChannelName,
|
||||||
lastAdvert: now,
|
lastAdvert: now,
|
||||||
advLat: 0, // Channels don't have location
|
advLat: 0, // Channels don't have location
|
||||||
advLon: 0,
|
advLon: 0,
|
||||||
@@ -809,7 +839,7 @@ class AppProvider with ChangeNotifier {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
' ⏭️ Skipping channel $channelIdx (empty: ${channelName.isEmpty}, isPublic: ${channelIdx == 0})',
|
' ⏭️ Skipping channel $channelIdx (isPublic: ${channelIdx == 0})',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
|
|||||||
@@ -825,6 +825,20 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
/// This should be set by AppProvider to query ChannelsProvider
|
/// This should be set by AppProvider to query ChannelsProvider
|
||||||
Function(int channelIdx)? getChannelInfo;
|
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
|
/// Check if a specific channel slot is empty
|
||||||
Future<bool> isChannelSlotEmpty(int channelIdx) async {
|
Future<bool> isChannelSlotEmpty(int channelIdx) async {
|
||||||
if (!_activeService.isConnected) {
|
if (!_activeService.isConnected) {
|
||||||
@@ -836,8 +850,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
if (getChannelInfo != null) {
|
if (getChannelInfo != null) {
|
||||||
final channel = getChannelInfo!(channelIdx);
|
final channel = getChannelInfo!(channelIdx);
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
final channelName = (channel as dynamic).name as String?;
|
return !_channelSlotIsOccupied(channel);
|
||||||
return channelName == null || channelName.isEmpty;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -849,8 +862,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
if (getChannelInfo != null) {
|
if (getChannelInfo != null) {
|
||||||
final channel = getChannelInfo!(channelIdx);
|
final channel = getChannelInfo!(channelIdx);
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
final channelName = (channel as dynamic).name as String?;
|
return !_channelSlotIsOccupied(channel);
|
||||||
return channelName == null || channelName.isEmpty;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,8 +912,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final channelName = (channel as dynamic).name as String?;
|
if (_channelSlotIsOccupied(channel)) {
|
||||||
if (channelName != null && channelName.isNotEmpty) {
|
|
||||||
usedIndices.add(i);
|
usedIndices.add(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -923,14 +934,15 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
// First check cache
|
// First check cache
|
||||||
if (getChannelInfo != null) {
|
if (getChannelInfo != null) {
|
||||||
final channel = getChannelInfo!(i);
|
final channel = getChannelInfo!(i);
|
||||||
if (channel != null) {
|
if (channel != null && _channelSlotIsOccupied(channel)) {
|
||||||
final channelName = (channel as dynamic).name as String?;
|
final channelName = (channel as dynamic).name as String?;
|
||||||
if (channelName != null && channelName.isNotEmpty) {
|
final label = (channelName != null && channelName.isNotEmpty)
|
||||||
debugPrint(' ⏭️ Slot $i occupied: "$channelName"');
|
? channelName
|
||||||
|
: 'Channel $i';
|
||||||
|
debugPrint(' ⏭️ Slot $i occupied: "$label"');
|
||||||
continue; // Skip occupied slots
|
continue; // Skip occupied slots
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Slot appears empty in cache, verify by querying device
|
// Slot appears empty in cache, verify by querying device
|
||||||
debugPrint(' 🔍 Checking slot $i...');
|
debugPrint(' 🔍 Checking slot $i...');
|
||||||
|
|||||||
@@ -636,7 +636,23 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
final existingSender = existing.senderKeyShort ?? existing.senderName;
|
final existingSender = existing.senderKeyShort ?? existing.senderName;
|
||||||
final incomingSender = message.senderKeyShort ?? message.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;
|
return true;
|
||||||
|
|||||||
@@ -81,9 +81,7 @@ class _BidirectionalRefreshState extends State<BidirectionalRefresh> {
|
|||||||
try {
|
try {
|
||||||
await widget.onRefresh();
|
await widget.onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
if (!mounted) {
|
if (mounted) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isRefreshing = false;
|
_isRefreshing = false;
|
||||||
_topPullDistance = 0;
|
_topPullDistance = 0;
|
||||||
@@ -91,6 +89,7 @@ class _BidirectionalRefreshState extends State<BidirectionalRefresh> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _resetPullDistance() {
|
void _resetPullDistance() {
|
||||||
if (_topPullDistance == 0 && _bottomPullDistance == 0) {
|
if (_topPullDistance == 0 && _bottomPullDistance == 0) {
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- file_picker (0.0.1):
|
- file_picker (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- file_selector_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
- flutter_avif_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
- flutter_blue_plus_darwin (0.0.2):
|
- flutter_blue_plus_darwin (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -19,10 +23,6 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- nsd_macos (0.0.1):
|
- nsd_macos (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- ObjectBox (4.4.1)
|
|
||||||
- objectbox_flutter_libs (0.0.1):
|
|
||||||
- FlutterMacOS
|
|
||||||
- ObjectBox (= 4.4.1)
|
|
||||||
- package_info_plus (0.0.1):
|
- package_info_plus (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
@@ -35,6 +35,9 @@ PODS:
|
|||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- sqflite_darwin (0.0.4):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- url_launcher_macos (0.0.1):
|
- url_launcher_macos (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
|
||||||
@@ -43,23 +46,21 @@ DEPENDENCIES:
|
|||||||
- codec2_flutter (from `Flutter/ephemeral/.symlinks/plugins/codec2_flutter/macos`)
|
- codec2_flutter (from `Flutter/ephemeral/.symlinks/plugins/codec2_flutter/macos`)
|
||||||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||||
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/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_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`)
|
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
- geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin`)
|
- geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin`)
|
||||||
- nsd_macos (from `Flutter/ephemeral/.symlinks/plugins/nsd_macos/macos`)
|
- 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`)
|
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`)
|
- record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`)
|
||||||
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- 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`)
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||||
|
|
||||||
SPEC REPOS:
|
|
||||||
trunk:
|
|
||||||
- ObjectBox
|
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
audioplayers_darwin:
|
audioplayers_darwin:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/darwin
|
||||||
@@ -69,6 +70,10 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
||||||
file_picker:
|
file_picker:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
|
: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:
|
flutter_blue_plus_darwin:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_blue_plus_darwin/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_blue_plus_darwin/darwin
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
@@ -79,8 +84,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin
|
||||||
nsd_macos:
|
nsd_macos:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/nsd_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/nsd_macos/macos
|
||||||
objectbox_flutter_libs:
|
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/objectbox_flutter_libs/macos
|
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
@@ -91,28 +94,31 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||||
|
sqflite_darwin:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
audioplayers_darwin: 4f9ca89d92d3d21cec7ec580e78ca888e5fb68bd
|
audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5
|
||||||
codec2_flutter: 50d930e6a1a187f05891b89300c6ef589651bfbd
|
codec2_flutter: 50d930e6a1a187f05891b89300c6ef589651bfbd
|
||||||
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||||
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
||||||
|
file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7
|
||||||
|
flutter_avif_macos: 9ed61d67adfbd6964eccb59971020fb55c31fc11
|
||||||
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
||||||
flutter_local_notifications: 4bf37a31afde695b56091b4ae3e4d9c7a7e6cda0
|
flutter_local_notifications: 1fc7ffb10a83d6a2eeeeddb152d43f1944b0aad0
|
||||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||||
nsd_macos: a472240e770b92f6c6df1022403aa29c90d012e3
|
nsd_macos: a472240e770b92f6c6df1022403aa29c90d012e3
|
||||||
ObjectBox: 7da4aceb5013d041bfafdbc6d744a26918b09757
|
|
||||||
objectbox_flutter_libs: f51d18f6a4b5965c218843373b7dc5ed5e3a2008
|
|
||||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||||
record_macos: 7f227161b93c49e7e34fe681c5891c8622c8cc8b
|
record_macos: 7f227161b93c49e7e34fe681c5891c8622c8cc8b
|
||||||
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
||||||
|
|
||||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
PODFILE CHECKSUM: e3de8f2486e300492cc80a2fe0576e9d269ba22b
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -794,9 +794,11 @@ packages:
|
|||||||
meshcore_client:
|
meshcore_client:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../meshcore_client"
|
path: "."
|
||||||
relative: true
|
ref: fbe0803
|
||||||
source: path
|
resolved-ref: fbe08032df6a76ebc8339cbcca794a95e976a514
|
||||||
|
url: "https://github.com/dz0ny/meshcore_client.git"
|
||||||
|
source: git
|
||||||
version: "0.1.0"
|
version: "0.1.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
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
|
# 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
|
# 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.
|
# 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:
|
environment:
|
||||||
sdk: ^3.9.2
|
sdk: ^3.9.2
|
||||||
@@ -44,7 +44,7 @@ dependencies:
|
|||||||
meshcore_client:
|
meshcore_client:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/dz0ny/meshcore_client.git
|
url: https://github.com/dz0ny/meshcore_client.git
|
||||||
ref: main
|
ref: "fbe0803"
|
||||||
|
|
||||||
# Codec2 ultra-low-bitrate speech codec (FFI plugin)
|
# Codec2 ultra-low-bitrate speech codec (FFI plugin)
|
||||||
codec2_flutter:
|
codec2_flutter:
|
||||||
@@ -139,9 +139,6 @@ dev_dependencies:
|
|||||||
fake_async: ^1.3.3
|
fake_async: ^1.3.3
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
meshcore_client:
|
|
||||||
path: ../meshcore_client
|
|
||||||
|
|
||||||
# path_provider_foundation 2.6.0 pulls in package:objective_c as a native
|
# 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
|
# asset. That framework has been ending up archived with a macOS platform
|
||||||
# slice and fails App Store validation for iOS uploads.
|
# 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() {
|
void main() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
@@ -124,18 +164,7 @@ void main() {
|
|||||||
test('channel messages are marked sent immediately', () {
|
test('channel messages are marked sent immediately', () {
|
||||||
final provider = MessagesProvider();
|
final provider = MessagesProvider();
|
||||||
provider.addSentMessage(
|
provider.addSentMessage(
|
||||||
Message(
|
_buildSentChannelMessage(id: 'c1', senderTimestamp: 1700000000),
|
||||||
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,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
provider.markMessageSent('c1', 0, 0);
|
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', () {
|
test('missing ACK schedules a delayed retransmission', () {
|
||||||
fakeAsync((async) {
|
fakeAsync((async) {
|
||||||
final provider = MessagesProvider();
|
final provider = MessagesProvider();
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ void main() {
|
|||||||
unreadCount: 0,
|
unreadCount: 0,
|
||||||
unreadCountsByPublicKey: const {},
|
unreadCountsByPublicKey: const {},
|
||||||
showAllOption: false,
|
showAllOption: false,
|
||||||
onSelect: (_, __) {},
|
onSelect: (selectedRecipient, draftMessage) {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user