Add image to call link previews for sent messages
This commit is contained in:
parent
d30f9be555
commit
752dffb2c1
@ -2670,6 +2670,7 @@
|
||||
E18C4A7729EF2ECC007534D4 /* SignalAccountTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18C4A7629EF2ECC007534D4 /* SignalAccountTest.swift */; };
|
||||
E1A090382A4B909B00F2BE8B /* RecipientHidingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A090372A4B909B00F2BE8B /* RecipientHidingManager.swift */; };
|
||||
E1C2A54B2A8FCB0D00AEC4DA /* DeleteSystemContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C2A54A2A8FCB0D00AEC4DA /* DeleteSystemContactViewController.swift */; };
|
||||
E1CFAAA32C9DD2B1003145C3 /* LinkPreviewCallLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CFAAA22C9DD2B1003145C3 /* LinkPreviewCallLink.swift */; };
|
||||
E1D827D52BD9B6E50022C1AF /* ReactionsBurstView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D827D42BD9B6E50022C1AF /* ReactionsBurstView.swift */; };
|
||||
E1D827D72BD9DA4D0022C1AF /* ReactionsSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D827D62BD9DA4D0022C1AF /* ReactionsSink.swift */; };
|
||||
E1D827DA2BDC1F7B0022C1AF /* ReactionBurstManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D827D82BDC1F6B0022C1AF /* ReactionBurstManagerTests.swift */; };
|
||||
@ -6301,6 +6302,7 @@
|
||||
E1A090372A4B909B00F2BE8B /* RecipientHidingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientHidingManager.swift; sourceTree = "<group>"; };
|
||||
E1A0AD8B16E13FDD0071E604 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
|
||||
E1C2A54A2A8FCB0D00AEC4DA /* DeleteSystemContactViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteSystemContactViewController.swift; sourceTree = "<group>"; };
|
||||
E1CFAAA22C9DD2B1003145C3 /* LinkPreviewCallLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewCallLink.swift; sourceTree = "<group>"; };
|
||||
E1D827D42BD9B6E50022C1AF /* ReactionsBurstView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsBurstView.swift; sourceTree = "<group>"; };
|
||||
E1D827D62BD9DA4D0022C1AF /* ReactionsSink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsSink.swift; sourceTree = "<group>"; };
|
||||
E1D827D82BDC1F6B0022C1AF /* ReactionBurstManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionBurstManagerTests.swift; sourceTree = "<group>"; };
|
||||
@ -7436,6 +7438,7 @@
|
||||
348815C5255346A500D4F4C4 /* CVNode.swift */,
|
||||
D9170EE9290C57BF00CD813A /* CVViewState+Banners.swift */,
|
||||
341D392825472F3B00996E7B /* CVViewState.swift */,
|
||||
E1CFAAA22C9DD2B1003145C3 /* LinkPreviewCallLink.swift */,
|
||||
34A95517271B510400B05242 /* LinkPreviewGroupLink.swift */,
|
||||
346EAA13250199A300E8AB6F /* MemberRequestView.swift */,
|
||||
4CB5F26820F7D060004D1B42 /* MessageActions.swift */,
|
||||
@ -16122,6 +16125,7 @@
|
||||
505C2ED42997015800C23FB2 /* LinkDeviceViewController.swift in Sources */,
|
||||
3437F63A2512835300AC1767 /* LinkedDevicesTableViewController.swift in Sources */,
|
||||
76C87FE128BE8E2400BD8709 /* LinkPreviewAttachmentViewController.swift in Sources */,
|
||||
E1CFAAA32C9DD2B1003145C3 /* LinkPreviewCallLink.swift in Sources */,
|
||||
507B69122C5044F800F1C6D7 /* LinkPreviewGroupLink.swift in Sources */,
|
||||
4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */,
|
||||
4C25768A23AD510800E0398D /* LoadMoreMessagesView.swift in Sources */,
|
||||
|
||||
@ -376,16 +376,7 @@ private class _CreateCallLinkViewController: OWSTableViewController2 {
|
||||
|
||||
private class CallLinkCardView: UIView {
|
||||
private lazy var circleView: UIView = {
|
||||
let circleView = CircleView()
|
||||
circleView.backgroundColor = UIColor(rgbHex: Constants.iconBackgroundColor)
|
||||
circleView.autoSetDimensions(to: CGSize(width: Constants.circleViewDimension, height: Constants.circleViewDimension))
|
||||
|
||||
let iconImageView = UIImageView(image: UIImage(named: "video-compact"))
|
||||
iconImageView.tintColor = UIColor(rgbHex: Constants.iconTintColor)
|
||||
iconImageView.autoSetDimensions(to: CGSize(width: Constants.iconDimension, height: Constants.iconDimension))
|
||||
circleView.addSubview(iconImageView)
|
||||
iconImageView.autoCenterInSuperview()
|
||||
return circleView
|
||||
return SignalUI.CallLinkComponentFactory.callLinkIconView()
|
||||
}()
|
||||
|
||||
private lazy var textStack: UIStackView = {
|
||||
@ -502,9 +493,5 @@ private class CallLinkCardView: UIView {
|
||||
static let spacingTextToButton: CGFloat = 16
|
||||
static let spacingIconToText: CGFloat = 12
|
||||
static let textStackSpacing: CGFloat = 2
|
||||
static let circleViewDimension: CGFloat = 64
|
||||
static let iconDimension: CGFloat = 36
|
||||
static let iconBackgroundColor: UInt32 = 0xE4E4FD
|
||||
static let iconTintColor: UInt32 = 0x5151F6
|
||||
}
|
||||
}
|
||||
|
||||
@ -1574,6 +1574,16 @@ fileprivate extension CVComponentState.Builder {
|
||||
state: state
|
||||
)
|
||||
}
|
||||
} else if let _ = CallLink(url: url) {
|
||||
let state = LinkPreviewCallLink(
|
||||
linkPreview: linkPreview,
|
||||
conversationStyle: conversationStyle
|
||||
)
|
||||
self.linkPreview = LinkPreview(
|
||||
linkPreview: linkPreview,
|
||||
linkPreviewAttachment: nil,
|
||||
state: state
|
||||
)
|
||||
} else {
|
||||
let linkPreviewAttachment = { () -> TSResource? in
|
||||
guard
|
||||
|
||||
72
Signal/ConversationView/LinkPreviewCallLink.swift
Normal file
72
Signal/ConversationView/LinkPreviewCallLink.swift
Normal file
@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import SignalServiceKit
|
||||
import SignalUI
|
||||
|
||||
// MARK: -
|
||||
|
||||
class LinkPreviewCallLink: LinkPreviewState {
|
||||
private let linkPreview: OWSLinkPreview
|
||||
|
||||
public let conversationStyle: ConversationStyle?
|
||||
|
||||
public init(
|
||||
linkPreview: OWSLinkPreview,
|
||||
conversationStyle: ConversationStyle?
|
||||
) {
|
||||
self.linkPreview = linkPreview
|
||||
self.conversationStyle = conversationStyle
|
||||
}
|
||||
|
||||
public var isLoaded: Bool { true }
|
||||
|
||||
public var urlString: String? {
|
||||
guard let urlString = linkPreview.urlString else {
|
||||
owsFailDebug("Missing url")
|
||||
return nil
|
||||
}
|
||||
return urlString
|
||||
}
|
||||
|
||||
public var displayDomain: String? {
|
||||
guard let displayDomain = linkPreview.displayDomain else {
|
||||
Logger.error("Missing display domain")
|
||||
return nil
|
||||
}
|
||||
return displayDomain
|
||||
}
|
||||
|
||||
public var title: String? {
|
||||
linkPreview.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) {}
|
||||
|
||||
public func imageCacheKey(thumbnailQuality: AttachmentThumbnailQuality) -> LinkPreviewImageCacheKey? { return nil }
|
||||
|
||||
public var imagePixelSize: CGSize {
|
||||
// This value does not matter since the image is local for call links.
|
||||
return .zero
|
||||
}
|
||||
|
||||
public var previewDescription: String? {
|
||||
return linkPreview.previewDescription?.filterForDisplay.nilIfEmpty ?? CallStrings.callLinkDescription
|
||||
}
|
||||
|
||||
public var date: Date? { linkPreview.date }
|
||||
|
||||
public let isGroupInviteLink = false
|
||||
public var isCallLink = true
|
||||
|
||||
public var activityIndicatorStyle: UIActivityIndicatorView.Style {
|
||||
LinkPreviewView.defaultActivityIndicatorStyle
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,6 @@ import SignalUI
|
||||
// MARK: -
|
||||
|
||||
class LinkPreviewGroupLink: LinkPreviewState {
|
||||
|
||||
private let linkPreview: OWSLinkPreview
|
||||
public let linkType: LinkPreviewLinkType
|
||||
private let groupInviteLinkViewModel: GroupInviteLinkViewModel
|
||||
@ -122,6 +121,7 @@ class LinkPreviewGroupLink: LinkPreviewState {
|
||||
var date: Date? { linkPreview.date }
|
||||
|
||||
let isGroupInviteLink = true
|
||||
public let isCallLink = false
|
||||
|
||||
var activityIndicatorStyle: UIActivityIndicatorView.Style {
|
||||
switch linkType {
|
||||
|
||||
@ -476,6 +476,13 @@ public enum CallStrings {
|
||||
comment: "Shown in the header when the user hasn't provided a custom name for a call."
|
||||
)
|
||||
}
|
||||
|
||||
public static var callLinkDescription: String {
|
||||
return OWSLocalizedString(
|
||||
"CALL_LINK_LINK_PREVIEW_DESCRIPTION",
|
||||
comment: "Shown in a message bubble when you send a call link in a Signal chat"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@ -310,10 +310,7 @@ public class LinkPreviewFetcherImpl: LinkPreviewFetcher {
|
||||
let callLinkState = try await CallLinkFetcherImpl().readCallLink(callLink.rootKey, authCredential: authCredential)
|
||||
return (
|
||||
callLinkState.localizedName,
|
||||
OWSLocalizedString(
|
||||
"CALL_LINK_LINK_PREVIEW_DESCRIPTION",
|
||||
comment: "Shown in a message bubble when you send a call link in a Signal chat"
|
||||
)
|
||||
CallStrings.callLinkDescription
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ public protocol LinkPreviewState: AnyObject {
|
||||
var previewDescription: String? { get }
|
||||
var date: Date? { get }
|
||||
var isGroupInviteLink: Bool { get }
|
||||
var isCallLink: Bool { get }
|
||||
var activityIndicatorStyle: UIActivityIndicatorView.Style { get }
|
||||
var conversationStyle: ConversationStyle? { get }
|
||||
}
|
||||
@ -108,6 +109,8 @@ public class LinkPreviewLoading: LinkPreviewState {
|
||||
}
|
||||
}
|
||||
|
||||
public let isCallLink: Bool = false
|
||||
|
||||
public var activityIndicatorStyle: UIActivityIndicatorView.Style {
|
||||
switch linkType {
|
||||
case .incomingMessageGroupInviteLink:
|
||||
@ -203,6 +206,7 @@ public class LinkPreviewDraft: LinkPreviewState {
|
||||
public var date: Date? { linkPreviewDraft.date }
|
||||
|
||||
public let isGroupInviteLink = false
|
||||
public let isCallLink: Bool = false
|
||||
|
||||
public var activityIndicatorStyle: UIActivityIndicatorView.Style {
|
||||
LinkPreviewView.defaultActivityIndicatorStyle
|
||||
@ -332,6 +336,7 @@ public class LinkPreviewSent: LinkPreviewState {
|
||||
public var date: Date? { linkPreview.date }
|
||||
|
||||
public let isGroupInviteLink = false
|
||||
public var isCallLink = false
|
||||
|
||||
public var activityIndicatorStyle: UIActivityIndicatorView.Style {
|
||||
LinkPreviewView.defaultActivityIndicatorStyle
|
||||
|
||||
@ -139,6 +139,8 @@ public class LinkPreviewView: ManualStackViewWithLayer {
|
||||
return LinkPreviewViewAdapterDraft(state: state)
|
||||
} else if state.isGroupInviteLink {
|
||||
return LinkPreviewViewAdapterGroupLink(state: state)
|
||||
} else if state.isCallLink {
|
||||
return LinkPreviewViewAdapterCallLink(state: state)
|
||||
} else {
|
||||
if state.hasLoadedImage {
|
||||
if Self.sentIsHero(state: state) {
|
||||
@ -912,6 +914,143 @@ private class LinkPreviewViewAdapterGroupLink: LinkPreviewViewAdapter {
|
||||
|
||||
// MARK: -
|
||||
|
||||
private class LinkPreviewViewAdapterCallLink: LinkPreviewViewAdapter {
|
||||
|
||||
let state: LinkPreviewState
|
||||
|
||||
init(state: LinkPreviewState) {
|
||||
self.state = state
|
||||
}
|
||||
|
||||
var rootStackConfig: ManualStackView.Config {
|
||||
ManualStackView.Config(
|
||||
axis: .horizontal,
|
||||
alignment: .fill,
|
||||
spacing: LinkPreviewView.sentNonHeroHSpacing,
|
||||
layoutMargins: LinkPreviewView.sentNonHeroLayoutMargins
|
||||
)
|
||||
}
|
||||
|
||||
var textStackConfig: ManualStackView.Config {
|
||||
return ManualStackView.Config(
|
||||
axis: .vertical,
|
||||
alignment: .leading,
|
||||
spacing: LinkPreviewView.sentVSpacing,
|
||||
layoutMargins: .zero
|
||||
)
|
||||
}
|
||||
|
||||
func configureForRendering(
|
||||
linkPreviewView: LinkPreviewView,
|
||||
hasAsymmetricalRounding: Bool,
|
||||
cellMeasurement: CVCellMeasurement
|
||||
) {
|
||||
|
||||
linkPreviewView.backgroundColor = Theme.secondaryBackgroundColor
|
||||
|
||||
var rootStackSubviews = [UIView]()
|
||||
|
||||
let iconView = CallLinkComponentFactory.callLinkIconView()
|
||||
iconView.contentMode = .scaleAspectFill
|
||||
iconView.clipsToBounds = true
|
||||
rootStackSubviews.append(iconView)
|
||||
|
||||
let textStack = linkPreviewView.textStack
|
||||
var textStackSubviews = [UIView]()
|
||||
if let titleLabel = sentTitleLabel(state: state) {
|
||||
textStackSubviews.append(titleLabel)
|
||||
}
|
||||
if let descriptionLabel = sentDescriptionLabel(state: state) {
|
||||
textStackSubviews.append(descriptionLabel)
|
||||
}
|
||||
textStack.configure(
|
||||
config: textStackConfig,
|
||||
cellMeasurement: cellMeasurement,
|
||||
measurementKey: Self.measurementKey_textStack,
|
||||
subviews: textStackSubviews
|
||||
)
|
||||
rootStackSubviews.append(textStack)
|
||||
|
||||
linkPreviewView.configure(
|
||||
config: rootStackConfig,
|
||||
cellMeasurement: cellMeasurement,
|
||||
measurementKey: Self.measurementKey_rootStack,
|
||||
subviews: rootStackSubviews
|
||||
)
|
||||
}
|
||||
|
||||
func measure(
|
||||
maxWidth: CGFloat,
|
||||
measurementBuilder: CVCellMeasurement.Builder,
|
||||
state: LinkPreviewState
|
||||
) -> CGSize {
|
||||
|
||||
var maxLabelWidth = (maxWidth - (
|
||||
textStackConfig.layoutMargins.totalWidth + rootStackConfig.layoutMargins.totalWidth
|
||||
))
|
||||
|
||||
var rootStackSubviewInfos = [ManualStackSubviewInfo]()
|
||||
|
||||
let imageSize = LinkPreviewView.sentNonHeroImageSize
|
||||
rootStackSubviewInfos.append(CGSize.square(imageSize).asManualSubviewInfo(hasFixedSize: true))
|
||||
maxLabelWidth -= imageSize + rootStackConfig.spacing
|
||||
|
||||
maxLabelWidth = max(0, maxLabelWidth)
|
||||
|
||||
var textStackSubviewInfos = [ManualStackSubviewInfo]()
|
||||
if let labelConfig = sentTitleLabelConfig(state: state) {
|
||||
let labelSize = CVText.measureLabel(config: labelConfig, maxWidth: maxLabelWidth)
|
||||
textStackSubviewInfos.append(labelSize.asManualSubviewInfo)
|
||||
}
|
||||
if let labelConfig = sentDescriptionLabelConfig(state: state) {
|
||||
let labelSize = CVText.measureLabel(config: labelConfig, maxWidth: maxLabelWidth)
|
||||
textStackSubviewInfos.append(labelSize.asManualSubviewInfo)
|
||||
}
|
||||
|
||||
let textStackMeasurement = ManualStackView.measure(
|
||||
config: textStackConfig,
|
||||
measurementBuilder: measurementBuilder,
|
||||
measurementKey: Self.measurementKey_textStack,
|
||||
subviewInfos: textStackSubviewInfos
|
||||
)
|
||||
rootStackSubviewInfos.append(textStackMeasurement.measuredSize.asManualSubviewInfo)
|
||||
|
||||
let rootStackMeasurement = ManualStackView.measure(
|
||||
config: rootStackConfig,
|
||||
measurementBuilder: measurementBuilder,
|
||||
measurementKey: Self.measurementKey_rootStack,
|
||||
subviewInfos: rootStackSubviewInfos,
|
||||
maxWidth: maxWidth
|
||||
)
|
||||
return rootStackMeasurement.measuredSize
|
||||
}
|
||||
}
|
||||
|
||||
public class CallLinkComponentFactory {
|
||||
public static func callLinkIconView() -> UIView {
|
||||
let circleView = CircleView()
|
||||
circleView.backgroundColor = UIColor(rgbHex: Constants.iconBackgroundColor)
|
||||
circleView.autoSetDimensions(to: CGSize(width: Constants.circleViewDimension, height: Constants.circleViewDimension))
|
||||
|
||||
let iconImageView = UIImageView(image: UIImage(named: "video-compact"))
|
||||
iconImageView.tintColor = UIColor(rgbHex: Constants.iconTintColor)
|
||||
iconImageView.autoSetDimensions(to: CGSize(width: Constants.iconDimension, height: Constants.iconDimension))
|
||||
circleView.addSubview(iconImageView)
|
||||
iconImageView.autoCenterInSuperview()
|
||||
|
||||
return circleView
|
||||
}
|
||||
|
||||
private enum Constants {
|
||||
static let circleViewDimension: CGFloat = 64
|
||||
static let iconDimension: CGFloat = 36
|
||||
static let iconBackgroundColor: UInt32 = 0xE4E4FD
|
||||
static let iconTintColor: UInt32 = 0x5151F6
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private class LinkPreviewViewAdapterSentHero: LinkPreviewViewAdapter {
|
||||
|
||||
let state: LinkPreviewState
|
||||
|
||||
Loading…
Reference in New Issue
Block a user