Remove legacy donation view

_I recommend reviewing this with whitespace changes disabled._

We have a kill switch that disables badge acquisition. If that kill
switch is active, we show a one-time donation view.

That kill switch has been off since January of 2021, so I think we can
remove this one-time donation view. That lets us remove hundreds of
lines of code, but it will also make future donation-related changes a
bit easier.
This commit is contained in:
Evan Hahn 2022-04-15 12:29:47 -05:00 committed by Evan Hahn
parent 67ea0d0516
commit ef6c044a54
9 changed files with 43 additions and 869 deletions

View File

@ -977,7 +977,6 @@
88F58A1725EEE5B9008CDA24 /* AppSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F58A1625EEE5B9008CDA24 /* AppSettingsViewController.swift */; };
88F67A0C24E5126D00435A71 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */; };
88F7EE93230253C5003ADF7D /* UsernameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F7EE92230253C5003ADF7D /* UsernameViewController.swift */; };
88FBE9502649E7EB005F6C80 /* DonationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88FBE94F2649E7EB005F6C80 /* DonationViewController.swift */; };
88FBE954264B6849005F6C80 /* SubscriptionMegaphone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88FBE953264B6848005F6C80 /* SubscriptionMegaphone.swift */; };
88FE237E249C22080041670F /* ConversationViewController+Scroll.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88FE237D249C22080041670F /* ConversationViewController+Scroll.swift */; };
954AEE6A1DF33E01002E5410 /* ContactsPickerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954AEE681DF33D32002E5410 /* ContactsPickerTest.swift */; };
@ -2292,7 +2291,6 @@
88EFF4FB25AD4230000FAFBA /* SharingThreadPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingThreadPickerViewController.swift; sourceTree = "<group>"; };
88F58A1625EEE5B9008CDA24 /* AppSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsViewController.swift; sourceTree = "<group>"; };
88F7EE92230253C5003ADF7D /* UsernameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsernameViewController.swift; sourceTree = "<group>"; };
88FBE94F2649E7EB005F6C80 /* DonationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationViewController.swift; sourceTree = "<group>"; };
88FBE953264B6848005F6C80 /* SubscriptionMegaphone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionMegaphone.swift; sourceTree = "<group>"; };
88FE237D249C22080041670F /* ConversationViewController+Scroll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationViewController+Scroll.swift"; sourceTree = "<group>"; };
8981C8F64D94D3C52EB67A2C /* Pods-SignalTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.test.xcconfig"; sourceTree = "<group>"; };
@ -2655,7 +2653,6 @@
887B380725F0355700685845 /* ChatsSettingsViewController.swift */,
3414896825C9B6490098E3ED /* CurrencyPickerViewController.swift */,
887B6DCC25F6C41500E677D4 /* Data Usage */,
88FBE94F2649E7EB005F6C80 /* DonationViewController.swift */,
F0CA67F92728A89A002B9DFE /* SubscriptionViewController.swift */,
32ECD08624BFBF5C00EDDED0 /* Help */,
887B6DCE25F6C45B00E677D4 /* Internal */,
@ -6359,7 +6356,6 @@
458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */,
3491899B269CD68E008A18AF /* BlockingAnnouncementOnlyView.swift in Sources */,
88D23D2323CEC0C700B0E74B /* NonCallKitCallUIAdaptee.swift in Sources */,
88FBE9502649E7EB005F6C80 /* DonationViewController.swift in Sources */,
34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */,
884DB95027DE67BB00C6A309 /* StoryContextViewController.swift in Sources */,
34A4D87F2677B23100A794E7 /* ConversationViewController+MessageActions.swift in Sources */,

View File

