// // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import SignalServiceKit public import SignalUI public class CVComponentBottomButtons: CVComponentBase, CVComponent { public var componentKey: CVComponentKey { .bottomButtons } private let bottomButtonsState: CVComponentState.BottomButtons typealias Action = CVMessageAction fileprivate var actions: [Action] { bottomButtonsState.actions } init(itemModel: CVItemModel, bottomButtonsState: CVComponentState.BottomButtons) { self.bottomButtonsState = bottomButtonsState super.init(itemModel: itemModel) } public func buildComponentView(componentDelegate: CVComponentDelegate) -> CVComponentView { CVComponentViewBottomButtons() } public func configureForRendering( componentView componentViewParam: CVComponentView, cellMeasurement: CVCellMeasurement, componentDelegate: CVComponentDelegate, ) { guard let componentView = componentViewParam as? CVComponentViewBottomButtons else { owsFailDebug("Unexpected componentView.") componentViewParam.reset() return } componentView.reset() var subviews = [UIView]() for action in actions { let buttonView = CVMessageActionButton(action: action) if isIncoming { if conversationStyle.hasWallpaper { buttonView.backgroundColor = .Signal.MaterialBase.button } else { buttonView.backgroundColor = .Signal.LightBase.button } buttonView.textColor = .Signal.label } else { buttonView.backgroundColor = .Signal.ColorBase.button buttonView.textColor = .Signal.ColorBase.labelPrimary } subviews.append(buttonView) componentView.buttonViews.append(buttonView) } let stackView = componentView.stackView stackView.reset() stackView.configure( config: stackConfig, cellMeasurement: cellMeasurement, measurementKey: Self.measurementKey_stackView, subviews: subviews, ) } private var stackConfig: CVStackViewConfig { CVStackViewConfig( axis: .vertical, alignment: .fill, spacing: Self.buttonSpacing, layoutMargins: .init(top: 6, leading: 12, bottom: 12, trailing: 12), ) } fileprivate static var buttonHeight: CGFloat { CVMessageActionButton.buttonHeight } fileprivate static let buttonSpacing: CGFloat = 4 private static let measurementKey_stackView = "CVComponentBottomButtons.measurementKey_stackView" public func measure(maxWidth: CGFloat, measurementBuilder: CVCellMeasurement.Builder) -> CGSize { owsAssertDebug(maxWidth > 0) let subviewSize = CGSize(width: maxWidth - stackConfig.layoutMargins.totalWidth, height: Self.buttonHeight) var subviewInfos = [ManualStackSubviewInfo]() for _ in 0.. Bool { guard let componentView = componentView as? CVComponentViewBottomButtons else { owsFailDebug("Unexpected componentView.") return false } for buttonView in componentView.buttonViews { let location = sender.location(in: buttonView) guard buttonView.bounds.contains(location) else { continue } buttonView.action.perform(delegate: componentDelegate) return true } return false } // MARK: - private class CVMessageActionButton: CVLabel { let action: CVMessageAction init(action: CVMessageAction) { self.action = action super.init(frame: .zero) configure() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func configure() { layoutMargins = .zero layer.masksToBounds = true layer.cornerRadius = Self.buttonHeight / 2 text = action.title font = Self.buttonFont textAlignment = .center } private static var buttonFont: UIFont { UIFont.dynamicTypeFootnoteClamped.medium() } private static let buttonVMargin: CGFloat = 5 static var buttonHeight: CGFloat { ceil(buttonFont.lineHeight + buttonVMargin * 2).clamp(28, 44) } } private class CVComponentViewBottomButtons: NSObject, CVComponentView { let stackView = ManualStackView(name: "bottomButtons") var buttonViews = [CVMessageActionButton]() var isDedicatedCellView = false var rootView: UIView { stackView } func setIsCellVisible(_ isCellVisible: Bool) {} func reset() { stackView.reset() buttonViews.removeAll() } } }