TM-SGNL-iOS/Signal/ConversationView/AudioMessagePresenter.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

160 lines
6.3 KiB
Swift

//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Lottie
import SignalServiceKit
import SignalUI
// Defines the look of audio messages in the conversation view.
class AudioMessagePresenter: AudioPresenter {
let name = "AudioMessageView"
let isIncoming: Bool
let playbackTimeLabel = CVLabel()
let playedDotContainer = ManualLayoutView(name: "playedDotContainer")
let playbackRateView: AudioMessagePlaybackRateView
let audioAttachment: AudioAttachment
let threadUniqueId: String
let audioPlaybackRate: AudioPlaybackRate
init(isIncoming: Bool,
audioAttachment: AudioAttachment,
threadUniqueId: String,
playbackRate: AudioPlaybackRate) {
self.threadUniqueId = threadUniqueId
self.audioPlaybackRate = playbackRate
self.isIncoming = isIncoming
self.audioAttachment = audioAttachment
self.playbackRateView = AudioMessagePlaybackRateView(
threadUniqueId: threadUniqueId,
audioAttachment: audioAttachment,
playbackRate: playbackRate,
isIncoming: isIncoming)
}
var bottomInnerStackSpacing: CGFloat {
switch UIApplication.shared.preferredContentSizeCategory {
case .extraSmall, .small, .medium, .large, .extraLarge:
return 8
default:
return 4
}
}
func playedColor(isIncoming: Bool) -> UIColor {
return isIncoming ? (Theme.isDarkThemeEnabled ? .ows_gray15 : .ows_gray60)
: .ows_white
}
func unplayedColor(isIncoming: Bool) -> UIColor {
return isIncoming ? (Theme.isDarkThemeEnabled ? .ows_gray60 : .ows_gray25) : .ows_whiteAlpha40
}
func thumbColor(isIncoming: Bool) -> UIColor {
return playedColor(isIncoming: isIncoming)
}
func playPauseContainerBackgroundColor(isIncoming: Bool) -> UIColor {
return isIncoming ? (Theme.isDarkThemeEnabled ? .ows_gray60 : .ows_whiteAlpha80) : .ows_whiteAlpha20
}
func playPauseAnimationColor(isIncoming: Bool) -> ColorValueProvider {
ColorValueProvider(thumbColor(isIncoming: isIncoming).lottieColorValue)
}
func playedDotAnimationColor(conversationStyle: ConversationStyle,
isIncoming: Bool) -> ColorValueProvider {
return ColorValueProvider(conversationStyle.bubbleSecondaryTextColor(isIncoming: isIncoming).lottieColorValue)
}
func configureForRendering(conversationStyle: ConversationStyle) {
let playbackTimeLabelConfig = Self.playbackTimeLabelConfig(isIncoming: isIncoming, conversationStyle: conversationStyle)
playbackTimeLabelConfig.applyForRendering(label: playbackTimeLabel)
playbackTimeLabel.setContentHuggingHigh()
}
func bottomSubviewGenerators(conversationStyle: ConversationStyle?) -> [SubviewGenerator] {
struct SubviewConfig {
var playbackTimeLabelMeasurementInfo: ManualStackSubviewInfo
var playedDotContainerMeasurementInfo: ManualStackSubviewInfo
var playbackRateViewMeasurementInfo: ManualStackSubviewInfo
}
let makeSubviewConfig = { [unowned self] (maxWidth: CGFloat) -> SubviewConfig in
let dotSize = CGSize(square: 6)
let playbackTimeLabelConfig = Self.playbackTimeLabelConfig_forMeasurement(
audioAttachment: audioAttachment,
maxWidth: maxWidth
)
let playbackTimeLabelSize = CVText.measureLabel(config: playbackTimeLabelConfig, maxWidth: maxWidth)
let playbackRateSize = AudioMessagePlaybackRateView.measure(maxWidth: maxWidth)
return SubviewConfig(
playbackTimeLabelMeasurementInfo: playbackTimeLabelSize.asManualSubviewInfo(hasFixedSize: true),
playedDotContainerMeasurementInfo: dotSize.asManualSubviewInfo(hasFixedSize: true),
playbackRateViewMeasurementInfo: playbackRateSize.asManualSubviewInfo(hasFixedSize: true))
}
var subviewConfig: SubviewConfig?
let lazySubviewConfig = { maxWidth in
if let subviewConfig {
return subviewConfig
}
let result = makeSubviewConfig(maxWidth)
subviewConfig = result
return result
}
return [
SubviewGenerator(
id: "transparentSpacer1",
measurementInfo: { _ in CGSize.zero.asManualSubviewInfo(hasFixedWidth: true) },
viewGenerator: { UIView.transparentSpacer() }),
SubviewGenerator(
id: "playbackTimeLabel",
measurementInfo: { lazySubviewConfig($0).playbackTimeLabelMeasurementInfo },
viewGenerator: { [unowned self] in self.playbackTimeLabel }),
SubviewGenerator(
id: "playedDotContainer",
measurementInfo: { lazySubviewConfig($0).playedDotContainerMeasurementInfo },
viewGenerator: { [unowned self] in self.playedDotContainer }),
SubviewGenerator(
id: "playbackRateView",
measurementInfo: { lazySubviewConfig($0).playbackRateViewMeasurementInfo },
viewGenerator: { [unowned self] in self.playbackRateView}),
SubviewGenerator(
id: "transparentSpacer2",
measurementInfo: { _ in .empty },
viewGenerator: { UIView.transparentSpacer() })
]
}
func topLabelConfig(audioAttachment: AudioAttachment, isIncoming: Bool, conversationStyle: ConversationStyle?) -> CVLabelConfig? {
guard !audioAttachment.isVoiceMessage else {
return nil
}
let text: String
if let fileName = audioAttachment.sourceFilename?.stripped, !fileName.isEmpty {
text = fileName
} else {
text = OWSLocalizedString("GENERIC_ATTACHMENT_LABEL", comment: "A label for generic attachments.")
}
return CVLabelConfig.unstyledText(
text,
font: Constants.labelFont,
textColor: conversationStyle?.bubbleTextColor(isIncoming: isIncoming) ?? .label
)
}
func audioWaveform(attachmentStream: SignalServiceKit.AttachmentStream?) -> Task<AudioWaveform, Error>? {
return attachmentStream?.audioWaveform()
}
}
private enum Constants {
static let labelFont: UIFont = .dynamicTypeCaption2
}