Signal-iOS/Signal/ConversationView/LinkPreviewGroupLink.swift
2026-03-31 09:30:34 -04:00

180 lines
5.2 KiB
Swift

//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import SignalServiceKit
import SignalUI
// MARK: -
class LinkPreviewGroupLink: LinkPreviewState {
private let linkPreview: OWSLinkPreview
let linkType: LinkPreviewLinkType
private let groupInviteLinkViewModel: GroupInviteLinkViewModel
private var groupInviteLinkPreview: GroupInviteLinkPreview? {
groupInviteLinkViewModel.groupInviteLinkPreview
}
let conversationStyle: ConversationStyle?
init(
linkType: LinkPreviewLinkType,
linkPreview: OWSLinkPreview,
groupInviteLinkViewModel: GroupInviteLinkViewModel,
conversationStyle: ConversationStyle,
) {
self.linkPreview = linkPreview
self.linkType = linkType
self.groupInviteLinkViewModel = groupInviteLinkViewModel
self.conversationStyle = conversationStyle
}
var isLoaded: Bool { groupInviteLinkPreview != nil }
var urlString: String? {
guard let urlString = linkPreview.urlString else {
owsFailDebug("Missing url")
return nil
}
return urlString
}
var displayDomain: String? {
guard let displayDomain = linkPreview.displayDomain else {
Logger.error("Missing display domain")
return nil
}
return displayDomain
}
var title: String? {
groupInviteLinkPreview?.title.filterForDisplay.nilIfEmpty
}
var imageState: LinkPreviewImageState {
if let avatar = groupInviteLinkViewModel.avatar {
if avatar.isValid {
return .loaded
} else {
return .invalid
}
}
guard groupInviteLinkPreview?.avatarUrlPath != nil else {
return .none
}
return .loading(blurHash: nil)
}
func imageAsync(thumbnailQuality: AttachmentThumbnailQuality, completion: @escaping (UIImage) -> Void) {
owsAssertDebug(imageState == .loaded)
let groupInviteLinkViewModel = self.groupInviteLinkViewModel
DispatchQueue.global().async {
guard let avatar = groupInviteLinkViewModel.avatar, avatar.isValid else {
return
}
guard let image = UIImage(contentsOfFile: avatar.cacheFileUrl.path) else {
owsFailDebug("Couldn't load group avatar.")
return
}
completion(image)
}
}
func imageCacheKey(thumbnailQuality: AttachmentThumbnailQuality) -> LinkPreviewImageCacheKey? {
let urlString = groupInviteLinkViewModel.url.absoluteString
return .init(id: nil, urlString: urlString, thumbnailQuality: thumbnailQuality)
}
private let imagePixelSizeCache = AtomicOptional<CGSize>(nil, lock: .sharedGlobal)
var imagePixelSize: CGSize {
if let cachedValue = imagePixelSizeCache.get() {
return cachedValue
}
guard let avatar = groupInviteLinkViewModel.avatar else {
return CGSize.zero
}
let result = avatar.imageSizePixels
imagePixelSizeCache.set(result)
return result
}
var previewDescription: String? {
guard let groupInviteLinkPreview else {
Logger.warn("Missing groupInviteLinkPreview.")
return nil
}
let groupIndicator = OWSLocalizedString(
"GROUP_LINK_ACTION_SHEET_VIEW_GROUP_INDICATOR",
comment: "Indicator for group conversations in the 'group invite link' action sheet.",
)
let memberCount = GroupViewUtils.formatGroupMembersLabel(memberCount: Int(groupInviteLinkPreview.memberCount), isTerminated: false)
return groupIndicator + " | " + memberCount
}
var date: Date? { linkPreview.date }
var isGroupInviteLink: Bool { true }
var isCallLink: Bool { false }
}
// MARK: -
class GroupInviteLinkViewModel: Equatable {
let url: URL
let groupInviteLinkPreview: GroupInviteLinkPreview?
let avatar: GroupInviteLinkCachedAvatar?
let isExpired: Bool
var isLoaded: Bool {
groupInviteLinkPreview != nil
}
init(
url: URL,
groupInviteLinkPreview: GroupInviteLinkPreview?,
avatar: GroupInviteLinkCachedAvatar?,
isExpired: Bool,
) {
self.url = url
self.groupInviteLinkPreview = groupInviteLinkPreview
self.avatar = avatar
self.isExpired = isExpired
}
static func ==(lhs: GroupInviteLinkViewModel, rhs: GroupInviteLinkViewModel) -> Bool {
return lhs.url == rhs.url &&
lhs.groupInviteLinkPreview == rhs.groupInviteLinkPreview &&
lhs.avatar == rhs.avatar
}
}
// MARK: -
class GroupInviteLinkCachedAvatar: Equatable {
let cacheFileUrl: URL
let imageSizePixels: CGSize
let isValid: Bool
init(
cacheFileUrl: URL,
imageSizePixels: CGSize,
isValid: Bool,
) {
self.cacheFileUrl = cacheFileUrl
self.imageSizePixels = imageSizePixels
self.isValid = isValid
}
static func ==(lhs: GroupInviteLinkCachedAvatar, rhs: GroupInviteLinkCachedAvatar) -> Bool {
return lhs.cacheFileUrl == rhs.cacheFileUrl &&
lhs.imageSizePixels == rhs.imageSizePixels &&
lhs.isValid == rhs.isValid
}
}