@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "donate_light_dark2.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -272,67 +272,54 @@ class AppSettingsViewController: OWSTableViewController2 {
self?.showInviteFlow()
}
))
if RemoteConfig.donorBadgeAcquisition {
if DonationUtilities.isApplePayAvailable {
section4.add(.init(customCellBlock: { [weak self] in
guard let self = self else { return UITableViewCell() }
let cell = OWSTableItem.buildCellWithAccessoryLabel(
icon: .settingsDonate,
if DonationUtilities.isApplePayAvailable {
section4.add(.init(customCellBlock: { [weak self] in
guard let self = self else { return UITableViewCell() }
let cell = OWSTableItem.buildCellWithAccessoryLabel(
icon: .settingsDonate,
itemName: self.hasCurrentSubscription ? NSLocalizedString("SETTINGS_CURRENT_SUBSCRIPTION", comment: "Title for the 'Subscription' link in settings.") : NSLocalizedString("SETTINGS_SUBSCRIPTION", comment: "Title for the 'monthly donation' link in settings."),
accessoryType: .disclosureIndicator,
accessoryType: .disclosureIndicator,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "sustainer"))
if self.hasExpiredSubscription {
let imageView = UIImageView(image: UIImage(named: "info-solid-24")?.withRenderingMode(.alwaysTemplate))
imageView.tintColor = Theme.accentBlueColor
cell.contentView.addSubview(imageView)
if self.hasExpiredSubscription {
let imageView = UIImageView(image: UIImage(named: "info-solid-24")?.withRenderingMode(.alwaysTemplate))
imageView.tintColor = Theme.accentBlueColor
cell.contentView.addSubview(imageView)
imageView.autoSetDimensions(to: CGSize(square: 24))
imageView.autoVCenterInSuperview()
imageView.autoPinEdge(.trailing, to: .trailing, of: cell.contentView, withOffset: -8)
}
imageView.autoSetDimensions(to: CGSize(square: 24))
imageView.autoVCenterInSuperview()
imageView.autoPinEdge(.trailing, to: .trailing, of: cell.contentView, withOffset: -8)
}
return cell
}, actionBlock: { [weak self] in
let vc = SubscriptionViewController()
self?.navigationController?.pushViewController(vc, animated: true)
}))
return cell
}, actionBlock: { [weak self] in
let vc = SubscriptionViewController()
self?.navigationController?.pushViewController(vc, animated: true)
}))
section4.add(.disclosureItem(
icon: .settingsBoost,
name: NSLocalizedString("SETTINGS_ONE_TIME_DONATION", comment: "Title for the 'one time donation' link in settings."),
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "one time donation"),
actionBlock: { [weak self] in
let vc = BoostViewController()
self?.navigationController?.pushViewController(vc, animated: true)
}
))
} else {
section4.add(.item(icon: .settingsDonate,
tintColor: Theme.primaryIconColor,
name: NSLocalizedString("SETTINGS_DONATE", comment: "Title for the 'donate to signal' link in settings."),
maxNameLines: 0,
textColor: Theme.primaryTextColor,
accessoryText: nil,
accessoryType: .none,
accessoryImage: #imageLiteral(resourceName: "open-20").withRenderingMode(.alwaysTemplate),
accessoryView: nil,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "donate"),
actionBlock: { () in
UIApplication.shared.open(URL(string: "https://signal.org/donate")!, options: [:], completionHandler: nil)
}))
}
} else {
section4.add(.disclosureItem(
icon: .settingsDonate,
name: NSLocalizedString("SETTINGS_DONATE", comment: "Title for the 'donate to signal' link in settings."),
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "donate"),
icon: .settingsBoost,
name: NSLocalizedString("SETTINGS_ONE_TIME_DONATION", comment: "Title for the 'one time donation' link in settings."),
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "one time donation"),
actionBlock: { [weak self] in
let vc = DonationViewController()
let vc = BoostViewController()
self?.navigationController?.pushViewController(vc, animated: true)
}
))
} else {
section4.add(.item(icon: .settingsDonate,
tintColor: Theme.primaryIconColor,
name: NSLocalizedString("SETTINGS_DONATE", comment: "Title for the 'donate to signal' link in settings."),
maxNameLines: 0,
textColor: Theme.primaryTextColor,
accessoryText: nil,
accessoryType: .none,
accessoryImage: #imageLiteral(resourceName: "open-20").withRenderingMode(.alwaysTemplate),
accessoryView: nil,
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "donate"),
actionBlock: { () in
UIApplication.shared.open(URL(string: "https://signal.org/donate")!, options: [:], completionHandler: nil)
}))
}
contents.addSection(section4)

View File

