Fix URL detection and message spacing

- Add URL detection to message formatting with blue color and underline
- Replace List with ScrollView for precise spacing control
- Use minimal vertical padding (1px) between messages
- Remove LazyLinkPreviewView, use LinkPreviewView directly
- URLs now appear highlighted in message text AND show preview below
This commit is contained in:
jack
2025-07-24 12:53:55 +02:00
parent b480a4836c
commit c08fe38c72
2 changed files with 36 additions and 54 deletions
+10
View File
@@ -988,8 +988,12 @@ class ChatViewModel: ObservableObject {
let hashtagRegex = try? NSRegularExpression(pattern: hashtagPattern, options: [])
let mentionRegex = try? NSRegularExpression(pattern: mentionPattern, options: [])
// Use NSDataDetector for URL detection
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let hashtagMatches = hashtagRegex?.matches(in: content, options: [], range: NSRange(location: 0, length: content.count)) ?? []
let mentionMatches = mentionRegex?.matches(in: content, options: [], range: NSRange(location: 0, length: content.count)) ?? []
let urlMatches = detector?.matches(in: content, options: [], range: NSRange(location: 0, length: content.count)) ?? []
// Combine and sort matches
var allMatches: [(range: NSRange, type: String)] = []
@@ -999,6 +1003,9 @@ class ChatViewModel: ObservableObject {
for match in mentionMatches {
allMatches.append((match.range(at: 0), "mention"))
}
for match in urlMatches {
allMatches.append((match.range, "url"))
}
allMatches.sort { $0.range.location < $1.range.location }
// Build content with styling
@@ -1029,6 +1036,9 @@ class ChatViewModel: ObservableObject {
matchStyle.underlineStyle = .single
} else if type == "mention" {
matchStyle.foregroundColor = Color.orange
} else if type == "url" {
matchStyle.foregroundColor = Color.blue
matchStyle.underlineStyle = .single
}
result.append(AttributedString(matchText).mergingAttributes(matchStyle))
+26 -54
View File
@@ -163,21 +163,22 @@ struct ContentView: View {
private func messagesView(privatePeer: String?) -> some View {
ScrollViewReader { proxy in
List {
let messages: [BitchatMessage] = {
if let privatePeer = privatePeer {
let msgs = viewModel.getPrivateChatMessages(for: privatePeer)
return msgs
} else {
return viewModel.messages
}
}()
// Implement windowing - show last 100 messages for performance
let windowedMessages = messages.suffix(100)
ForEach(Array(windowedMessages), id: \.id) { message in
VStack(alignment: .leading, spacing: 2) {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
let messages: [BitchatMessage] = {
if let privatePeer = privatePeer {
let msgs = viewModel.getPrivateChatMessages(for: privatePeer)
return msgs
} else {
return viewModel.messages
}
}()
// Implement windowing - show last 100 messages for performance
let windowedMessages = messages.suffix(100)
ForEach(Array(windowedMessages), id: \.id) { message in
VStack(alignment: .leading, spacing: 0) {
// Check if current user is mentioned
let _ = message.mentions?.contains(viewModel.nickname) ?? false
@@ -207,12 +208,12 @@ struct ContentView: View {
// Check for plain URLs
let urls = message.content.extractURLs()
ForEach(Array(urls.prefix(3).enumerated()), id: \.offset) { index, urlInfo in
LazyLinkPreviewView(
url: urlInfo.url,
title: nil,
id: "\(message.id)-\(urlInfo.url.absoluteString)"
)
if !urls.isEmpty {
ForEach(Array(urls.prefix(3).enumerated()), id: \.offset) { index, urlInfo in
LinkPreviewView(url: urlInfo.url, title: nil)
.padding(.top, 2)
.id("\(message.id)-\(urlInfo.url.absoluteString)")
}
}
}
}
@@ -227,13 +228,12 @@ struct ContentView: View {
showMessageActions = true
}
}
.listRowInsets(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
.listRowSeparator(.hidden)
.listRowBackground(backgroundColor)
.padding(.horizontal, 12)
.padding(.vertical, 1)
}
}
.padding(.vertical, 4)
}
.listStyle(.plain)
.scrollContentBackground(.hidden)
.background(backgroundColor)
.onTapGesture(count: 3) {
// Triple-tap to clear current chat
@@ -938,34 +938,6 @@ struct MessageContentView: View {
}
}
// Lazy loading wrapper for LinkPreviewView
struct LazyLinkPreviewView: View {
let url: URL
let title: String?
let id: String
@State private var shouldLoad = false
var body: some View {
Group {
if shouldLoad {
LinkPreviewView(url: url, title: title)
.padding(.top, 2)
.id(id)
}
}
.onAppear {
// Delay loading to improve scroll performance
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
shouldLoad = true
}
}
.onDisappear {
shouldLoad = false
}
}
}
// Delivery status indicator view
struct DeliveryStatusView: View {
let status: DeliveryStatus