Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Signal/ConversationView/Components/CVComponentPoll.swift
1 views
//
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation
import SignalServiceKit
public import SignalUI

public class CVComponentPoll: CVComponentBase, CVComponent {
    public var componentKey: CVComponentKey { .poll }
    private let poll: CVComponentState.Poll

    init(
        itemModel: CVItemModel,
        poll: CVComponentState.Poll,
    ) {
        self.poll = poll
        super.init(itemModel: itemModel)
    }

    public func buildComponentView(componentDelegate: any CVComponentDelegate) -> any CVComponentView {
        CVComponentViewPoll(componentDelegate: componentDelegate)
    }

    public func configureForRendering(
        componentView: any CVComponentView,
        cellMeasurement: SignalUI.CVCellMeasurement,
        componentDelegate: any CVComponentDelegate,
    ) {
        guard let componentViewPoll = componentView as? CVComponentViewPoll else {
            owsFailDebug("Unexpected componentView.")
            componentView.reset()
            return
        }

        componentViewPoll.pollView.configureForRendering(
            state: poll.state,
            previousPollState: poll.prevPollState,
            cellMeasurement: cellMeasurement,
            componentDelegate: componentDelegate,
            accessibilitySummary: buildAccessibilityLabel(),
        )
    }

    public func measure(maxWidth: CGFloat, measurementBuilder: SignalUI.CVCellMeasurement.Builder) -> CGSize {
        owsAssertDebug(maxWidth > 0)

        let maxWidth = min(maxWidth, conversationStyle.maxMessageWidth)
        return CVPollView.measure(maxWidth: maxWidth, measurementBuilder: measurementBuilder, state: poll.state)
    }

    // Builds an accessibility label for the entire poll.
    // This label uses basic punctuation which might be used by
    // VoiceOver for pauses/timing.
    //
    // Example: Lilia sent: a poll, what should we have for dinner?
    // Example: You sent: a poll, where should we go on vacation?
    private func buildAccessibilityLabel() -> String {
        var elements = [String]()
        if isIncoming {
            if let accessibilityAuthorName = itemViewState.accessibilityAuthorName {
                let senderFormat = OWSLocalizedString(
                    "CONVERSATION_VIEW_CELL_ACCESSIBILITY_SENDER_FORMAT",
                    comment: "Format for sender info for accessibility label for message. Embeds {{ the sender name }}.",
                )
                elements.append(String.nonPluralLocalizedStringWithFormat(senderFormat, accessibilityAuthorName))
            } else {
                owsFailDebug("Missing accessibilityAuthorName.")
            }
        } else if isOutgoing {
            elements.append(OWSLocalizedString(
                "CONVERSATION_VIEW_CELL_ACCESSIBILITY_SENDER_LOCAL_USER",
                comment: "Format for sender info for outgoing messages.",
            ))
        }

        let formatQuestion = OWSLocalizedString(
            "POLL_ACCESSIBILITY_LABEL",
            comment: "Accessibility label for poll message. Embeds {{ poll question }}.",
        )
        elements.append(String.nonPluralLocalizedStringWithFormat(formatQuestion, poll.state.poll.question))

        let result = elements.joined(separator: " ")
        return result
    }

    // MARK: -

    // Used for rendering some portion of an Conversation View item.
    // It could be the entire item or some part thereof.
    public class CVComponentViewPoll: NSObject, CVComponentView {

        fileprivate var pollView = CVPollView(name: "CVPollView")

        private weak var componentDelegate: CVComponentDelegate?

        public var isDedicatedCellView = false

        public var rootView: UIView {
            pollView
        }

        init(componentDelegate: CVComponentDelegate) {
            self.componentDelegate = componentDelegate
        }

        public func setIsCellVisible(_ isCellVisible: Bool) {}

        public func reset() {
            pollView.reset()
        }
    }
}