@ -1,763 +0,0 @@
//
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import Foundation
import PassKit
import BonMot
import SignalServiceKit
/* From BonMot 6.0.0: If you're targeting iOS 15 or higher, you may want to check out [AttributedString](https://developer.apple.com/documentation/foundation/attributedstring) instead.
If you're an existing user of BonMot using Xcode 13, you may want to add the following `typealias` somewhere in your project to avoid a conflict with `Foundation.StringStyle`: */
typealias StringStyle = BonMot.StringStyle
class DonationViewController: OWSTableViewController2 {
private var currencyCode = Stripe.defaultCurrencyCode {
didSet {
guard oldValue != currencyCode else { return }
customValueTextField.setCurrencyCode(currencyCode)
state = nil
updateTableContents()
}
}
private let customValueTextField = CustomValueTextField()
private var donationAmount: NSDecimalNumber? {
switch state {
case .presetSelected(let amount): return NSDecimalNumber(value: amount)
case .customValueSelected: return customValueTextField.decimalNumber
default: return nil
}
}
enum State: Equatable {
case presetSelected(amount: UInt)
case customValueSelected
case donatedSuccessfully
}
private var state: State? {
didSet {
guard oldValue != state else { return }
if oldValue == .customValueSelected { clearCustomTextField() }
if state == .donatedSuccessfully { updateTableContents() }
updatePresetButtonSelection()
}
}
func clearCustomTextField() {
customValueTextField.text = nil
customValueTextField.resignFirstResponder()
}
override func viewDidLoad() {
shouldAvoidKeyboard = true
super.viewDidLoad()
customValueTextField.placeholder = NSLocalizedString(
"DONATION_VIEW_CUSTOM_AMOUNT_PLACEHOLDER",
comment: "Default text for the custom amount field of the donation view."
)
customValueTextField.delegate = self
customValueTextField.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "custom_amount_text_field")
updateTableContents()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// If we're the root view, add a cancel button
if navigationController?.viewControllers.first == self {
navigationItem.leftBarButtonItem = .init(
barButtonSystemItem: .done,
target: self,
action: #selector(didTapDone)
)
}
}
@objc
func didTapDone() {
self.dismiss(animated: true)
}
static let bubbleBorderWidth: CGFloat = 1.5
static let bubbleBorderColor = UIColor(rgbHex: 0xdedede)
static var bubbleBackgroundColor: UIColor { Theme.isDarkThemeEnabled ? .ows_gray80 : .ows_white }
func newCell() -> UITableViewCell {
let cell = OWSTableItem.newCell()
cell.selectionStyle = .none
cell.layoutMargins = cellOuterInsets
cell.contentView.layoutMargins = .zero
return cell
}
override var canBecomeFirstResponder: Bool { true }
override func becomeFirstResponder() -> Bool {
let result = super.becomeFirstResponder()
// If we become the first responder, but the user was entering
// a customValue, restore the first responder state to the text field.
if result, case .customValueSelected = state {
customValueTextField.becomeFirstResponder()
}
return result
}
var presetButtons: [UInt: UIView] = [:]
func updatePresetButtonSelection() {
for (amount, button) in presetButtons {
if case .presetSelected(amount: amount) = self.state {
button.layer.borderColor = Theme.accentBlueColor.cgColor
} else {
button.layer.borderColor = Self.bubbleBorderColor.cgColor
}
}
}
func updateTableContents() {
let contents = OWSTableContents()
defer {
self.contents = contents
if case .customValueSelected = state { customValueTextField.becomeFirstResponder() }
}
let section = OWSTableSection()
section.hasBackground = false
contents.addSection(section)
section.customHeaderView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .center
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 16, bottom: 28, right: 16)
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "character-loving")
imageView.contentMode = .scaleAspectFit
imageView.autoSetDimension(.height, toSize: 144)
stackView.addArrangedSubview(imageView)
let label = UILabel()
label.textAlignment = .center
label.font = UIFont.ows_dynamicTypeTitle2.ows_semibold
label.text = NSLocalizedString(
"DONATION_VIEW_TITLE",
comment: "Title for the donate to signal view"
)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
stackView.addArrangedSubview(label)
return stackView
}()
if case .donatedSuccessfully = state {
section.add(.init(
customCellBlock: { [weak self] in
guard let self = self else { return UITableViewCell() }
let cell = self.newCell()
let backgroundView = UIView()
backgroundView.backgroundColor = Self.bubbleBackgroundColor
backgroundView.layer.cornerRadius = 12
cell.insertSubview(backgroundView, belowSubview: cell.contentView)
backgroundView.autoPinEdgesToSuperviewMargins()
cell.contentView.layoutMargins = UIEdgeInsets(
hMargin: Self.cellHInnerMargin,
vMargin: 38
)
let label = UILabel()
label.textAlignment = .center
label.font = .ows_dynamicTypeBody
label.textColor = Theme.primaryTextColor
label.text = NSLocalizedString(
"DONATION_VIEW_THANKS_FOR_YOUR_SUPPORT",
comment: "Thank you message on the donate to signal view"
)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
cell.contentView.addSubview(label)
label.autoPinEdgesToSuperviewMargins()
return cell
},
actionBlock: {}
))
} else {
addApplePayItemsIfAvailable(to: section)
// If ApplePay isn't available, show just a link to the website
if !DonationUtilities.isApplePayAvailable {
section.add(.init(
customCellBlock: { [weak self] in
guard let self = self else { return UITableViewCell() }
let cell = self.newCell()
let donateButton = OWSFlatButton()
donateButton.setBackgroundColors(upColor: Theme.accentBlueColor)
donateButton.setTitleColor(.ows_white)
donateButton.setAttributedTitle(NSAttributedString.composed(of: [
NSLocalizedString(
"SETTINGS_DONATE",
comment: "Title for the 'donate to signal' link in settings."
),
Special.noBreakSpace,
NSAttributedString.with(
image: #imageLiteral(resourceName: "open-20").withRenderingMode(.alwaysTemplate),
font: UIFont.ows_dynamicTypeBodyClamped.ows_semibold
)
]).styled(
with: .font(UIFont.ows_dynamicTypeBodyClamped.ows_semibold),
.color(.ows_white)
))
donateButton.layer.cornerRadius = 12
donateButton.clipsToBounds = true
donateButton.setPressedBlock { [weak self] in
self?.openDonateWebsite()
}
cell.contentView.addSubview(donateButton)
donateButton.autoPinEdgesToSuperviewMargins()
donateButton.autoSetDimension(.height, toSize: 48, relation: .greaterThanOrEqual)
return cell
},
actionBlock: {}
))
}
}
let whySection = OWSTableSection()
whySection.hasBackground = false
contents.addSection(whySection)
whySection.add(.init(
customCellBlock: { [weak self] in
guard let self = self else { return UITableViewCell() }
let cell = self.newCell()
let label = UILabel()
label.font = UIFont.ows_dynamicTypeTitle2.ows_semibold
label.textColor = Theme.primaryTextColor
label.text = NSLocalizedString(
"DONATION_VIEW_WHY_DONATE_TITLE",
comment: "The title of the 'Why Donate' section of the donate to signal view"
)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
cell.contentView.addSubview(label)
label.autoPinEdgesToSuperviewMargins()
return cell
},
actionBlock: {}
))
whySection.add(.init(
customCellBlock: { [weak self] in
guard let self = self else { return UITableViewCell() }
let cell = self.newCell()
let label = UILabel()
label.font = .ows_dynamicTypeBody
label.textColor = Theme.primaryTextColor
label.text = NSLocalizedString(
"DONATION_VIEW_WHY_DONATE_BODY",
comment: "The body of the 'Why Donate' section of the donate to signal view"
)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
cell.contentView.addSubview(label)
label.autoPinEdgesToSuperviewMargins()
return cell
},
actionBlock: {}
))
}
private func openDonateWebsite() {
UIApplication.shared.open(URL(string: "https://signal.org/donate")!, options: [:], completionHandler: nil)
}
}
// MARK: - ApplePay
extension DonationViewController: PKPaymentAuthorizationControllerDelegate {
func addApplePayItemsIfAvailable(to section: OWSTableSection) {
guard DonationUtilities.isApplePayAvailable else { return }
// Currency Picker
section.add(.init(
customCellBlock: { [weak self] in
guard let self = self else { return UITableViewCell() }
let cell = self.newCell()
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = 8
cell.contentView.addSubview(stackView)
stackView.autoPinEdgesToSuperviewEdges()
let label = UILabel()
label.font = .ows_dynamicTypeBodyClamped
label.textColor = Theme.primaryTextColor
label.text = NSLocalizedString(
"DONATION_VIEW_AMOUNT_LABEL",
comment: "Donation amount label for the donate to signal view"
)
stackView.addArrangedSubview(label)
let picker = OWSButton { [weak self] in
guard let self = self else { return }
let vc = CurrencyPickerViewController(
dataSource: StripeCurrencyPickerDataSource(currentCurrencyCode: self.currencyCode)
) { [weak self] currencyCode in
self?.currencyCode = currencyCode
}
self.navigationController?.pushViewController(vc, animated: true)
}
picker.setAttributedTitle(NSAttributedString.composed(of: [
self.currencyCode,
Special.noBreakSpace,
NSAttributedString.with(
image: #imageLiteral(resourceName: "chevron-down-18").withRenderingMode(.alwaysTemplate),
font: .ows_regularFont(withSize: 17)
).styled(
with: .color(Self.bubbleBorderColor)
)
]).styled(
with: .font(.ows_regularFont(withSize: 17)),
.color(Theme.primaryTextColor)
), for: .normal)
picker.setBackgroundImage(UIImage.init(color: Self.bubbleBackgroundColor), for: .normal)
picker.setBackgroundImage(UIImage.init(color: Self.bubbleBackgroundColor.withAlphaComponent(0.8)), for: .highlighted)
let pillView = PillView()
pillView.layer.borderWidth = Self.bubbleBorderWidth
pillView.layer.borderColor = Self.bubbleBorderColor.cgColor
pillView.clipsToBounds = true
pillView.addSubview(picker)
picker.autoPinEdgesToSuperviewEdges()
picker.autoSetDimension(.width, toSize: 74, relation: .greaterThanOrEqual)
stackView.addArrangedSubview(pillView)
pillView.autoSetDimension(.height, toSize: 36, relation: .greaterThanOrEqual)
let leadingSpacer = UIView.hStretchingSpacer()
let trailingSpacer = UIView.hStretchingSpacer()
stackView.insertArrangedSubview(leadingSpacer, at: 0)
stackView.addArrangedSubview(trailingSpacer)
leadingSpacer.autoMatch(.width, to: .width, of: trailingSpacer)
return cell
},
actionBlock: {}
))
// Preset donation options
if let preset = DonationUtilities.Presets.presets[currencyCode] {
section.add(.init(
customCellBlock: { [weak self] in
guard let self = self else { return UITableViewCell() }
let cell = self.newCell()
let vStack = UIStackView()
vStack.axis = .vertical
vStack.distribution = .fillEqually
vStack.spacing = 16
cell.contentView.addSubview(vStack)
vStack.autoPinEdgesToSuperviewMargins()
self.presetButtons.removeAll()
for amounts in preset.amounts.chunked(by: 3) {
let hStack = UIStackView()
hStack.axis = .horizontal
hStack.distribution = .fillEqually
hStack.spacing = UIDevice.current.isIPhone5OrShorter ? 8 : 14
vStack.addArrangedSubview(hStack)
for amount in amounts {
let button = OWSFlatButton()
hStack.addArrangedSubview(button)
button.setBackgroundColors(
upColor: Self.bubbleBackgroundColor,
downColor: Self.bubbleBackgroundColor.withAlphaComponent(0.8)
)
button.layer.cornerRadius = 12
button.clipsToBounds = true
button.layer.borderWidth = Self.bubbleBorderWidth
button.setPressedBlock { [weak self] in
self?.state = .presetSelected(amount: amount)
}
button.setTitle(
title: DonationUtilities.formatCurrency(NSDecimalNumber(value: amount), currencyCode: self.currencyCode),
font: .ows_regularFont(withSize: UIDevice.current.isIPhone5OrShorter ? 18 : 20),
titleColor: Theme.primaryTextColor
)
button.autoSetDimension(.height, toSize: 48)
self.presetButtons[amount] = button
}
}
self.updatePresetButtonSelection()
return cell
},
actionBlock: {}
))
}
// Custom donation option
let applePayButtonIndex = IndexPath(row: section.items.count + 1, section: 0)
let customValueTextField = self.customValueTextField
section.add(.init(
customCellBlock: { [weak self] in
guard let self = self else { return UITableViewCell() }
let cell = self.newCell()
customValueTextField.backgroundColor = Self.bubbleBackgroundColor
customValueTextField.layer.cornerRadius = 12
customValueTextField.layer.borderWidth = Self.bubbleBorderWidth
customValueTextField.layer.borderColor = Self.bubbleBorderColor.cgColor
customValueTextField.font = .ows_dynamicTypeBodyClamped
customValueTextField.textColor = Theme.primaryTextColor
cell.contentView.addSubview(customValueTextField)
customValueTextField.autoPinEdgesToSuperviewMargins()
return cell
},
actionBlock: { [weak self] in
customValueTextField.becomeFirstResponder()
self?.tableView.scrollToRow(at: applePayButtonIndex, at: .bottom, animated: true)
}
))
// Donate with Apple Pay button
section.add(.init(
customCellBlock: { [weak self] in
guard let self = self else { return UITableViewCell() }
let cell = self.newCell()
let donateButton = PKPaymentButton(
paymentButtonType: .donate,
paymentButtonStyle: Theme.isDarkThemeEnabled ? .white : .black
)
donateButton.cornerRadius = 12
donateButton.addTarget(self, action: #selector(self.requestApplePayDonation), for: .touchUpInside)
cell.contentView.addSubview(donateButton)
donateButton.autoPinEdgesToSuperviewMargins()
donateButton.autoSetDimension(.height, toSize: 48, relation: .greaterThanOrEqual)
return cell
},
actionBlock: {}
))
// Other options button
section.add(.init(
customCellBlock: { [weak self] in
guard let self = self else { return UITableViewCell() }
let cell = self.newCell()
let donateButton = OWSFlatButton()
donateButton.setTitleColor(Theme.accentBlueColor)
donateButton.setAttributedTitle(NSAttributedString.composed(of: [
NSLocalizedString(
"DONATION_VIEW_OTHER_WAYS",
comment: "Text explaining there are other ways to donate on the donation view."
),
Special.noBreakSpace,
NSAttributedString.with(
image: #imageLiteral(resourceName: "open-20").withRenderingMode(.alwaysTemplate),
font: .ows_dynamicTypeBodyClamped
)
]).styled(
with: .font(.ows_dynamicTypeBodyClamped),
.color(Theme.accentBlueColor)
))
donateButton.setPressedBlock { [weak self] in
self?.openDonateWebsite()
}
cell.contentView.addSubview(donateButton)
donateButton.autoPinEdgesToSuperviewMargins()
return cell
},
actionBlock: {}
))
}
@objc
func requestApplePayDonation() {
guard let donationAmount = donationAmount else {
presentToast(text: NSLocalizedString(
"DONATION_VIEW_SELECT_AN_AMOUNT",
comment: "Error text notifying the user they must select an amount on the donate to signal view"
), extraVInset: view.height - tableView.frame.maxY)
return
}
guard !Stripe.isAmountTooSmall(donationAmount, in: currencyCode) else {
presentToast(text: NSLocalizedString(
"DONATION_VIEW_SELECT_A_LARGER_AMOUNT",
comment: "Error text notifying the user they must select a large amount on the donate to signal view"
), extraVInset: view.height - tableView.frame.maxY)
return
}
guard !Stripe.isAmountTooLarge(donationAmount, in: currencyCode) else {
presentToast(text: NSLocalizedString(
"DONATION_VIEW_SELECT_A_SMALLER_AMOUNT",
comment: "Error text notifying the user they must select a smaller amount on the donate to signal view"
), extraVInset: view.height - tableView.frame.maxY)
return
}
let request = DonationUtilities.newPaymentRequest(for: donationAmount, currencyCode: currencyCode)
let paymentController = PKPaymentAuthorizationController(paymentRequest: request)
paymentController.delegate = self
paymentController.present { presented in
if !presented { owsFailDebug("Failed to present payment controller") }
}
}
func paymentAuthorizationControllerDidFinish(_ controller: PKPaymentAuthorizationController) {
controller.dismiss()
}
func paymentAuthorizationController(
_ controller: PKPaymentAuthorizationController,
didAuthorizePayment payment: PKPayment,
handler completion: @escaping (PKPaymentAuthorizationResult) -> Void
) {
guard let donationAmount = donationAmount else {
completion(.init(status: .failure, errors: [OWSAssertionError("Missing donation amount")]))
return
}
Stripe.donate(amount: donationAmount, in: currencyCode, for: payment).done { [weak self] in
completion(.init(status: .success, errors: nil))
self?.state = .donatedSuccessfully
}.catch { error in
owsFailDebugUnlessNetworkFailure(error)
completion(.init(status: .failure, errors: [error]))
}
}
}
// MARK: - CustomValueTextField
private protocol CustomValueTextFieldDelegate: AnyObject {
func customValueTextFieldStateDidChange(_ textField: CustomValueTextField)
}
private class CustomValueTextField: UIView {
private let placeholderLabel = UILabel()
private let symbolLabel = UILabel()
private let textField = UITextField()
private let stackView = UIStackView()
weak var delegate: CustomValueTextFieldDelegate?
@discardableResult
override func becomeFirstResponder() -> Bool { textField.becomeFirstResponder() }
@discardableResult
override func resignFirstResponder() -> Bool { textField.resignFirstResponder() }
override var canBecomeFirstResponder: Bool { textField.canBecomeFirstResponder }
override var canResignFirstResponder: Bool { textField.canResignFirstResponder }
override var isFirstResponder: Bool { textField.isFirstResponder }
init() {
super.init(frame: .zero)
textField.autocorrectionType = .no
textField.spellCheckingType = .no
textField.keyboardType = .decimalPad
textField.textAlignment = .center
textField.delegate = self
symbolLabel.textAlignment = .center
placeholderLabel.textAlignment = .center
stackView.axis = .horizontal
stackView.addArrangedSubview(placeholderLabel)
stackView.addArrangedSubview(textField)
addSubview(stackView)
stackView.autoPinHeightToSuperview()
stackView.autoMatch(.width, to: .width, of: self, withMultiplier: 1, relation: .lessThanOrEqual)
stackView.autoHCenterInSuperview()
stackView.autoSetDimension(.height, toSize: 48, relation: .greaterThanOrEqual)
updateVisibility()
setCurrencyCode(currencyCode)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var text: String? {
set {
textField.text = newValue
updateVisibility()
}
get { textField.text }
}
var decimalNumber: NSDecimalNumber? {
let number = NSDecimalNumber(string: valueString(for: text), locale: Locale.current)
guard number != NSDecimalNumber.notANumber else { return nil }
return number
}
var font: UIFont? {
set {
textField.font = newValue
placeholderLabel.font = newValue
symbolLabel.font = newValue
}
get { textField.font }
}
var textColor: UIColor? {
set {
textField.textColor = newValue
placeholderLabel.textColor = newValue
symbolLabel.textColor = newValue
}
get { textField.textColor }
}
var placeholder: String? {
set { placeholderLabel.text = newValue }
get { placeholderLabel.text }
}
private lazy var symbol: DonationUtilities.Symbol = DonationUtilities.Presets.presets[currencyCode]?.symbol ?? .currencyCode
private lazy var currencyCode = Stripe.defaultCurrencyCode
func setCurrencyCode(_ currencyCode: Currency.Code) {
self.symbol = DonationUtilities.Presets.symbol(for: currencyCode)
self.currencyCode = currencyCode
symbolLabel.removeFromSuperview()
switch symbol {
case .before(let symbol):
symbolLabel.text = symbol
stackView.insertArrangedSubview(symbolLabel, at: 0)
case .after(let symbol):
symbolLabel.text = symbol
stackView.addArrangedSubview(symbolLabel)
case .currencyCode:
symbolLabel.text = currencyCode + " "
stackView.insertArrangedSubview(symbolLabel, at: 0)
}
}
func updateVisibility() {
let shouldShowPlaceholder = text.isEmptyOrNil && !isFirstResponder
placeholderLabel.isHiddenInStackView = !shouldShowPlaceholder
symbolLabel.isHiddenInStackView = shouldShowPlaceholder
textField.isHiddenInStackView = shouldShowPlaceholder
}
}
extension CustomValueTextField: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
updateVisibility()
delegate?.customValueTextFieldStateDidChange(self)
}
func textFieldDidEndEditing(_ textField: UITextField) {
updateVisibility()
}
func textField(_ textField: UITextField, shouldChangeCharactersIn editingRange: NSRange, replacementString: String) -> Bool {
let existingString = textField.text ?? ""
let newString = (existingString as NSString).replacingCharacters(in: editingRange, with: replacementString)
if let numberString = self.valueString(for: newString) {
textField.text = numberString
// Make a best effort to preserve cursor position
if let newPosition = textField.position(
from: textField.beginningOfDocument,
offset: editingRange.location + max(0, numberString.count - existingString.count)
) {
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
}
} else {
textField.text = ""
}
updateVisibility()
delegate?.customValueTextFieldStateDidChange(self)
return false
}
/// Converts an arbitrary string into a string representing a valid value
/// for the current currency. If no valid value is represented, returns nil
func valueString(for string: String?) -> String? {
guard let string = string else { return nil }
let isZeroDecimalCurrency = Stripe.zeroDecimalCurrencyCodes.contains(currencyCode)
guard !isZeroDecimalCurrency else { return string.digitsOnly }
let decimalSeparator = Locale.current.decimalSeparator ?? "."
let components = string.components(separatedBy: decimalSeparator).compactMap { $0.digitsOnly.nilIfEmpty }
guard let integralString = components.first else {
if string.contains(decimalSeparator) {
return "0" + decimalSeparator
} else {
return nil
}
}
if let decimalString = components.dropFirst().joined().nilIfEmpty {
return integralString + decimalSeparator + decimalString
} else if string.starts(with: decimalSeparator) {
return "0" + decimalSeparator + integralString
} else if string.contains(decimalSeparator) {
return integralString + decimalSeparator
} else {
return integralString
}
}
}
extension DonationViewController: CustomValueTextFieldDelegate {
fileprivate func customValueTextFieldStateDidChange(_ textField: CustomValueTextField) {
state = .customValueSelected
}
}

