import DesignSystem import Models import Nuke import NukeUI import Shimmer import SwiftUI @MainActor public struct StatusRowCardView: View { @Environment(\.openURL) private var openURL @Environment(\.isInCaptureMode) private var isInCaptureMode: Bool @Environment(Theme.self) private var theme let card: Card public init(card: Card) { self.card = card } private var maxWidth: CGFloat? { if theme.statusDisplayStyle == .medium { return 300 } return nil } private func imageWidthFor(proxy: GeometryProxy) -> CGFloat { if theme.statusDisplayStyle == .medium, let maxWidth { return maxWidth } return proxy.frame(in: .local).width } private var imageHeight: CGFloat { if theme.statusDisplayStyle == .medium { return 100 } return 200 } public var body: some View { Button { if let url = URL(string: card.url) { openURL(url) } } label: { if let title = card.title, let url = URL(string: card.url) { VStack(alignment: .leading) { let sitesWithIcons = ["apps.apple.com", "music.apple.com", "open.spotify.com"] if (UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac), let host = url.host(), sitesWithIcons.contains(host) { iconLinkPreview(title, url) } else { defaultLinkPreview(title, url) } } .frame(maxWidth: maxWidth) .fixedSize(horizontal: false, vertical: true) .background(theme.secondaryBackgroundColor) .cornerRadius(16) .overlay( RoundedRectangle(cornerRadius: 16) .stroke(.gray.opacity(0.35), lineWidth: 1) ) .contextMenu { ShareLink(item: url) { Label("status.card.share", systemImage: "square.and.arrow.up") } Button { openURL(url) } label: { Label("status.action.view-in-browser", systemImage: "safari") } Divider() Button { UIPasteboard.general.url = url } label: { Label("status.card.copy", systemImage: "doc.on.doc") } } .accessibilityElement(children: .combine) .accessibilityAddTraits(.isLink) .accessibilityRemoveTraits(.isStaticText) } } .buttonStyle(.plain) } @ViewBuilder private func defaultLinkPreview(_ title: String, _ url: URL) -> some View { if let imageURL = card.image, !isInCaptureMode { LazyResizableImage(url: imageURL) { state, proxy in let width = imageWidthFor(proxy: proxy) if let image = state.image { image .resizable() .aspectRatio(contentMode: .fill) .frame(height: imageHeight) .frame(maxWidth: width) .clipped() } else if state.isLoading { Rectangle() .fill(Color.gray) .frame(height: imageHeight) } } // This image is decorative .accessibilityHidden(true) .frame(height: imageHeight) } HStack { VStack(alignment: .leading, spacing: 6) { Text(title) .font(.scaledHeadline) .lineLimit(3) if let description = card.description, !description.isEmpty { Text(description) .font(.scaledBody) .foregroundStyle(.secondary) .lineLimit(3) } Text(url.host() ?? url.absoluteString) .font(.scaledFootnote) .foregroundColor(theme.tintColor) .lineLimit(1) } Spacer() }.padding(16) } private func iconLinkPreview(_ title: String, _ url: URL) -> some View { // ..where the image is known to be a square icon HStack { if let imageURL = card.image, !isInCaptureMode { LazyResizableImage(url: imageURL) { state, _ in if let image = state.image { image .resizable() .aspectRatio(contentMode: .fill) .frame(width: imageHeight, height: imageHeight) .clipped() } else if state.isLoading { Rectangle() .fill(Color.gray) .frame(width: imageHeight, height: imageHeight) } } // This image is decorative .accessibilityHidden(true) .frame(width: imageHeight, height: imageHeight) } VStack(alignment: .leading, spacing: 6) { Text(title) .font(.scaledHeadline) .lineLimit(3) if let description = card.description, !description.isEmpty { Text(description) .font(.scaledBody) .foregroundStyle(.secondary) .lineLimit(3) } Text(url.host() ?? url.absoluteString) .font(.scaledFootnote) .foregroundColor(theme.tintColor) .lineLimit(1) }.padding(16) } } }