diff --git a/Signal/src/ViewControllers/ConversationView/CV/CVComponent.swift b/Signal/src/ViewControllers/ConversationView/CV/CVComponent.swift index cc26d27d5c..073498bb0a 100644 --- a/Signal/src/ViewControllers/ConversationView/CV/CVComponent.swift +++ b/Signal/src/ViewControllers/ConversationView/CV/CVComponent.swift @@ -130,6 +130,10 @@ public struct CVCellMeasurement: Equatable { values[key] } + public var debugDescription: String { + "[cellSize: \(cellSize), sizes: \(sizes), values: \(values)]" + } + public func debugLog() { Logger.verbose("cellSize: \(cellSize)") Logger.verbose("sizes: \(sizes)") diff --git a/Signal/src/ViewControllers/ConversationView/CV/CVComponents/CVComponentAudioAttachment.swift b/Signal/src/ViewControllers/ConversationView/CV/CVComponents/CVComponentAudioAttachment.swift index b48ac078bf..e9e55d91aa 100644 --- a/Signal/src/ViewControllers/ConversationView/CV/CVComponents/CVComponentAudioAttachment.swift +++ b/Signal/src/ViewControllers/ConversationView/CV/CVComponents/CVComponentAudioAttachment.swift @@ -60,7 +60,10 @@ public class CVComponentAudioAttachment: CVComponentBase, CVComponent { public func measure(maxWidth: CGFloat, measurementBuilder: CVCellMeasurement.Builder) -> CGSize { owsAssertDebug(maxWidth > 0) - return CGSize(width: maxWidth, height: AudioMessageView.bubbleHeight).ceil + let height = AudioMessageView.measureHeight(audioAttachment: audioAttachment, + isIncoming: isIncoming, + conversationStyle: conversationStyle) + return CGSize(width: maxWidth, height: height).ceil } // MARK: - Events diff --git a/Signal/src/ViewControllers/ConversationView/CV/OWSStackView.swift b/Signal/src/ViewControllers/ConversationView/CV/OWSStackView.swift index c179e81819..2d4615ad1a 100644 --- a/Signal/src/ViewControllers/ConversationView/CV/OWSStackView.swift +++ b/Signal/src/ViewControllers/ConversationView/CV/OWSStackView.swift @@ -11,9 +11,13 @@ class OWSStackView: UIStackView { public var layoutBlock: LayoutBlock? @objc - public required init(name: String) { + public required init(name: String, arrangedSubviews: [UIView] = []) { super.init(frame: .zero) + for subview in arrangedSubviews { + addArrangedSubview(subview) + } + #if TESTABLE_BUILD self.accessibilityLabel = name #endif diff --git a/Signal/src/ViewControllers/ConversationView/Cells/AudioMessageView.swift b/Signal/src/ViewControllers/ConversationView/Cells/AudioMessageView.swift index 0a90d0bc80..10c232d282 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/AudioMessageView.swift +++ b/Signal/src/ViewControllers/ConversationView/Cells/AudioMessageView.swift @@ -5,7 +5,7 @@ import Foundation import Lottie -class AudioMessageView: UIStackView { +class AudioMessageView: OWSStackView { private let audioAttachment: AudioAttachment private var attachment: TSAttachment { audioAttachment.attachment } @@ -37,23 +37,15 @@ class AudioMessageView: UIStackView { self.isIncoming = isIncoming self.conversationStyle = conversationStyle - super.init(frame: .zero) + super.init(name: "AudioMessageView") - axis = .vertical - spacing = AudioMessageView.vSpacing - - if !attachment.isVoiceMessage { - let topText: String - if let fileName = attachment.sourceFilename?.stripped, !fileName.isEmpty { - topText = fileName - } else { - topText = NSLocalizedString("GENERIC_ATTACHMENT_LABEL", comment: "A label for generic attachments.") - } + self.apply(config: Self.outerStackConfig) + if let topLabelConfig = Self.topLabelConfig(audioAttachment: audioAttachment, + isIncoming: isIncoming, + conversationStyle: conversationStyle) { let topLabel = UILabel() - topLabel.text = topText - topLabel.textColor = conversationStyle.bubbleTextColor(isIncoming: isIncoming) - topLabel.font = AudioMessageView.labelFont + topLabelConfig.applyForRendering(label: topLabel) addArrangedSubview(topLabel) } @@ -65,7 +57,7 @@ class AudioMessageView: UIStackView { playPauseAnimation.animationSpeed = 3 playPauseAnimation.backgroundBehavior = .forceFinish playPauseAnimation.contentMode = .scaleAspectFit - playPauseAnimation.autoSetDimensions(to: CGSize(square: animationSize)) + playPauseAnimation.autoSetDimensions(to: CGSize(square: Self.animationSize)) playPauseAnimation.setContentHuggingHigh() let fillColorKeypath = AnimationKeypath(keypath: "**.Fill 1.Color") @@ -89,21 +81,14 @@ class AudioMessageView: UIStackView { progressSlider.autoSetDimension(.height, toSize: 12) progressSlider.autoVCenterInSuperview() - playbackTimeLabel.textColor = conversationStyle.bubbleSecondaryTextColor(isIncoming: isIncoming) - playbackTimeLabel.font = UIFont.ows_dynamicTypeCaption1.ows_monospaced + Self.playbackTimeLabelConfig(isIncoming: isIncoming, + conversationStyle: conversationStyle).applyForRendering(label: playbackTimeLabel) playbackTimeLabel.setContentHuggingHigh() - let playerStack = UIStackView(arrangedSubviews: [playPauseAnimation, waveformContainer, playbackTimeLabel]) - playerStack.isLayoutMarginsRelativeArrangement = true - playerStack.layoutMargins = UIEdgeInsets( - top: AudioMessageView.vMargin, - leading: hMargin, - bottom: AudioMessageView.vMargin, - trailing: hMargin - ) - playerStack.spacing = hSpacing - - addArrangedSubview(playerStack) + let innerStack = OWSStackView(name: "playerStack", + arrangedSubviews: [playPauseAnimation, waveformContainer, playbackTimeLabel]) + innerStack.apply(config: Self.innerStackConfig) + addArrangedSubview(innerStack) waveformContainer.autoAlignAxis(.horizontal, toSameAxisOf: playPauseAnimation) @@ -112,8 +97,75 @@ class AudioMessageView: UIStackView { audioPlayer.addListener(self) } - required init(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + @available(swift, obsoleted: 1.0) + required init(name: String, arrangedSubviews: [UIView] = []) { + owsFail("Do not use this initializer.") + } + + private static var outerStackConfig: CVStackViewConfig { + CVStackViewConfig(axis: .vertical, + alignment: .fill, + spacing: vSpacing, + layoutMargins: .zero) + } + + private static var innerStackConfig: CVStackViewConfig { + CVStackViewConfig(axis: .horizontal, + alignment: .center, + spacing: 12, + layoutMargins: innerLayoutMargins) + } + + private static func topLabelConfig(audioAttachment: AudioAttachment, + isIncoming: Bool, + conversationStyle: ConversationStyle) -> CVLabelConfig? { + + let attachment = audioAttachment.attachment + guard !attachment.isVoiceMessage else { + return nil + } + + let text: String + if let fileName = attachment.sourceFilename?.stripped, !fileName.isEmpty { + text = fileName + } else { + text = NSLocalizedString("GENERIC_ATTACHMENT_LABEL", comment: "A label for generic attachments.") + } + + return CVLabelConfig(text: text, + font: labelFont, + textColor: conversationStyle.bubbleTextColor(isIncoming: isIncoming)) + } + + private static func playbackTimeLabelConfig(isIncoming: Bool, + conversationStyle: ConversationStyle) -> CVLabelConfig { + return CVLabelConfig(text: " ", + font: UIFont.ows_dynamicTypeCaption1.ows_monospaced, + textColor: conversationStyle.bubbleSecondaryTextColor(isIncoming: isIncoming)) + } + + @objc + static func measureHeight(audioAttachment: AudioAttachment, + isIncoming: Bool, + conversationStyle: ConversationStyle) -> CGFloat { + + var outerSubviewSizes = [CGSize]() + if let topLabelConfig = Self.topLabelConfig(audioAttachment: audioAttachment, + isIncoming: isIncoming, + conversationStyle: conversationStyle) { + outerSubviewSizes.append(CGSize(width: 0, height: topLabelConfig.font.lineHeight)) + } + + let playPauseAnimationSize = CGSize(square: animationSize) + let waveformSize = CGSize(width: 0, height: waveformHeight) + let playbackTimeLabelSize = CGSize(width: 0, height: playbackTimeLabelConfig(isIncoming: isIncoming, + conversationStyle: conversationStyle).font.lineHeight) + let innerSubviewSizes = [playPauseAnimationSize, waveformSize, playbackTimeLabelSize] + let innerStackSize = CVStackView.measure(config: innerStackConfig, subviewSizes: innerSubviewSizes) + outerSubviewSizes.append(innerStackSize) + + let outerStackSize = CVStackView.measure(config: outerStackConfig, subviewSizes: outerSubviewSizes) + return outerStackSize.height } // MARK: - Scrubbing @@ -157,12 +209,12 @@ class AudioMessageView: UIStackView { private static var labelFont: UIFont = .ows_dynamicTypeCaption2 private static var waveformHeight: CGFloat = 35 - private static var vMargin: CGFloat = 4 - private var animationSize: CGFloat = 28 + private static var animationSize: CGFloat = 28 private var iconSize: CGFloat = 24 - private var hMargin: CGFloat = 0 - private var hSpacing: CGFloat = 12 private static var vSpacing: CGFloat = 2 + private static var innerLayoutMargins: UIEdgeInsets { + UIEdgeInsets(hMargin: 0, vMargin: 4) + } private lazy var playedColor: UIColor = isIncoming ? .init(rgbHex: 0x92caff) : .ows_white private lazy var unplayedColor: UIColor = @@ -175,11 +227,6 @@ class AudioMessageView: UIStackView { // of playback scrubbing. private var overrideProgress: CGFloat? - @objc - static var bubbleHeight: CGFloat { - return labelFont.lineHeight + waveformHeight + vSpacing + (vMargin * 2) - } - func updateContents(animated: Bool) { updatePlaybackState(animated: animated) updateAudioProgress() diff --git a/Signal/src/ViewControllers/ConversationView/Cells/CVReactionCountsView.swift b/Signal/src/ViewControllers/ConversationView/Cells/CVReactionCountsView.swift index 5341408242..1f45006a12 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/CVReactionCountsView.swift +++ b/Signal/src/ViewControllers/ConversationView/Cells/CVReactionCountsView.swift @@ -48,8 +48,8 @@ class CVReactionCountsView: OWSStackView { } @available(swift, obsoleted: 1.0) - required init(name: String) { - super.init(name: name) + required init(name: String, arrangedSubviews: [UIView] = []) { + super.init(name: name, arrangedSubviews: arrangedSubviews) } static var stackConfig: CVStackViewConfig {