From 1bce467959d704118a2f8f33186b69bc724853af Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Tue, 22 Oct 2024 12:04:59 +0200 Subject: [PATCH] Add DynamicLinkText component for clickable URLs in chat bubbles --- .../Fragments/ChatBubbleView.swift | 42 +++++++++++++++++-- .../Fragments/ConversationFragment.swift | 2 +- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift index a673a81de..ab3ea8e76 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift @@ -164,9 +164,7 @@ struct ChatBubbleView: View { } if !eventLogMessage.message.text.isEmpty { - Text(eventLogMessage.message.text) - .foregroundStyle(Color.grayMain2c700) - .default_text_style(styleSize: 14) + DynamicLinkText(text: eventLogMessage.message.text) } if eventLogMessage.message.isIcalendar && eventLogMessage.message.messageConferenceInfo != nil { @@ -756,6 +754,44 @@ struct ChatBubbleView: View { } } +struct DynamicLinkText: View { + let text: String + + var body: some View { + let components = text.components(separatedBy: " ") + + Text(makeAttributedString(from: components)) + .fixedSize(horizontal: false, vertical: true) + .multilineTextAlignment(.leading) + .lineLimit(nil) + .foregroundStyle(Color.grayMain2c700) + .default_text_style(styleSize: 14) + } + + // Function to create an AttributedString with clickable links + private func makeAttributedString(from components: [String]) -> AttributedString { + var result = AttributedString("") + for (index, component) in components.enumerated() { + if let url = URL(string: component.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""), + url.scheme == "http" || url.scheme == "https" { + var attributedText = AttributedString(component) + attributedText.link = url + attributedText.foregroundColor = .blue + attributedText.underlineStyle = .single + result.append(attributedText) + } else { + result.append(AttributedString(component)) + } + + // Add space between words except for the last one + if index < components.count - 1 { + result.append(AttributedString(" ")) + } + } + return result + } +} + enum URLType { case name(String) // local file name of gif case url(URL) // remote url diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift index 1bd1637d7..685eb2260 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift @@ -785,7 +785,7 @@ struct ConversationFragment: View { if !conversationViewModel.selectedMessage!.message.text.isEmpty { Button { UIPasteboard.general.setValue( - conversationViewModel.selectedMessage!.message.text, + conversationViewModel.selectedMessage?.message.text ?? "Error_message_not_available", forPasteboardType: UTType.plainText.identifier )