Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Signal/src/ViewControllers/AppSettings/Donations/DonationReceiptViewController.swift
1 views
//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import SignalServiceKit
import SignalUI

class DonationReceiptViewController: OWSTableViewController2 {
    let model: DonationReceipt
    let signalLogoView: UIImageView = {
        let view = UIImageView()
        view.autoSetDimensions(to: CGSize(width: 100, height: 31))
        view.contentMode = .scaleAspectFit
        return view
    }()

    private lazy var shareReceiptButton = UIButton(
        configuration: .largePrimary(title: OWSLocalizedString(
            "DONATION_RECEIPT_EXPORT_RECEIPT_BUTTON",
            comment: "Text on the button that exports the receipt",
        )),
        primaryAction: UIAction { [weak self] _ in
            self?.showShareReceiptActivity()
        },
    )

    private lazy var shareReceiptButtonContainer: UIView = {
        let stackView = UIStackView.verticalButtonStack(buttons: [shareReceiptButton])
        let containerView = UIView()
        containerView.preservesSuperviewLayoutMargins = true
        containerView.addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: containerView.topAnchor),
            stackView.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor),
            stackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
        ])
        return containerView
    }()

    private lazy var dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .none
        return formatter
    }()

    init(model: DonationReceipt) {
        self.model = model
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        title = OWSLocalizedString("DONATION_RECEIPT_DETAILS", comment: "Title on the view where you can see a single receipt")

        updateTableContents()
        updateSignalLogoImage()
    }

    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.themeDidChange()
        if previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle {
            updateSignalLogoImage()
        }
    }

    private func updateSignalLogoImage() {
        let signalLogoImage = UIImage(named: "signal-full-logo")
        if traitCollection.userInterfaceStyle == .dark {
            signalLogoView.image = signalLogoImage?.tintedImage(color: .ows_white)
        } else {
            signalLogoView.image = signalLogoImage
        }
    }

    // MARK: - Rendering table contents

    private func updateTableContents() {
        self.contents = OWSTableContents(sections: [amountSection(), detailsSection()])
    }

    private func amountSection() -> OWSTableSection {
        OWSTableSection(items: [
            OWSTableItem(customCellBlock: {
                let model = self.model

                let amountLabel = UILabel()
                amountLabel.text = CurrencyFormatter.format(money: model.amount)
                amountLabel.textColor = .Signal.label
                amountLabel.font = .dynamicTypeLargeTitle1Clamped
                amountLabel.adjustsFontForContentSizeCategory = true

                let content = UIStackView(arrangedSubviews: [self.signalLogoView, amountLabel])
                content.axis = .vertical
                content.alignment = .center
                content.spacing = 12

                let cell = OWSTableItem.newCell()
                cell.contentView.addSubview(content)

                content.autoPinEdgesToSuperviewMargins()

                return cell
            }),
        ])
    }

    private func detailsSection() -> OWSTableSection {
        OWSTableSection(items: [
            .item(
                name: OWSLocalizedString("DONATION_RECEIPT_TYPE", comment: "Section title for donation type on receipts"),
                subtitle: model.localizedName,
                accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "donation_receipt_details_type"),
            ),
            .item(
                name: OWSLocalizedString("DONATION_RECEIPT_DATE_PAID", comment: "Section title for donation date on receipts"),
                subtitle: dateFormatter.string(from: model.timestamp),
                accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "donation_receipt_details_date"),
            ),
        ])
    }

    // MARK: - Share button

    override open var bottomFooter: UIView? {
        get { shareReceiptButtonContainer }
        set {}
    }

    private func showShareReceiptActivity() {
        ShareActivityUtil.present(
            activityItems: [DonationReceiptImageActivityItemProvider(donationReceipt: model)],
            from: self,
            sourceView: shareReceiptButton,
        )
    }

    // MARK: - Donation receipt image activity provider

    private class DonationReceiptImageActivityItemProvider: UIActivityItemProvider, @unchecked Sendable {
        let donationReceiptImage: UIImage

        override var item: Any { donationReceiptImage }

        init(donationReceipt: DonationReceipt) {
            donationReceiptImage = Self.getDonationReceiptImage(donationReceipt: donationReceipt)
            super.init(placeholderItem: donationReceiptImage)
        }

        // MARK: Image creation

        private class func getDonationReceiptImage(donationReceipt: DonationReceipt) -> UIImage {
            let view = Self.makeView(donationReceipt: donationReceipt)
            let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
            return renderer.image { _ in
                view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
            }
        }

        // MARK: View creation

        private class func makeView(donationReceipt: DonationReceipt) -> UIView {
            let stackView = UIStackView()
            stackView.backgroundColor = .white
            stackView.isOpaque = true
            stackView.axis = .vertical
            stackView.alignment = .fill

            let subviewsWithSpacings: [(UIView, CGFloat)] = [
                (Self.headerView(), 12),
                (Self.dividerView(color: .ows_gray15), 37),
                (Self.titleView(), 24),
                (Self.amountView(donationReceipt: donationReceipt), 20),
                (Self.dividerView(color: .ows_gray90), 22),
                (Self.donationTypeView(donationReceipt: donationReceipt), 10),
                (Self.dividerView(color: .ows_gray20), 20),
                (Self.datePaidView(donationReceipt: donationReceipt), 22),
                (Self.footerView(), 0),
            ]
            for (subview, spacing) in subviewsWithSpacings {
                stackView.addArrangedSubview(subview)
                stackView.setCustomSpacing(spacing, after: subview)
            }

            let containerWidth: CGFloat = 612
            let containerMargins = UIEdgeInsets(hMargin: 65, vMargin: 32)

            let stackViewSize = stackView.systemLayoutSizeFitting(
                CGSize(
                    width: containerWidth - containerMargins.leading - containerMargins.trailing,
                    height: CGFloat.greatestFiniteMagnitude,
                ),
                withHorizontalFittingPriority: .required,
                verticalFittingPriority: .fittingSizeLevel,
            )
            let containerView = UIView()
            containerView.frame.size = CGSize(
                width: stackViewSize.width + containerMargins.leading + containerMargins.trailing,
                height: stackViewSize.height + containerMargins.top + containerMargins.bottom,
            )
            containerView.backgroundColor = .white
            containerView.isOpaque = true
            containerView.addSubview(stackView)
            stackView.autoPinEdgesToSuperviewEdges(with: containerMargins)

            return containerView
        }

        private class func headerView() -> UIView {
            let signalLogo = UIImage(named: "signal-full-logo")
            let signalLogoView = UIImageView(image: signalLogo)

            signalLogoView.autoSetDimensions(to: CGSize(width: 140, height: 40))

            let currentDateView = label(
                dateFormatter().string(from: Date()),
                fontSize: 13,
                textColor: .ows_gray60,
                isAlignedToEdge: true,
            )

            let headerView = UIStackView(arrangedSubviews: [signalLogoView, currentDateView])
            headerView.axis = .horizontal
            headerView.alignment = .center
            headerView.distribution = .fill

            return headerView
        }

        private class func titleView() -> UIView {
            label(
                OWSLocalizedString("DONATION_RECEIPT_TITLE", comment: "Title on donation receipts"),
                fontSize: 20,
            )
        }

        private class func amountView(donationReceipt: DonationReceipt) -> UIView {
            let arrangedSubviews = [
                label(OWSLocalizedString("DONATION_RECEIPT_AMOUNT", comment: "Section title for donation amount on receipts")),
                label(CurrencyFormatter.format(money: donationReceipt.amount), isAlignedToEdge: true),
            ]
            let amountView = UIStackView(arrangedSubviews: arrangedSubviews)
            amountView.axis = .horizontal
            amountView.alignment = .leading
            amountView.distribution = .fillProportionally
            return amountView
        }

        private class func donationTypeView(donationReceipt: DonationReceipt) -> UIView {
            detailView(
                title: OWSLocalizedString("DONATION_RECEIPT_TYPE", comment: "Section title for donation type on receipts"),
                subtitle: donationReceipt.localizedName,
            )
        }

        private class func datePaidView(donationReceipt: DonationReceipt) -> UIView {
            detailView(
                title: OWSLocalizedString("DONATION_RECEIPT_DATE_PAID", comment: "Section title for donation date on receipts"),
                subtitle: dateFormatter().string(from: donationReceipt.timestamp),
            )
        }

        private class func footerView() -> UIView {
            label(
                OWSLocalizedString("DONATION_RECEIPT_FOOTER", comment: "Footer text at the bottom of donation receipts"),
                fontSize: 12,
                textColor: .ows_gray60,
            )
        }

        private class func label(
            _ text: String,
            fontSize: CGFloat = 17,
            textColor: UIColor = .ows_gray95,
            isAlignedToEdge: Bool = false,
        ) -> UILabel {
            let result = UILabel()
            result.text = text
            result.textColor = textColor
            result.font = UIFont(name: "Inter-Regular_Medium", size: fontSize)
            result.numberOfLines = 0
            if isAlignedToEdge {
                result.textAlignment = CurrentAppContext().isRTL ? .left : .right
            }
            return result
        }

        private class func dividerView(color: UIColor) -> UIView {
            let divider = UIView()
            divider.backgroundColor = color
            divider.autoSetDimension(.height, toSize: 1)
            return divider
        }

        private class func detailView(title: String, subtitle: String) -> UIView {
            let arrangedSubviews = [
                label(title),
                label(subtitle, fontSize: 13, textColor: .ows_gray45),
            ]
            let detailView = UIStackView(arrangedSubviews: arrangedSubviews)
            detailView.axis = .vertical
            detailView.alignment = .leading
            return detailView
        }

        private class func dateFormatter() -> DateFormatter {
            let dateFormatter = DateFormatter()
            dateFormatter.dateStyle = .medium
            dateFormatter.timeStyle = .none
            return dateFormatter
        }
    }
}