mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 02:25:20 +00:00
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:
@@ -988,8 +988,12 @@ class ChatViewModel: ObservableObject {
|
|||||||
let hashtagRegex = try? NSRegularExpression(pattern: hashtagPattern, options: [])
|
let hashtagRegex = try? NSRegularExpression(pattern: hashtagPattern, options: [])
|
||||||
let mentionRegex = try? NSRegularExpression(pattern: mentionPattern, 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 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 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
|
// Combine and sort matches
|
||||||
var allMatches: [(range: NSRange, type: String)] = []
|
var allMatches: [(range: NSRange, type: String)] = []
|
||||||
@@ -999,6 +1003,9 @@ class ChatViewModel: ObservableObject {
|
|||||||
for match in mentionMatches {
|
for match in mentionMatches {
|
||||||
allMatches.append((match.range(at: 0), "mention"))
|
allMatches.append((match.range(at: 0), "mention"))
|
||||||
}
|
}
|
||||||
|
for match in urlMatches {
|
||||||
|
allMatches.append((match.range, "url"))
|
||||||
|
}
|
||||||
allMatches.sort { $0.range.location < $1.range.location }
|
allMatches.sort { $0.range.location < $1.range.location }
|
||||||
|
|
||||||
// Build content with styling
|
// Build content with styling
|
||||||
@@ -1029,6 +1036,9 @@ class ChatViewModel: ObservableObject {
|
|||||||
matchStyle.underlineStyle = .single
|
matchStyle.underlineStyle = .single
|
||||||
} else if type == "mention" {
|
} else if type == "mention" {
|
||||||
matchStyle.foregroundColor = Color.orange
|
matchStyle.foregroundColor = Color.orange
|
||||||
|
} else if type == "url" {
|
||||||
|
matchStyle.foregroundColor = Color.blue
|
||||||
|
matchStyle.underlineStyle = .single
|
||||||
}
|
}
|
||||||
|
|
||||||
result.append(AttributedString(matchText).mergingAttributes(matchStyle))
|
result.append(AttributedString(matchText).mergingAttributes(matchStyle))
|
||||||
|
|||||||
@@ -163,21 +163,22 @@ struct ContentView: View {
|
|||||||
|
|
||||||
private func messagesView(privatePeer: String?) -> some View {
|
private func messagesView(privatePeer: String?) -> some View {
|
||||||
ScrollViewReader { proxy in
|
ScrollViewReader { proxy in
|
||||||
List {
|
ScrollView {
|
||||||
let messages: [BitchatMessage] = {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
if let privatePeer = privatePeer {
|
let messages: [BitchatMessage] = {
|
||||||
let msgs = viewModel.getPrivateChatMessages(for: privatePeer)
|
if let privatePeer = privatePeer {
|
||||||
return msgs
|
let msgs = viewModel.getPrivateChatMessages(for: privatePeer)
|
||||||
} else {
|
return msgs
|
||||||
return viewModel.messages
|
} else {
|
||||||
}
|
return viewModel.messages
|
||||||
}()
|
}
|
||||||
|
}()
|
||||||
// Implement windowing - show last 100 messages for performance
|
|
||||||
let windowedMessages = messages.suffix(100)
|
// 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) {
|
ForEach(Array(windowedMessages), id: \.id) { message in
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
// Check if current user is mentioned
|
// Check if current user is mentioned
|
||||||
let _ = message.mentions?.contains(viewModel.nickname) ?? false
|
let _ = message.mentions?.contains(viewModel.nickname) ?? false
|
||||||
|
|
||||||
@@ -207,12 +208,12 @@ struct ContentView: View {
|
|||||||
|
|
||||||
// Check for plain URLs
|
// Check for plain URLs
|
||||||
let urls = message.content.extractURLs()
|
let urls = message.content.extractURLs()
|
||||||
ForEach(Array(urls.prefix(3).enumerated()), id: \.offset) { index, urlInfo in
|
if !urls.isEmpty {
|
||||||
LazyLinkPreviewView(
|
ForEach(Array(urls.prefix(3).enumerated()), id: \.offset) { index, urlInfo in
|
||||||
url: urlInfo.url,
|
LinkPreviewView(url: urlInfo.url, title: nil)
|
||||||
title: nil,
|
.padding(.top, 2)
|
||||||
id: "\(message.id)-\(urlInfo.url.absoluteString)"
|
.id("\(message.id)-\(urlInfo.url.absoluteString)")
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,13 +228,12 @@ struct ContentView: View {
|
|||||||
showMessageActions = true
|
showMessageActions = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listRowInsets(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
|
.padding(.horizontal, 12)
|
||||||
.listRowSeparator(.hidden)
|
.padding(.vertical, 1)
|
||||||
.listRowBackground(backgroundColor)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
}
|
}
|
||||||
.listStyle(.plain)
|
|
||||||
.scrollContentBackground(.hidden)
|
|
||||||
.background(backgroundColor)
|
.background(backgroundColor)
|
||||||
.onTapGesture(count: 3) {
|
.onTapGesture(count: 3) {
|
||||||
// Triple-tap to clear current chat
|
// 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
|
// Delivery status indicator view
|
||||||
struct DeliveryStatusView: View {
|
struct DeliveryStatusView: View {
|
||||||
let status: DeliveryStatus
|
let status: DeliveryStatus
|
||||||
|
|||||||
Reference in New Issue
Block a user