Bump iOS project version

This commit is contained in:
Janez T
2026-03-13 14:18:27 +01:00
parent 6e17de3866
commit 24e715d01e
14 changed files with 261 additions and 73 deletions

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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,12 +934,13 @@ 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
continue; // Skip occupied slots : 'Channel $i';
} debugPrint(' ⏭️ Slot $i occupied: "$label"');
continue; // Skip occupied slots
} }
} }

View File

@@ -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;

View File

@@ -81,14 +81,13 @@ class _BidirectionalRefreshState extends State<BidirectionalRefresh> {
try { try {
await widget.onRefresh(); await widget.onRefresh();
} finally { } finally {
if (!mounted) { if (mounted) {
return; setState(() {
_isRefreshing = false;
_topPullDistance = 0;
_bottomPullDistance = 0;
});
} }
setState(() {
_isRefreshing = false;
_topPullDistance = 0;
_bottomPullDistance = 0;
});
} }
} }

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View 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',
);
});
});
}

View 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,
);
});
});
}

View File

@@ -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();

View File

@@ -89,7 +89,7 @@ void main() {
unreadCount: 0, unreadCount: 0,
unreadCountsByPublicKey: const {}, unreadCountsByPublicKey: const {},
showAllOption: false, showAllOption: false,
onSelect: (_, __) {}, onSelect: (selectedRecipient, draftMessage) {},
), ),
), ),
), ),