diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index f27e66e..500be12 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -489,7 +489,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 125;
+ CURRENT_PROJECT_VERSION = 126;
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 = 126;
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 = 126;
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 = 126;
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 = 126;
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 = 126;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 503a8ff..e7aadf3 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -43,7 +43,7 @@
CFBundleSignature
????
CFBundleVersion
- 125
+ 126
LSRequiresIPhoneOS
ITSAppUsesNonExemptEncryption
diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml
index e0139aa..3e48371 100644
--- a/ios/fastlane/report.xml
+++ b/ios/fastlane/report.xml
@@ -5,22 +5,22 @@
-
+
-
+
-
+
-
+
diff --git a/lib/providers/messages_provider.dart b/lib/providers/messages_provider.dart
index 632c693..868c297 100644
--- a/lib/providers/messages_provider.dart
+++ b/lib/providers/messages_provider.dart
@@ -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 messages) {
int addedCount = 0;
diff --git a/pubspec.yaml b/pubspec.yaml
index 2f1cef1..85d02b5 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -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.1+40
environment:
sdk: ^3.9.2
diff --git a/test/providers/messages_provider_retransmission_test.dart b/test/providers/messages_provider_retransmission_test.dart
index bb962eb..02aaa4d 100644
--- a/test/providers/messages_provider_retransmission_test.dart
+++ b/test/providers/messages_provider_retransmission_test.dart
@@ -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', () {