It doesn't do anything yet, but an image and some text is there. This is all behind a feature flag, so this should have no user impact.
336 lines
14 KiB
Swift
336 lines
14 KiB
Swift
//
|
|
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import UIKit
|
|
|
|
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
|
|
}()
|
|
|
|
let shareReceiptButton: OWSButton = {
|
|
let button = OWSButton()
|
|
button.setTitle(NSLocalizedString("DONATION_RECEIPT_EXPORT_RECEIPT_BUTTON", comment: "Text on the button that exports the receipt"),
|
|
for: .normal)
|
|
button.titleLabel?.font = .ows_dynamicTypeBodyClamped.ows_semibold
|
|
button.clipsToBounds = true
|
|
button.layer.cornerRadius = 8
|
|
button.contentEdgeInsets = UIEdgeInsets(top: 13, leading: 13, bottom: 13, trailing: 13)
|
|
button.dimsWhenHighlighted = true
|
|
button.backgroundColor = .ows_accentBlue
|
|
|
|
return button
|
|
}()
|
|
let shareReceiptButtonContainer: UIStackView = {
|
|
let stackView = UIStackView()
|
|
stackView.axis = .vertical
|
|
stackView.alignment = .fill
|
|
stackView.layoutMargins.top = 10
|
|
stackView.layoutMargins.bottom = 10
|
|
stackView.preservesSuperviewLayoutMargins = true
|
|
stackView.isLayoutMarginsRelativeArrangement = true
|
|
return stackView
|
|
}()
|
|
|
|
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 = NSLocalizedString("DONATION_RECEIPT_DETAILS", comment: "Title on the view where you can see a single receipt")
|
|
|
|
shareReceiptButton.block = { self.showShareReceiptActivity() }
|
|
shareReceiptButtonContainer.addArrangedSubview(shareReceiptButton)
|
|
|
|
updateTableContents()
|
|
updateSignalLogoImage()
|
|
updateShareReceiptButton()
|
|
}
|
|
|
|
override func themeDidChange() {
|
|
super.themeDidChange()
|
|
updateSignalLogoImage()
|
|
updateShareReceiptButton()
|
|
}
|
|
|
|
private func updateSignalLogoImage() {
|
|
let signalLogoImage = UIImage(named: "signal-full-logo")
|
|
if Theme.isDarkThemeEnabled {
|
|
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 = DonationUtilities.formatCurrency(NSDecimalNumber(decimal: model.amount), currencyCode: model.currencyCode)
|
|
amountLabel.textColor = Theme.primaryTextColor
|
|
amountLabel.font = .preferredFont(forTextStyle: .largeTitle)
|
|
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: NSLocalizedString("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: NSLocalizedString("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
|
|
|
|
open override var bottomFooter: UIView? {
|
|
get { shareReceiptButtonContainer }
|
|
set {}
|
|
}
|
|
|
|
private func updateShareReceiptButton() {
|
|
let textColor: UIColor = Theme.isDarkThemeEnabled ? .ows_whiteAlpha90 : .ows_white
|
|
shareReceiptButton.setTitleColor(textColor, for: .normal)
|
|
}
|
|
|
|
private func showShareReceiptActivity() {
|
|
// HACK: `UIActivityViewController` will sometimes dismiss its parent due to an iOS bug
|
|
// (see links below). To get around this, we present an invisible view controller and
|
|
// have that present the `UIActivityViewController`. Then, when the latter completes,
|
|
// we dismiss the invisible one.
|
|
//
|
|
// - <https://stackoverflow.com/q/56903030>
|
|
// - <https://stackoverflow.com/q/59413850>
|
|
// - <https://developer.apple.com/forums/thread/119482>
|
|
// - <https://github.com/iMacHumphries/TestShareSheet>
|
|
let wrapperViewControllerToFixIosBug = UIViewController()
|
|
wrapperViewControllerToFixIosBug.modalPresentationStyle = .overCurrentContext
|
|
wrapperViewControllerToFixIosBug.view.backgroundColor = .clear
|
|
|
|
let image = DonationReceiptImageActivityItemProvider(donationReceipt: model)
|
|
let shareActivityViewController = UIActivityViewController(activityItems: [image], applicationActivities: [])
|
|
if let popoverPresentationController = shareActivityViewController.popoverPresentationController {
|
|
popoverPresentationController.sourceView = self.shareReceiptButton
|
|
}
|
|
shareActivityViewController.excludedActivityTypes = [
|
|
.addToReadingList,
|
|
.assignToContact,
|
|
.openInIBooks,
|
|
.postToFacebook,
|
|
.postToFlickr,
|
|
.postToTencentWeibo,
|
|
.postToTwitter,
|
|
.postToVimeo,
|
|
.postToWeibo
|
|
]
|
|
shareActivityViewController.completionWithItemsHandler = { _, _, _, _ in
|
|
wrapperViewControllerToFixIosBug.dismiss(animated: false)
|
|
}
|
|
|
|
self.present(wrapperViewControllerToFixIosBug, animated: false) {
|
|
wrapperViewControllerToFixIosBug.present(shareActivityViewController, animated: true)
|
|
}
|
|
}
|
|
|
|
// MARK: - Donation receipt image activity provider
|
|
|
|
private class DonationReceiptImageActivityItemProvider: UIActivityItemProvider {
|
|
let donationReceiptImage: UIImage
|
|
|
|
public 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(withInsets: 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: 100, height: 31))
|
|
|
|
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(NSLocalizedString("DONATION_RECEIPT_TITLE", comment: "Title on donation receipts"),
|
|
fontSize: 20)
|
|
}
|
|
|
|
private class func amountView(donationReceipt: DonationReceipt) -> UIView {
|
|
let arrangedSubviews = [
|
|
label(NSLocalizedString("DONATION_RECEIPT_AMOUNT", comment: "Section title for donation amount on receipts")),
|
|
label(DonationUtilities.formatCurrency(NSDecimalNumber(decimal: donationReceipt.amount), currencyCode: donationReceipt.currencyCode),
|
|
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: NSLocalizedString("DONATION_RECEIPT_TYPE", comment: "Section title for donation type on receipts"),
|
|
subtitle: donationReceipt.localizedName)
|
|
}
|
|
|
|
private class func datePaidView(donationReceipt: DonationReceipt) -> UIView {
|
|
detailView(title: NSLocalizedString("DONATION_RECEIPT_DATE_PAID", comment: "Section title for donation date on receipts"),
|
|
subtitle: dateFormatter().string(from: donationReceipt.timestamp))
|
|
}
|
|
|
|
private class func footerView() -> UIView {
|
|
label(NSLocalizedString("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
|
|
}
|
|
}
|
|
}
|