fix: Keep received replays separate

This commit is contained in:
Janez T
2026-03-23 20:07:06 +01:00
parent e85dccea35
commit 3e3c9a34d5
6 changed files with 55 additions and 25 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 = 125; CURRENT_PROJECT_VERSION = 126;
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 = 125; CURRENT_PROJECT_VERSION = 126;
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 = 125; CURRENT_PROJECT_VERSION = 126;
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 = 125; CURRENT_PROJECT_VERSION = 126;
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 = 125; CURRENT_PROJECT_VERSION = 126;
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 = 125; CURRENT_PROJECT_VERSION = 126;
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>125</string> <string>126</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.000216"> <testcase classname="fastlane.lanes" name="0: default_platform" time="0.000231">
</testcase> </testcase>
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="1.086406"> <testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.728696">
</testcase> </testcase>
<testcase classname="fastlane.lanes" name="2: build_app" time="96.919368"> <testcase classname="fastlane.lanes" name="2: build_app" time="101.589371">
</testcase> </testcase>
<testcase classname="fastlane.lanes" name="3: upload_to_app_store" time="563.579992"> <testcase classname="fastlane.lanes" name="3: upload_to_app_store" time="690.798016">
</testcase> </testcase>

View File

@@ -664,6 +664,11 @@ class MessagesProvider with ChangeNotifier {
return; // Skip duplicate return; // Skip duplicate
} }
final matchingSentReplayIndex = _findMatchingSentReplayIndex(finalMessage);
if (matchingSentReplayIndex != -1) {
_clearChannelSendWarning(_messages[matchingSentReplayIndex].id);
}
_messages.add(finalMessage); _messages.add(finalMessage);
if (contactLocationSnapshot != null) { if (contactLocationSnapshot != null) {
_messageContactLocations[finalMessage.id] = contactLocationSnapshot; _messageContactLocations[finalMessage.id] = contactLocationSnapshot;
@@ -712,7 +717,8 @@ class MessagesProvider with ChangeNotifier {
for (int index = 0; index < _messages.length; index++) { for (int index = 0; index < _messages.length; index++) {
final existing = _messages[index]; final existing = _messages[index];
if (!_matchesDuplicateScope(existing, message) || if (existing.isSentMessage ||
!_matchesDuplicateScope(existing, message) ||
existing.text != message.text) { existing.text != message.text) {
continue; continue;
} }
@@ -790,6 +796,27 @@ class MessagesProvider with ChangeNotifier {
existingSenderName == incomingSenderName; 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 /// Add multiple messages
void addMessages(List<Message> messages) { void addMessages(List<Message> messages) {
int addedCount = 0; int addedCount = 0;

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.0322.2+39 version: 2026.0323.1+40
environment: environment:
sdk: ^3.9.2 sdk: ^3.9.2

View File

@@ -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) { fakeAsync((async) {
final provider = MessagesProvider(); final provider = MessagesProvider();
provider.resolveContactNameCallback = (_) => 'dz0ny (SI)'; provider.resolveContactNameCallback = (_) => 'dz0ny (SI)';
@@ -239,11 +239,11 @@ void main() {
); );
expect(provider.hasChannelSendWarning('c-warn-replay'), isFalse); 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(); final provider = MessagesProvider();
provider.resolveContactNameCallback = (_) => 'dz0ny (SI)'; provider.resolveContactNameCallback = (_) => 'dz0ny (SI)';
provider.addSentMessage( provider.addSentMessage(
@@ -259,9 +259,10 @@ void main() {
), ),
); );
expect(provider.messages, hasLength(1)); expect(provider.messages, hasLength(2));
expect(provider.messages.single.id, equals('c-echo')); expect(provider.messages.first.id, equals('c-echo'));
expect(provider.messages.single.senderName, equals('dz0ny (SI)')); expect(provider.messages.last.id, equals('c-echo-incoming'));
expect(provider.messages.last.senderName, equals('dz0ny (SI)'));
}); });
test( test(
@@ -308,7 +309,7 @@ void main() {
expect(provider.messages, hasLength(2)); 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(); final provider = MessagesProvider();
provider.addSentMessage( provider.addSentMessage(
_buildSentChannelMessage(id: 'c-lazy', senderTimestamp: 1700000400), _buildSentChannelMessage(id: 'c-lazy', senderTimestamp: 1700000400),
@@ -324,11 +325,12 @@ void main() {
), ),
); );
expect(provider.messages, hasLength(1)); expect(provider.messages, hasLength(2));
expect(provider.messages.single.id, equals('c-lazy')); 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(); final provider = MessagesProvider();
provider.resolveContactNameCallback = (_) => 'MeshCore-dz0ny (SI)'; provider.resolveContactNameCallback = (_) => 'MeshCore-dz0ny (SI)';
provider.addSentMessage( provider.addSentMessage(
@@ -344,8 +346,9 @@ void main() {
), ),
); );
expect(provider.messages, hasLength(1)); expect(provider.messages, hasLength(2));
expect(provider.messages.single.id, equals('c-prefix')); 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', () { test('duplicate incoming message increments received copy count', () {