Signal-iOS/Signal/ConversationView/LinkPreviewCallLink.swift
2026-03-05 00:04:37 -06:00

156 lines
4.8 KiB
Swift

//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
public import SignalRingRTC
public import SignalServiceKit
// MARK: -
public class LinkPreviewCallLink: LinkPreviewState {
public let conversationStyle: ConversationStyle?
public enum PreviewType {
case sent(OWSLinkPreview, ConversationStyle)
case draft(OWSLinkPreviewDraft)
}
private let previewType: PreviewType
private let callLink: CallLink
public init(previewType: PreviewType, callLink: CallLink) {
self.previewType = previewType
self.callLink = callLink
switch previewType {
case .sent(_, let conversationStyle):
self.conversationStyle = conversationStyle
case .draft:
self.conversationStyle = nil
}
}
public var isLoaded: Bool { true }
public var urlString: String? {
switch previewType {
case .sent(let linkPreview, _):
guard let urlString = linkPreview.urlString else {
owsFailDebug("Missing url")
return nil
}
return urlString
case .draft(let linkPreviewDraft):
return linkPreviewDraft.urlString
}
}
public var displayDomain: String? {
let displayDomain: String?
switch previewType {
case .sent(let linkPreview, _):
displayDomain = linkPreview.displayDomain
case .draft(let linkPreviewDraft):
displayDomain = linkPreviewDraft.displayDomain
}
guard let displayDomain else {
Logger.error("Missing display domain")
return nil
}
return displayDomain
}
public var title: String? {
let title: String?
switch previewType {
case .sent(let linkPreview, _):
title = linkPreview.title
case .draft(let linkPreviewDraft):
title = linkPreviewDraft.title
}
return title?.filterForDisplay.nilIfEmpty ?? CallStrings.signalCall
}
public var imageState: LinkPreviewImageState {
// Image is a local asset.
return .loaded
}
public func imageAsync(thumbnailQuality: AttachmentThumbnailQuality, completion: @escaping (UIImage) -> Void) {
if let image = CommonCallLinksUI.callLinkIcon(rootKey: callLink.rootKey) {
completion(image)
}
}
public func imageCacheKey(thumbnailQuality: AttachmentThumbnailQuality) -> LinkPreviewImageCacheKey? { return nil }
public var imagePixelSize: CGSize {
return CGSize(square: CommonCallLinksUI.Constants.circleViewDimension)
}
public var previewDescription: String? {
let description: String?
switch previewType {
case .sent(let linkPreview, _):
description = linkPreview.previewDescription
case .draft(let linkPreviewDraft):
description = linkPreviewDraft.previewDescription
}
return description?.filterForDisplay.nilIfEmpty ?? CallStrings.callLinkDescription
}
public var date: Date? {
switch previewType {
case .sent(let linkPreview, _):
linkPreview.date
case .draft(let linkPreviewDraft):
linkPreviewDraft.date
}
}
public var isGroupInviteLink: Bool { false }
public var isCallLink: Bool { true }
}
public class CommonCallLinksUI {
public static func callLinkIcon(rootKey: CallLinkRootKey) -> UIImage? {
guard let image = UIImage(named: "video-compact") else { return nil }
let newSize = CGSize(square: Constants.circleViewDimension)
// Call link default colors aren't persisted, so we can use the derived
// color directly instead of making a database query.
let theme = AvatarDefaultColorManager.deriveDefaultColor(useCase: .callLink(rootKey: rootKey))
let renderer = UIGraphicsImageRenderer(size: newSize)
let finalImage = renderer.image { context in
let rect = CGRect(origin: .zero, size: newSize)
let circlePath = UIBezierPath(ovalIn: rect)
theme.backgroundColor.setFill()
circlePath.fill()
context.cgContext.addPath(circlePath.cgPath)
context.cgContext.clip()
theme.foregroundColor.set()
let centerOffset = Constants.circleViewDimension / 2 - Constants.iconDimension / 2
let imageRect = CGRect(
x: centerOffset,
y: centerOffset,
width: Constants.iconDimension,
height: Constants.iconDimension,
)
image.withRenderingMode(.alwaysTemplate).draw(in: imageRect)
}
return finalImage
}
public enum Constants {
public static let circleViewDimension: CGFloat = 64
fileprivate static let iconDimension: CGFloat = 36
}
}