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
}
}
}