From 99bb446ee9ef1326d0efb0fce22a2d41b94ffc3b Mon Sep 17 00:00:00 2001 From: Janez T Date: Wed, 11 Mar 2026 16:31:53 +0100 Subject: [PATCH] Update iOS Runner project version --- ios/Runner.xcodeproj/project.pbxproj | 12 ++-- ios/Runner/Info.plist | 2 +- ios/fastlane/report.xml | 8 +-- lib/screens/messages_tab.dart | 34 +++++++++++ lib/widgets/messages/message_bubble.dart | 74 +++++++++++++++++++++++- 5 files changed, 117 insertions(+), 13 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 88c3c89..a754fbc 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 = 109; + CURRENT_PROJECT_VERSION = 111; 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 = 109; + CURRENT_PROJECT_VERSION = 111; 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 = 109; + CURRENT_PROJECT_VERSION = 111; 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 = 109; + CURRENT_PROJECT_VERSION = 111; 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 = 109; + CURRENT_PROJECT_VERSION = 111; 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 = 109; + CURRENT_PROJECT_VERSION = 111; DEVELOPMENT_TEAM = JND55328G8; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index eac98b6..2e16162 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -43,7 +43,7 @@ CFBundleSignature ???? CFBundleVersion - 109 + 111 LSRequiresIPhoneOS ITSAppUsesNonExemptEncryption diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml index b4611ce..f33b0e2 100644 --- a/ios/fastlane/report.xml +++ b/ios/fastlane/report.xml @@ -5,22 +5,22 @@ - + - + - + - + diff --git a/lib/screens/messages_tab.dart b/lib/screens/messages_tab.dart index 68c5184..4f31a1d 100644 --- a/lib/screens/messages_tab.dart +++ b/lib/screens/messages_tab.dart @@ -338,9 +338,35 @@ class _MessagesTabState extends State { _focusNode.requestFocus(); } + void _insertReplyMention(String displayName) { + final trimmedName = displayName.trim(); + if (trimmedName.isEmpty) return; + + final mention = '@[$trimmedName] '; + final value = _textController.value; + final selection = value.selection; + final hasSelection = + selection.isValid && + selection.start >= 0 && + selection.end >= selection.start; + + final start = hasSelection ? selection.start : value.text.length; + final end = hasSelection ? selection.end : value.text.length; + final nextText = value.text.replaceRange(start, end, mention); + final nextOffset = start + mention.length; + + _textController.value = value.copyWith( + text: nextText, + selection: TextSelection.collapsed(offset: nextOffset), + composing: TextRange.empty, + ); + _enforceMessageByteLimit(); + } + Future _replyToMessage(Message message) async { final l10n = AppLocalizations.of(context)!; final contactsProvider = context.read(); + Contact? senderContact; String destinationType; Contact? recipient; @@ -360,6 +386,11 @@ class _MessagesTabState extends State { return; } } + + final senderPrefix = message.senderPublicKeyPrefix; + if (senderPrefix != null && senderPrefix.length >= 6) { + senderContact = contactsProvider.findContactByPrefix(senderPrefix); + } } else { final senderPrefix = message.senderPublicKeyPrefix; if (senderPrefix == null || senderPrefix.length < 6) { @@ -380,6 +411,9 @@ class _MessagesTabState extends State { await _onRecipientSelected(destinationType, recipient); if (!mounted) return; + if (message.isChannelMessage && senderContact != null) { + _insertReplyMention(senderContact.displayName); + } _focusNode.requestFocus(); } diff --git a/lib/widgets/messages/message_bubble.dart b/lib/widgets/messages/message_bubble.dart index 5c4ea58..134a168 100644 --- a/lib/widgets/messages/message_bubble.dart +++ b/lib/widgets/messages/message_bubble.dart @@ -68,6 +68,7 @@ class MessageBubble extends StatefulWidget { } class _MessageBubbleState extends State { + static final RegExp _mentionPattern = RegExp(r'@\[(.+?)\]'); bool _isExpanded = false; bool _showReceivedStats = false; @@ -95,6 +96,72 @@ class _MessageBubbleState extends State { }); } + Widget _buildMessageTextContent(String text, TextStyle? baseBodyStyle) { + final matches = _mentionPattern.allMatches(text).toList(); + if (matches.isEmpty) { + return Text(text, style: baseBodyStyle); + } + + final textColor = + baseBodyStyle?.color ?? Theme.of(context).colorScheme.onSurface; + final backgroundColor = Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0.12); + final borderColor = Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0.25); + + final spans = []; + var cursor = 0; + + for (final match in matches) { + if (match.start > cursor) { + spans.add( + TextSpan( + text: text.substring(cursor, match.start), + style: baseBodyStyle, + ), + ); + } + + final mentionName = match.group(1)?.trim() ?? ''; + if (mentionName.isNotEmpty) { + spans.add( + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 1, vertical: 1), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(999), + border: Border.all(color: borderColor), + ), + child: Text( + '@$mentionName', + style: baseBodyStyle?.copyWith( + color: textColor, + fontWeight: FontWeight.w700, + height: 1.1, + ), + ), + ), + ), + ); + } else { + spans.add(TextSpan(text: match.group(0), style: baseBodyStyle)); + } + + cursor = match.end; + } + + if (cursor < text.length) { + spans.add(TextSpan(text: text.substring(cursor), style: baseBodyStyle)); + } + + return Text.rich(TextSpan(children: spans, style: baseBodyStyle)); + } + void _handleBubbleTap({required bool isSarMarker, required bool isDrawing}) { if (!widget.message.isRead && !widget.message.isSentMessage && @@ -2229,7 +2296,10 @@ class _MessageBubbleState extends State { ); if (drawing == null) { - return Text(message.text, style: baseBodyStyle); + return _buildMessageTextContent( + message.text, + baseBodyStyle, + ); } final String drawingTypeLabel; @@ -2315,7 +2385,7 @@ class _MessageBubbleState extends State { ) // Regular message content else if (!message.isDrawing || widget.isCompact) - Text(message.text, style: baseBodyStyle), + _buildMessageTextContent(message.text, baseBodyStyle), if (!widget.isCompact && !isSarMarker &&