HuggingChat-Mac/Views/LinkPreview.swift (138 lines of code) (raw):
//
// LinkPreview.swift
// HuggingChat-Mac
//
// Created by Cyril Zakka on 11/30/24.
//
import SwiftUI
import LinkPresentation
struct LinkPreview: View {
var link: WebSearchSource
@State private var metadata: LPLinkMetadata?
@State private var isLoading = true
@State private var error: Error?
@State private var averageColor: Color = .gray.opacity(0.3)
var body: some View {
HStack(spacing: 7) {
// Icon/Image View
Group {
if isLoading {
RoundedRectangle(cornerRadius: 6)
.fill(Color.gray.opacity(0.2))
.frame(width: 30, height: 30)
.overlay {
ProgressView()
.controlSize(.small)
}
} else if let iconProvider = metadata?.iconProvider {
IconView(iconProvider: iconProvider, averageColor: $averageColor)
.frame(width: 30, height: 30)
} else {
RoundedRectangle(cornerRadius: 6)
.fill(Color.gray.opacity(0.2))
.frame(width: 30, height: 30)
.overlay {
Image(systemName: "link")
}
}
}
// Title and URL
VStack(alignment: .leading, spacing: 0) {
Text(metadata?.title ?? link.title)
.foregroundColor(.primary)
.fontWeight(.semibold)
// .getContrastText(backgroundColor: averageColor)
.font(.caption2)
.lineLimit(1)
Text(link.hostname)
// .getContrastText(backgroundColor: averageColor)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(1)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 5)
.padding(.horizontal, 5)
.background(.ultraThickMaterial)
.overlay {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.stroke(.secondary.opacity(0.5), lineWidth: 1.0)
}
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
.onTapGesture {
NSWorkspace.shared.open(link.link)
}
.onAppear {
if metadata == nil {
loadMetadata()
}
}
}
private func loadMetadata() {
isLoading = true
error = nil
let provider = LPMetadataProvider()
provider.startFetchingMetadata(for: link.link) { metadata, error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
print("Error fetching metadata: \(error)")
self.error = error
return
}
self.metadata = metadata
}
}
}
}
struct IconView: View {
let iconProvider: NSItemProvider
@Binding var averageColor: Color
@State private var icon: NSImage?
@State private var loadError: Error?
var body: some View {
Group {
if let icon = icon {
Image(nsImage: icon)
.resizable()
.aspectRatio(contentMode: .fill)
.cornerRadius(6)
} else {
ZStack {
RoundedRectangle(cornerRadius: 6)
.fill(Color.gray.opacity(0.2))
if loadError != nil {
Image(systemName: "link")
} else {
ProgressView()
.controlSize(.small)
}
}
}
}
.onAppear {
loadIcon()
}
}
private func loadIcon() {
iconProvider.loadObject(ofClass: NSImage.self) { image, error in
DispatchQueue.main.async {
if let error = error {
print("Error loading icon: \(error)")
self.loadError = error
return
}
if let nsImage = image as? NSImage {
self.icon = nsImage
// Calculate average color from CGImage
// if let cgImage = nsImage.cgImage(forProposedRect: nil, context: nil, hints: nil) {
// if let avgColor = findAverageColor(cgImage: cgImage, algorithm: .squareRoot) {
// self.averageColor = avgColor
// }
// }
}
}
}
}
}
// Preview
#Preview {
VStack() {
LinkPreview(link: WebSearchSource(
link: URL(string: "https://www.apple.com")!,
title: "Apple",
hostname: "apple.com"
))
LinkPreview(link: WebSearchSource(
link: URL(string: "https://www.github.com")!,
title: "GitHub",
hostname: "github.com"
))
}
.padding(.horizontal)
.frame(maxWidth: 160)
}