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:
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user