View File

@ -5,6 +5,10 @@
import Foundation
import BonMot
/* From BonMot 6.0.0: If you're targeting iOS 15 or higher, you may want to check out [AttributedString](https://developer.apple.com/documentation/foundation/attributedstring) instead.
If you're an existing user of BonMot using Xcode 13, you may want to add the following `typealias` somewhere in your project to avoid a conflict with `Foundation.StringStyle`: */
typealias StringStyle = BonMot.StringStyle
@objc
public protocol ConversationSearchViewDelegate: AnyObject {
func conversationSearchViewWillBeginDragging()

View File

@ -1639,39 +1639,9 @@
/* Text at the bottom of the donation receipts list, telling users that receipts might not be available */
"DONATION_RECEIPTS_MIGHT_BE_MISSING_IF_YOU_REINSTALLED" = "If you have reinstalled Signal, receipts from previous donations will not be available.";
/* Donation amount label for the donate to signal view */
"DONATION_VIEW_AMOUNT_LABEL" = "Donation amount";
/* Default text for the custom amount field of the donation view. */
"DONATION_VIEW_CUSTOM_AMOUNT_PLACEHOLDER" = "Enter Custom Amount";
/* Text describing to the user that they're going to pay a donation to Signal */
"DONATION_VIEW_DONATION_TO_SIGNAL" = "Donation to Signal";
/* Text explaining there are other ways to donate on the donation view. */
"DONATION_VIEW_OTHER_WAYS" = "Other Ways to Donate";
/* Error text notifying the user they must select a large amount on the donate to signal view */
"DONATION_VIEW_SELECT_A_LARGER_AMOUNT" = "Select a larger amount to donate";
/* Error text notifying the user they must select a smaller amount on the donate to signal view */
"DONATION_VIEW_SELECT_A_SMALLER_AMOUNT" = "Select a smaller amount to donate";
/* Error text notifying the user they must select an amount on the donate to signal view */
"DONATION_VIEW_SELECT_AN_AMOUNT" = "Select an amount to donate";
/* Thank you message on the donate to signal view */
"DONATION_VIEW_THANKS_FOR_YOUR_SUPPORT" = "Thank you for your support!";
/* Title for the donate to signal view */
"DONATION_VIEW_TITLE" = "Signal is powered by people like you.";
/* The body of the 'Why Donate' section of the donate to signal view */
"DONATION_VIEW_WHY_DONATE_BODY" = "The team at Signal is committed to the mission of developing open source privacy technology that protects free expression and enables secure global communication. Your contribution fuels this cause. No ads. No trackers. No kidding.\n\nYour donation helps pay for the development, servers, and bandwidth of an app used by millions around the world for private and instantaneous communication.";
/* The title of the 'Why Donate' section of the donate to signal view */
"DONATION_VIEW_WHY_DONATE_TITLE" = "Why Donate?";
/* Alert body for when the user has just tried to edit a contacts after declining to give Signal contacts permissions */
"EDIT_CONTACT_WITHOUT_CONTACTS_PERMISSION_ALERT_BODY" = "You can enable access in the iOS Settings app.";

View File

@ -381,9 +381,6 @@ public class DebugFlags: BaseFlags {
@objc
public static let forceDonorBadgeDisplay = build.includes(.qa)
@objc
public static let forceDonorBadgeAcquisition = build.includes(.qa)
@objc
public static let forceSubscriptionMegaphone = build.includes(.qa)

View File

@ -202,11 +202,6 @@ public class RemoteConfig: BaseFlags {
DebugFlags.forceDonorBadgeDisplay || !isEnabled(.donorBadgeDisplayKillSwitch)
}
@objc
public static var donorBadgeAcquisition: Bool {
DebugFlags.forceDonorBadgeAcquisition || !isEnabled(.donorBadgeAcquisitionKillSwitch)
}
@objc
public static var changePhoneNumberUI: Bool {
if DebugFlags.forceChangePhoneNumberUI.get() {
@ -383,7 +378,8 @@ private struct Flags {
// as soon as we fetch an update to the remote config. They will not
// wait for an app restart.
enum HotSwappableIsEnabledFlags: String, FlagType {
case donorBadgeAcquisitionKillSwitch
// This can't be empty, so we define a bogus case. Remove this if you add a flag here.
case __noHotSwappableIsEnabledFlags
}
// We filter the received config down to just the supported flags.
@ -400,7 +396,6 @@ private struct Flags {
case senderKeyKillSwitch
case messageResendKillSwitch
case donorBadgeDisplayKillSwitch
case donorBadgeAcquisitionKillSwitch
case changePhoneNumberUI
}