Merge branch 'eb/badgeExpiry'
This commit is contained in:
commit
c35cadfa79
@ -1008,6 +1008,7 @@
|
||||
F00385FF273F6388000B5ABD /* SubscriptionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F00385FC273F6388000B5ABD /* SubscriptionManager.swift */; };
|
||||
F0038600273F6388000B5ABD /* DonationUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = F00385FD273F6388000B5ABD /* DonationUtilities.swift */; };
|
||||
F0038601273F6388000B5ABD /* Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = F00385FE273F6388000B5ABD /* Stripe.swift */; };
|
||||
F02564D8274EDF4600D7B48A /* BadgeExpirationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02564D7274EDF4600D7B48A /* BadgeExpirationSheet.swift */; };
|
||||
F026749627405BF4005853DC /* SubscriptionReceiptCredentialRedemptionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = F026749527405BF4005853DC /* SubscriptionReceiptCredentialRedemptionJob.swift */; };
|
||||
F05F51C926A90D6B00861034 /* ContextMenuActionsAccessory.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05F51C826A90D6B00861034 /* ContextMenuActionsAccessory.swift */; };
|
||||
F0B872B6269CF6D900D26481 /* ContextMenuInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B872B5269CF6D900D26481 /* ContextMenuInteraction.swift */; };
|
||||
@ -2330,6 +2331,7 @@
|
||||
F00385FC273F6388000B5ABD /* SubscriptionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionManager.swift; sourceTree = "<group>"; };
|
||||
F00385FD273F6388000B5ABD /* DonationUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DonationUtilities.swift; sourceTree = "<group>"; };
|
||||
F00385FE273F6388000B5ABD /* Stripe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stripe.swift; sourceTree = "<group>"; };
|
||||
F02564D7274EDF4600D7B48A /* BadgeExpirationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeExpirationSheet.swift; sourceTree = "<group>"; };
|
||||
F026749527405BF4005853DC /* SubscriptionReceiptCredentialRedemptionJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionReceiptCredentialRedemptionJob.swift; sourceTree = "<group>"; };
|
||||
F05F51C826A90D6B00861034 /* ContextMenuActionsAccessory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuActionsAccessory.swift; sourceTree = "<group>"; };
|
||||
F0B872B5269CF6D900D26481 /* ContextMenuInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuInteraction.swift; sourceTree = "<group>"; };
|
||||
@ -2583,6 +2585,7 @@
|
||||
888017912741F33B00346E9A /* BoostViewController.swift */,
|
||||
8880179327430DDB00346E9A /* BadgeThanksSheet.swift */,
|
||||
881BB2892743531D00B609B8 /* BadgeDetailsSheet.swift */,
|
||||
F02564D7274EDF4600D7B48A /* BadgeExpirationSheet.swift */,
|
||||
887B380725F0355700685845 /* ChatsSettingsViewController.swift */,
|
||||
3414896825C9B6490098E3ED /* CurrencyPickerViewController.swift */,
|
||||
887B6DCC25F6C41500E677D4 /* Data Usage */,
|
||||
@ -6333,6 +6336,7 @@
|
||||
4C19A0FC227B356F007A0C7F /* DebugUIMessages+OWS.swift in Sources */,
|
||||
F0B872B8269D079B00D26481 /* ContextMenuConfiguration.swift in Sources */,
|
||||
3496955C219B605E00DCFE74 /* ImagePickerController.swift in Sources */,
|
||||
F02564D8274EDF4600D7B48A /* BadgeExpirationSheet.swift in Sources */,
|
||||
34ACA7E82733159600E47AD4 /* ChangePhoneNumberController.swift in Sources */,
|
||||
34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */,
|
||||
4CBBFE4A2306F5D300B37450 /* LogViewController.swift in Sources */,
|
||||
|
||||
@ -67,16 +67,7 @@ class AppSettingsViewController: OWSTableViewController2 {
|
||||
}
|
||||
|
||||
private lazy var hasCurrentSubscription: Bool = {
|
||||
var subscriberID: Data?
|
||||
SDSDatabaseStorage.shared.read { transaction in
|
||||
subscriberID = SubscriptionManager.getSubscriberID(transaction: transaction)
|
||||
}
|
||||
|
||||
guard subscriberID != nil else {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return SubscriptionManager.hasCurrentSubscriptionCached()
|
||||
}()
|
||||
|
||||
func updateTableContents() {
|
||||
|
||||
@ -0,0 +1,264 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SignalMessaging
|
||||
|
||||
protocol BadgeExpirationSheetDelegate: AnyObject {
|
||||
func badgeExpirationSheetActionButtonTapped(_ badgeExpirationSheet: BadgeExpirationSheet)
|
||||
func badgeExpirationSheetNotNowButtonTapped(_ badgeExpirationSheet: BadgeExpirationSheet)
|
||||
}
|
||||
|
||||
class BadgeExpirationSheet: InteractiveSheetViewController {
|
||||
override var interactiveScrollViews: [UIScrollView] { [tableViewController.tableView] }
|
||||
override var renderExternalHandle: Bool { false }
|
||||
private var shouldMakeVisibleAndPrimary = false
|
||||
public var delegate: BadgeExpirationSheetDelegate?
|
||||
|
||||
var contentSizeHeight: CGFloat {
|
||||
tableViewController.tableView.layoutIfNeeded()
|
||||
return tableViewController.tableView.contentSize.height + tableViewController.tableView.adjustedContentInset.top
|
||||
}
|
||||
override var minimizedHeight: CGFloat {
|
||||
return min(contentSizeHeight, maximizedHeight)
|
||||
}
|
||||
override var maximizedHeight: CGFloat {
|
||||
min(contentSizeHeight, CurrentAppContext().frame.height - (view.safeAreaInsets.top + 32))
|
||||
}
|
||||
|
||||
private let tableViewController = OWSTableViewController2()
|
||||
private let handleContainer = UIView()
|
||||
|
||||
private let badge: ProfileBadge
|
||||
|
||||
public var badgeID: String {
|
||||
return badge.id
|
||||
}
|
||||
|
||||
private lazy var isCurrentSustainer = {
|
||||
return SubscriptionManager.hasCurrentSubscriptionCached()
|
||||
}()
|
||||
|
||||
required init(badge: ProfileBadge) {
|
||||
owsAssertDebug(badge.assets != nil)
|
||||
self.badge = badge
|
||||
|
||||
super.init()
|
||||
|
||||
tableViewController.shouldDeferInitialLoad = false
|
||||
updateTableContents()
|
||||
}
|
||||
|
||||
public required init() {
|
||||
fatalError("init() has not been implemented")
|
||||
}
|
||||
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
addChild(tableViewController)
|
||||
|
||||
contentView.addSubview(tableViewController.view)
|
||||
tableViewController.view.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
// We add the handle directly to the content view,
|
||||
// so that it doesn't scroll with the table.
|
||||
handleContainer.backgroundColor = Theme.tableView2PresentedBackgroundColor
|
||||
contentView.addSubview(handleContainer)
|
||||
handleContainer.autoPinWidthToSuperview()
|
||||
handleContainer.autoPinEdge(toSuperviewEdge: .top)
|
||||
|
||||
let handle = UIView()
|
||||
handle.backgroundColor = tableViewController.separatorColor
|
||||
handle.autoSetDimensions(to: CGSize(width: 36, height: 5))
|
||||
handle.layer.cornerRadius = 5 / 2
|
||||
handleContainer.addSubview(handle)
|
||||
handle.autoPinHeightToSuperview(withMargin: 12)
|
||||
handle.autoHCenterInSuperview()
|
||||
|
||||
updateViewState()
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
updateViewState()
|
||||
}
|
||||
|
||||
private var previousMinimizedHeight: CGFloat?
|
||||
private func updateViewState() {
|
||||
if minimizedHeight != previousMinimizedHeight {
|
||||
heightConstraint?.constant = minimizedHeight
|
||||
previousMinimizedHeight = minimizedHeight
|
||||
}
|
||||
}
|
||||
|
||||
override func themeDidChange() {
|
||||
super.themeDidChange()
|
||||
handleContainer.backgroundColor = Theme.tableView2PresentedBackgroundColor
|
||||
updateTableContents()
|
||||
}
|
||||
|
||||
var titleText: String {
|
||||
switch badge.id {
|
||||
case "BOOST": return NSLocalizedString(
|
||||
"BADGE_EXPIRED_BOOST_TITLE",
|
||||
comment: "Title for boost on the badge expiration sheet."
|
||||
)
|
||||
default: return NSLocalizedString(
|
||||
"BADGE_EXPIRED_SUBSCRIPTION_TITLE",
|
||||
comment: "Title for subscription on the badge expiration sheet."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var bodyText: String {
|
||||
var formatText: String
|
||||
|
||||
switch (badgeID: badge.id, isSustainer: isCurrentSustainer) {
|
||||
case (badgeID: "BOOST", isSustainer: true):
|
||||
formatText = NSLocalizedString(
|
||||
"BADGE_EXIPRED_BOOST_CURRENT_SUSTAINER_BODY_FORMAT",
|
||||
comment: "String explaing to the user that their boost badge has expired while they are a current subscription sustainer on the badge expiry sheetsheet."
|
||||
)
|
||||
case (badgeID: "BOOST", isSustainer: false):
|
||||
formatText = NSLocalizedString(
|
||||
"BADGE_EXIPRED_BOOST_BODY_FORMAT",
|
||||
comment: "String explaing to the user that their boost badge has expired on the badge expiry sheetsheet."
|
||||
)
|
||||
|
||||
default:
|
||||
formatText = NSLocalizedString(
|
||||
"BADGE_EXIPRED_SUBSCRIPTION_BODY_FORMAT",
|
||||
comment: "String explaing to the user that their subscription badge has expired on the badge expiry sheetsheet. Embed {badge name}."
|
||||
)
|
||||
}
|
||||
|
||||
return String(format: formatText, badge.localizedName)
|
||||
}
|
||||
|
||||
var actionButtonText: String {
|
||||
switch (badgeID: badge.id, isSustainer: isCurrentSustainer) {
|
||||
case (badgeID: "BOOST", isSustainer: true):
|
||||
return NSLocalizedString("BADGE_EXPIRED_BOOST_RENEWAL_BUTTON_SUSTAINER",
|
||||
comment: "Button title for boost on the badge expiration sheet, used if the user is already a sustainer.")
|
||||
case (badgeID: "BOOST", isSustainer: false):
|
||||
return NSLocalizedString("BADGE_EXPIRED_BOOST_RENEWAL_BUTTON",
|
||||
comment: "Button title for boost on the badge expiration sheet, used if the user is not already a sustainer.")
|
||||
default:
|
||||
return NSLocalizedString("BADGE_EXPIRED_SUBSCRIPTION_RENEWAL_BUTTON",
|
||||
comment: "Button title for subscription on the badge expiration sheet.")
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTableContents() {
|
||||
let contents = OWSTableContents()
|
||||
defer { tableViewController.contents = contents }
|
||||
|
||||
let headerSection = OWSTableSection()
|
||||
headerSection.hasBackground = false
|
||||
headerSection.customHeaderHeight = 1
|
||||
contents.addSection(headerSection)
|
||||
|
||||
headerSection.add(.init(customCellBlock: { [weak self] in
|
||||
let cell = OWSTableItem.newCell()
|
||||
guard let self = self else { return cell }
|
||||
cell.selectionStyle = .none
|
||||
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .vertical
|
||||
stackView.alignment = .center
|
||||
stackView.layoutMargins = UIEdgeInsets(hMargin: 24, vMargin: 0)
|
||||
stackView.isLayoutMarginsRelativeArrangement = true
|
||||
|
||||
cell.contentView.addSubview(stackView)
|
||||
stackView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
let badgeImageView = UIImageView()
|
||||
badgeImageView.image = self.badge.assets?.universal112
|
||||
badgeImageView.autoSetDimensions(to: CGSize(square: 112))
|
||||
stackView.addArrangedSubview(badgeImageView)
|
||||
stackView.setCustomSpacing(16, after: badgeImageView)
|
||||
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.font = .ows_dynamicTypeTitle2.ows_semibold
|
||||
titleLabel.textColor = Theme.primaryTextColor
|
||||
titleLabel.textAlignment = .center
|
||||
titleLabel.numberOfLines = 0
|
||||
titleLabel.text = self.titleText
|
||||
stackView.addArrangedSubview(titleLabel)
|
||||
stackView.setCustomSpacing(12, after: titleLabel)
|
||||
|
||||
let bodyLabel = UILabel()
|
||||
bodyLabel.font = .ows_dynamicTypeBody
|
||||
bodyLabel.textColor = Theme.primaryTextColor
|
||||
bodyLabel.textAlignment = .center
|
||||
bodyLabel.numberOfLines = 0
|
||||
bodyLabel.text = self.bodyText
|
||||
stackView.addArrangedSubview(bodyLabel)
|
||||
stackView.setCustomSpacing(30, after: bodyLabel)
|
||||
|
||||
return cell
|
||||
}, actionBlock: nil))
|
||||
|
||||
let buttonSection = OWSTableSection()
|
||||
buttonSection.hasBackground = false
|
||||
contents.addSection(buttonSection)
|
||||
buttonSection.add(.init(customCellBlock: { [weak self] in
|
||||
let cell = OWSTableItem.newCell()
|
||||
cell.selectionStyle = .none
|
||||
guard let self = self else { return cell }
|
||||
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .vertical
|
||||
stackView.alignment = .center
|
||||
stackView.layoutMargins = UIEdgeInsets(top: 30, left: 24, bottom: 0, right: 24)
|
||||
stackView.spacing = 16
|
||||
stackView.isLayoutMarginsRelativeArrangement = true
|
||||
cell.contentView.addSubview(stackView)
|
||||
stackView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
let actionButton = OWSFlatButton.button(title: self.actionButtonText,
|
||||
font: UIFont.ows_dynamicTypeBody.ows_semibold,
|
||||
titleColor: .white,
|
||||
backgroundColor: .ows_accentBlue,
|
||||
target: self,
|
||||
selector: #selector(self.didTapAction))
|
||||
actionButton.autoSetHeightUsingFont()
|
||||
actionButton.cornerRadius = 8
|
||||
stackView.addArrangedSubview(actionButton)
|
||||
actionButton.autoPinWidthToSuperviewMargins()
|
||||
|
||||
let notNowButton = OWSButton(title: CommonStrings.notNowButton) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.didTapNotNow()
|
||||
}
|
||||
notNowButton.setTitleColor(Theme.accentBlueColor, for: .normal)
|
||||
notNowButton.dimsWhenHighlighted = true
|
||||
stackView.addArrangedSubview(notNowButton)
|
||||
|
||||
return cell
|
||||
}, actionBlock: nil))
|
||||
}
|
||||
|
||||
public override func willDismissInteractively() {
|
||||
didTapNotNow()
|
||||
super.willDismissInteractively()
|
||||
}
|
||||
|
||||
|
||||
@objc
|
||||
func didTapAction() {
|
||||
if let delegate = delegate {
|
||||
dismiss(animated: true, completion: nil)
|
||||
delegate.badgeExpirationSheetActionButtonTapped(self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func didTapNotNow() {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
@ -27,7 +27,19 @@ class SubscriptionViewController: OWSTableViewController2 {
|
||||
} else if fetchedSubscriptionStatus == false || subscriptions == nil {
|
||||
return .loading
|
||||
} else {
|
||||
return currentSubscription != nil ? .subscriptionExists : .subscriptionNotYetSetUp
|
||||
guard let currentSubscription = currentSubscription else {
|
||||
return .subscriptionNotYetSetUp
|
||||
}
|
||||
|
||||
if !ignoreProfileBadgeStateForNewBadgeRedemption && profileSubscriptionBadges.isEmpty {
|
||||
return .subscriptionNotYetSetUp
|
||||
}
|
||||
|
||||
if currentSubscription.active {
|
||||
return .subscriptionExists
|
||||
} else {
|
||||
return (subscriptionRedemptionFailureReason == .none) ? .subscriptionExists : .subscriptionNotYetSetUp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,12 +52,24 @@ class SubscriptionViewController: OWSTableViewController2 {
|
||||
}
|
||||
}
|
||||
|
||||
private var currentSubscription: Subscription?
|
||||
|
||||
private lazy var profileSubscriptionBadges: [OWSUserProfileBadgeInfo] = {
|
||||
return SubscriptionManager.currentProfileSubscriptionBadges()
|
||||
}()
|
||||
|
||||
private var selectedSubscription: SubscriptionLevel?
|
||||
|
||||
private var fetchedSubscriptionStatus = false
|
||||
private var fetchedSubscriptionStatus = false {
|
||||
didSet {
|
||||
if fetchedSubscriptionStatus {
|
||||
showBadgeExpirySheetIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var ignoreProfileBadgeStateForNewBadgeRedemption = false
|
||||
private var updatingSubscriptions = false
|
||||
private var currentSubscription: Subscription?
|
||||
private var persistedSubscriberID: Data?
|
||||
private var persistedSubscriberCurrencyCode: String?
|
||||
|
||||
@ -218,11 +242,11 @@ class SubscriptionViewController: OWSTableViewController2 {
|
||||
currencyCode = SubscriptionManager.getSubscriberCurrencyCode(transaction: transaction)
|
||||
}
|
||||
|
||||
if let subscripberID = subscriberID, let currencyCode = currencyCode {
|
||||
if let subscriberID = subscriberID, let currencyCode = currencyCode {
|
||||
self.persistedSubscriberID = subscriberID
|
||||
self.persistedSubscriberCurrencyCode = currencyCode
|
||||
firstly {
|
||||
SubscriptionManager.getCurrentSubscriptionStatus(for: subscripberID)
|
||||
SubscriptionManager.getCurrentSubscriptionStatus(for: subscriberID)
|
||||
}.done(on: .main) { subscription in
|
||||
self.fetchedSubscriptionStatus = true
|
||||
self.currentSubscription = subscription
|
||||
@ -244,6 +268,31 @@ class SubscriptionViewController: OWSTableViewController2 {
|
||||
}
|
||||
}
|
||||
|
||||
func showBadgeExpirySheetIfNeeded() {
|
||||
guard subscriptionViewState == .subscriptionNotYetSetUp, let subscriptions = subscriptions else {
|
||||
return
|
||||
}
|
||||
|
||||
let expiredBadgeID = SubscriptionManager.mostRecentlyExpiredBadgeIDWithSneakyTransaction()
|
||||
guard let expiredBadgeID = expiredBadgeID else {
|
||||
return
|
||||
}
|
||||
|
||||
let subscriptionLevel = subscriptions.first { $0.badge.id == expiredBadgeID }
|
||||
|
||||
guard let subscriptionLevel = subscriptionLevel else {
|
||||
owsFailDebug("Unable to find current subscription level for expired badge")
|
||||
return
|
||||
}
|
||||
|
||||
selectedSubscription = subscriptionLevel
|
||||
updateTableContents()
|
||||
|
||||
let sheet = BadgeExpirationSheet(badge: subscriptionLevel.badge)
|
||||
sheet.delegate = self
|
||||
self.present(sheet, animated: true)
|
||||
}
|
||||
|
||||
func updateTableContents() {
|
||||
let contents = OWSTableContents()
|
||||
defer {
|
||||
@ -473,16 +522,8 @@ class SubscriptionViewController: OWSTableViewController2 {
|
||||
))
|
||||
|
||||
// Current support level
|
||||
if let currentSubscription = currentSubscription, let subscriptions = subscriptions {
|
||||
let level = currentSubscription.level
|
||||
var subscriptionLevel: SubscriptionLevel?
|
||||
for subscription in subscriptions {
|
||||
if subscription.level == level {
|
||||
subscriptionLevel = subscription
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let subscriptionLevel = subscriptionLevelForSubscription(currentSubscription)
|
||||
if let subscriptionLevel = subscriptionLevel, let currentSubscription = currentSubscription {
|
||||
section.add(.init(
|
||||
customCellBlock: { [weak self] in
|
||||
guard let self = self else { return UITableViewCell() }
|
||||
@ -512,15 +553,10 @@ class SubscriptionViewController: OWSTableViewController2 {
|
||||
containerStackView.addArrangedSubview(stackView)
|
||||
stackView.autoPinWidthToSuperviewMargins()
|
||||
|
||||
guard let subscription = subscriptionLevel else {
|
||||
owsFailDebug("Can't find a matching description")
|
||||
return cell
|
||||
}
|
||||
|
||||
let imageView = UIImageView()
|
||||
imageView.setContentHuggingHigh()
|
||||
|
||||
if let badgeImage = subscription.badge.assets?.universal160 {
|
||||
if let badgeImage = subscriptionLevel.badge.assets?.universal160 {
|
||||
imageView.image = badgeImage
|
||||
}
|
||||
|
||||
@ -543,7 +579,7 @@ class SubscriptionViewController: OWSTableViewController2 {
|
||||
textStackView.alignment = .leading
|
||||
textStackView.spacing = 4
|
||||
|
||||
let localizedBadgeName = subscription.name
|
||||
let localizedBadgeName = subscriptionLevel.name
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = localizedBadgeName
|
||||
titleLabel.font = .ows_dynamicTypeBody.ows_semibold
|
||||
@ -1217,6 +1253,8 @@ extension SubscriptionViewController: PKPaymentAuthorizationControllerDelegate {
|
||||
throw SubscriptionError.assertion
|
||||
}
|
||||
|
||||
self.ignoreProfileBadgeStateForNewBadgeRedemption = true
|
||||
|
||||
progressView.stopAnimating(success: true) {
|
||||
backdropView.alpha = 0
|
||||
} completion: {
|
||||
@ -1484,3 +1522,15 @@ private class SubscriptionReadMoreSheet: InteractiveSheetViewController {
|
||||
stackView.addArrangedSubview(secondDescriptionBlock)
|
||||
}
|
||||
}
|
||||
|
||||
extension SubscriptionViewController: BadgeExpirationSheetDelegate {
|
||||
|
||||
func badgeExpirationSheetActionButtonTapped(_ badgeExpirationSheet: BadgeExpirationSheet) {
|
||||
requestApplePayDonation()
|
||||
}
|
||||
|
||||
func badgeExpirationSheetNotNowButtonTapped(_ badgeExpirationSheet: BadgeExpirationSheet) {
|
||||
SubscriptionManager.clearMostRecentlyExpiredBadgeIDWithSneakyTransaction()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -465,6 +465,8 @@ NSString *const kArchiveButtonPseudoGroup = @"kArchiveButtonPseudoGroup";
|
||||
|
||||
[self.searchResultsController viewDidAppear:animated];
|
||||
|
||||
[self showBadgeExpirationSheetIfNeeded];
|
||||
|
||||
self.hasEverAppeared = YES;
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SignalCoreKit
|
||||
import SignalServiceKit
|
||||
|
||||
@objc
|
||||
public extension HomeViewController {
|
||||
@ -51,6 +53,57 @@ public extension HomeViewController {
|
||||
|
||||
// MARK: -
|
||||
|
||||
func showBadgeExpirationSheetIfNeeded() {
|
||||
guard !hasEverAppeared else { // Do this once per launch
|
||||
return
|
||||
}
|
||||
|
||||
let expiredBadgeID = SubscriptionManager.mostRecentlyExpiredBadgeIDWithSneakyTransaction()
|
||||
guard let expiredBadgeID = expiredBadgeID, expiredBadgeID != "BOOST" else { // TODO Add boost support
|
||||
return
|
||||
}
|
||||
|
||||
let shouldShow = SDSDatabaseStorage.shared.read { transaction in
|
||||
SubscriptionManager.showExpirySheetOnHomeScreenKey(transaction: transaction)
|
||||
}
|
||||
|
||||
guard shouldShow else {
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch current subscriptions, required to populate badge assets
|
||||
firstly {
|
||||
SubscriptionManager.getSubscriptions()
|
||||
}.done(on: .global()) { (subscriptions: [SubscriptionLevel]) in
|
||||
for level in subscriptions {
|
||||
if level.badge.id == expiredBadgeID {
|
||||
// Fetch assets
|
||||
firstly {
|
||||
self.profileManager.badgeStore.populateAssetsOnBadge(level.badge)
|
||||
}.done(on: .main) {
|
||||
|
||||
SDSDatabaseStorage.shared.write { transaction in
|
||||
SubscriptionManager.setShowExpirySheetOnHomeScreenKey(show: false, transaction: transaction)
|
||||
}
|
||||
|
||||
let badgeSheet = BadgeExpirationSheet(badge: level.badge)
|
||||
badgeSheet.delegate = self
|
||||
self.present(badgeSheet, animated: true)
|
||||
|
||||
}.catch { error in
|
||||
owsFailDebug("Failed to fetch badge assets for expiry \(error)")
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}.catch { error in
|
||||
owsFailDebug("Failed to fetch subscriptions for expiry \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
func configureUnreadPaymentsBannerSingle(_ paymentsReminderView: UIView,
|
||||
paymentModel: TSPaymentModel,
|
||||
transaction: SDSAnyReadTransaction) {
|
||||
@ -226,6 +279,8 @@ public enum ShowAppSettingsMode {
|
||||
case paymentsTransferIn
|
||||
case appearance
|
||||
case avatarBuilder
|
||||
case subscriptions
|
||||
case boost
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
@ -299,8 +354,24 @@ public extension HomeViewController {
|
||||
let profile = ProfileSettingsViewController()
|
||||
viewControllers += [ profile ]
|
||||
completion = { profile.presentAvatarSettingsView() }
|
||||
case .subscriptions:
|
||||
let subscriptions = SubscriptionViewController()
|
||||
viewControllers += [ subscriptions ]
|
||||
case .boost:
|
||||
let boost = BoostViewController()
|
||||
viewControllers += [ boost ]
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
presentFormSheet(navigationController, animated: true, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension HomeViewController: BadgeExpirationSheetDelegate {
|
||||
func badgeExpirationSheetActionButtonTapped(_ badgeExpirationSheet: BadgeExpirationSheet) {
|
||||
SubscriptionManager.clearMostRecentlyExpiredBadgeIDWithSneakyTransaction()
|
||||
let mode: ShowAppSettingsMode = badgeExpirationSheet.badgeID == "BOOST" ? .boost : .subscriptions
|
||||
showAppSettings(mode: mode)
|
||||
}
|
||||
|
||||
func badgeExpirationSheetNotNowButtonTapped(_ badgeExpirationSheet: BadgeExpirationSheet) { }
|
||||
}
|
||||
|
||||
@ -385,6 +385,30 @@
|
||||
/* Text prompting the user to boost */
|
||||
"BADGE_DETAILS_GIVE_A_BOOST" = "Give Signal a Boost";
|
||||
|
||||
/* String explaing to the user that their boost badge has expired on the badge expiry sheetsheet. */
|
||||
"BADGE_EXIPRED_BOOST_BODY_FORMAT" = "Your Boost badge has expired, and is no longer visible on your profile.\n\nYou can continue using Signal but to support technology that is built for you, please consider becoming a monthly Sustainer.";
|
||||
|
||||
/* String explaing to the user that their boost badge has expired while they are a current subscription sustainer on the badge expiry sheetsheet. */
|
||||
"BADGE_EXIPRED_BOOST_CURRENT_SUSTAINER_BODY_FORMAT" = "Your Boost badge has expired, and is no longer visible on your profile.\n\nYou can reactivate your Boost badge for another 30 days with a one-time contribution.";
|
||||
|
||||
/* String explaing to the user that their subscription badge has expired on the badge expiry sheetsheet. Embed {badge name}. */
|
||||
"BADGE_EXIPRED_SUBSCRIPTION_BODY_FORMAT" = "Your Sustainer subscription was automatically cancelled because you were inactive for too long. Your %@ badge is no longer visible on your profile.\n\nYou can keep using Signal but to support the app and reactivate your badge, renew now.";
|
||||
|
||||
/* Button title for boost on the badge expiration sheet, used if the user is not already a sustainer. */
|
||||
"BADGE_EXPIRED_BOOST_RENEWAL_BUTTON" = "Become a Sustainer";
|
||||
|
||||
/* Button title for boost on the badge expiration sheet, used if the user is already a sustainer. */
|
||||
"BADGE_EXPIRED_BOOST_RENEWAL_BUTTON_SUSTAINER" = "Add a Boost";
|
||||
|
||||
/* Title for boost on the badge expiration sheet. */
|
||||
"BADGE_EXPIRED_BOOST_TITLE" = "Badge Expired";
|
||||
|
||||
/* Button title for subscription on the badge expiration sheet. */
|
||||
"BADGE_EXPIRED_SUBSCRIPTION_RENEWAL_BUTTON" = "Renew Subscription";
|
||||
|
||||
/* Title for subscription on the badge expiration sheet. */
|
||||
"BADGE_EXPIRED_SUBSCRIPTION_TITLE" = "Subscription Cancelled";
|
||||
|
||||
/* Title for boost on the thank you sheet. */
|
||||
"BADGE_THANKS_BOOST_TITLE" = "Thanks for the Boost!";
|
||||
|
||||
|
||||
@ -165,6 +165,7 @@ public class SubscriptionReceiptCredentailRedemptionOperation: OWSOperation, Dur
|
||||
)
|
||||
}.done(on: .global()) {
|
||||
Logger.info("[Subscriptions] Successfully redeemed receipt credential presentation")
|
||||
SubscriptionManager.mostRecentlyExpiredBadgeIDWithSneakyTransaction()
|
||||
self.didSucceed()
|
||||
}.catch(on: .global()) { error in
|
||||
self.reportError(error)
|
||||
|
||||
@ -91,12 +91,17 @@ public class SubscriptionManager: NSObject {
|
||||
public static let SubscriptionJobQueueDidFinishJobNotification = NSNotification.Name("SubscriptionJobQueueDidFinishJobNotification")
|
||||
public static let SubscriptionJobQueueDidFailJobNotification = NSNotification.Name("SubscriptionJobQueueDidFailJobNotification")
|
||||
private static let subscriptionKVS = SDSKeyValueStore(collection: "SubscriptionKeyValueStore")
|
||||
private static let subscriberIDKey = "subscriberID"
|
||||
private static let subscriberCurrencyCodeKey = "subscriberCurrencyCode"
|
||||
private static let lastSubscriptionExpirationKey = "subscriptionExpiration"
|
||||
private static let lastSubscriptionHeartbeatKey = "subscriptionHeartbeat"
|
||||
private static let lastSubscriptionReceiptRedemptionFailedKey = "lastSubscriptionReceiptRedemptionFailedKey"
|
||||
private static let userManuallyCancelledSubscriptionKey = "userManuallyCancelledSubscriptionKey"
|
||||
|
||||
fileprivate static let subscriberIDKey = "subscriberID"
|
||||
fileprivate static let subscriberCurrencyCodeKey = "subscriberCurrencyCode"
|
||||
fileprivate static let lastSubscriptionExpirationKey = "subscriptionExpiration"
|
||||
fileprivate static let lastSubscriptionHeartbeatKey = "subscriptionHeartbeat"
|
||||
fileprivate static let lastSubscriptionReceiptRedemptionFailedKey = "lastSubscriptionReceiptRedemptionFailedKey"
|
||||
fileprivate static let userManuallyCancelledSubscriptionKey = "userManuallyCancelledSubscriptionKey"
|
||||
fileprivate static let knownUserSubscriptionBadgeIDsKey = "knownUserSubscriptionBadgeIDsKey"
|
||||
fileprivate static let knownUserBoostBadgeIDsKey = "knownUserBoostBadgeIDsKey"
|
||||
fileprivate static let mostRecentlyExpiredBadgeIDKey = "mostRecentlyExpiredBadgeIDKey"
|
||||
fileprivate static let showExpirySheetOnHomeScreenKey = "showExpirySheetOnHomeScreenKey"
|
||||
|
||||
public static var terminateTransactionIfPossible = false
|
||||
|
||||
@ -155,8 +160,22 @@ public class SubscriptionManager: NSObject {
|
||||
return false
|
||||
}
|
||||
|
||||
return lastSubscriptionExpiryDate.isAfterNow
|
||||
if currentProfileSubscriptionBadges().count == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return lastSubscriptionExpiryDate.isAfterNow
|
||||
}
|
||||
|
||||
public class func currentProfileSubscriptionBadges() -> [OWSUserProfileBadgeInfo] {
|
||||
let snapshot = profileManagerImpl.localProfileSnapshot(shouldIncludeAvatar: true)
|
||||
let profileBadges = snapshot.profileBadgeInfo ?? []
|
||||
return profileBadges.compactMap { (badge: OWSUserProfileBadgeInfo) -> OWSUserProfileBadgeInfo? in
|
||||
if badge.badgeId != "BOOST" {
|
||||
return badge
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public class func getCurrentSubscriptionStatus(for subscriberID: Data) -> Promise<Subscription?> {
|
||||
@ -189,77 +208,6 @@ public class SubscriptionManager: NSObject {
|
||||
|
||||
// MARK: Subscription management
|
||||
|
||||
public static func getSubscriberID(transaction: SDSAnyReadTransaction) -> Data? {
|
||||
guard let subscriberID = subscriptionKVS.getObject(
|
||||
forKey: subscriberIDKey,
|
||||
transaction: transaction
|
||||
) as? Data else {
|
||||
return nil
|
||||
}
|
||||
return subscriberID
|
||||
}
|
||||
|
||||
public static func setSubscriberID(_ subscriberID: Data?, transaction: SDSAnyWriteTransaction) {
|
||||
subscriptionKVS.setObject(subscriberID,
|
||||
key: subscriberIDKey,
|
||||
transaction: transaction)
|
||||
}
|
||||
|
||||
public static func getSubscriberCurrencyCode(transaction: SDSAnyReadTransaction) -> String? {
|
||||
guard let subscriberCurrencyCode = subscriptionKVS.getObject(
|
||||
forKey: subscriberCurrencyCodeKey,
|
||||
transaction: transaction
|
||||
) as? String else {
|
||||
return nil
|
||||
}
|
||||
return subscriberCurrencyCode
|
||||
}
|
||||
|
||||
public static func setSubscriberCurrencyCode(_ currencyCode: String?, transaction: SDSAnyWriteTransaction) {
|
||||
subscriptionKVS.setObject(currencyCode,
|
||||
key: subscriberCurrencyCodeKey,
|
||||
transaction: transaction)
|
||||
}
|
||||
|
||||
public static func setLastReceiptRedemptionFailed(failureReason: SubscriptionRedemptionFailureReason, transaction: SDSAnyWriteTransaction) {
|
||||
subscriptionKVS.setInt(failureReason.rawValue, key: lastSubscriptionReceiptRedemptionFailedKey, transaction: transaction)
|
||||
}
|
||||
|
||||
public static func lastReceiptRedemptionFailed(transaction: SDSAnyReadTransaction) -> SubscriptionRedemptionFailureReason {
|
||||
let intValue = subscriptionKVS.getInt(lastSubscriptionReceiptRedemptionFailedKey, transaction: transaction)
|
||||
guard let intValue = intValue else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if let reason = SubscriptionRedemptionFailureReason(rawValue: intValue) {
|
||||
return reason
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
public static func userManuallyCancelledSubscription(transaction: SDSAnyReadTransaction) -> Bool {
|
||||
return subscriptionKVS.getBool(userManuallyCancelledSubscriptionKey, transaction: transaction) ?? false
|
||||
}
|
||||
|
||||
private static func setUserManuallyCancelledSubscription(_ userCancelled: Bool, transaction: SDSAnyWriteTransaction) {
|
||||
subscriptionKVS.setBool(userCancelled, key: userManuallyCancelledSubscriptionKey, transaction: transaction)
|
||||
}
|
||||
|
||||
private static func lastSubscriptionExpirationDate(transaction: SDSAnyReadTransaction) -> Date? {
|
||||
return subscriptionKVS.getDate(lastSubscriptionExpirationKey, transaction: transaction)
|
||||
}
|
||||
|
||||
private static func setLastSubscriptionExpirationDate(_ expirationDate: Date?, transaction: SDSAnyWriteTransaction) {
|
||||
if let expirationDate = expirationDate {
|
||||
subscriptionKVS.setDate(expirationDate, key: lastSubscriptionExpirationKey, transaction: transaction)
|
||||
} else {
|
||||
subscriptionKVS.removeValue(forKey: lastSubscriptionExpirationKey, transaction: transaction)
|
||||
}
|
||||
|
||||
OWSSyncManager.shared.sendFetchLatestSubscriptionStatusSyncMessage()
|
||||
}
|
||||
|
||||
private class func setupNewSubscriberID() throws -> Promise<Data> {
|
||||
Logger.info("[Subscriptions] Setting up new subscriber ID")
|
||||
let newSubscriberID = generateSubscriberID()
|
||||
@ -306,6 +254,7 @@ public class SubscriptionManager: NSObject {
|
||||
generatedSubscriberID = subscriberID
|
||||
|
||||
SDSDatabaseStorage.shared.write { transaction in
|
||||
self.setUserManuallyCancelledSubscription(false, transaction: transaction)
|
||||
self.setSubscriberID(subscriberID, transaction: transaction)
|
||||
self.setSubscriberCurrencyCode(currencyCode, transaction: transaction)
|
||||
self.storageServiceManager.recordPendingLocalAccountUpdates()
|
||||
@ -675,11 +624,6 @@ public class SubscriptionManager: NSObject {
|
||||
@objc
|
||||
public class func performSubscriptionKeepAliveIfNecessary() {
|
||||
|
||||
guard tsAccountManager.isPrimaryDevice else {
|
||||
Logger.info("[Subscriptions] Bailing out of heartbeat, this is not the primary device")
|
||||
return
|
||||
}
|
||||
|
||||
// Kick job queue
|
||||
subscriptionJobQueue.runAnyQueuedRetry()
|
||||
|
||||
@ -709,6 +653,14 @@ public class SubscriptionManager: NSObject {
|
||||
|
||||
Logger.info("[Subscriptions] Performing subscription heartbeat")
|
||||
|
||||
// Update badge states first
|
||||
mostRecentlyExpiredBadgeIDWithSneakyTransaction()
|
||||
|
||||
guard tsAccountManager.isPrimaryDevice else {
|
||||
Logger.info("[Subscriptions] Bailing out of remaining heartbeat tasks, this is not the primary device")
|
||||
return
|
||||
}
|
||||
|
||||
guard let subscriberID = subscriberID, currencyCode != nil else {
|
||||
Logger.info("[Subscriptions] No subscription + currency code found")
|
||||
self.updateSubscriptionHeartbeatDate()
|
||||
@ -751,6 +703,65 @@ public class SubscriptionManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
// Reconciles cached non-expired badge IDs with current profile badges, and returns the last expired ID if one exists.
|
||||
// Also sets mostRecentlyExpiredBadgeID
|
||||
public static func mostRecentlyExpiredBadgeIDWithSneakyTransaction() -> String? {
|
||||
Logger.info("[Subscriptions] Checking if current badge set has changed")
|
||||
|
||||
// Get current badges
|
||||
let snapshot = profileManagerImpl.localProfileSnapshot(shouldIncludeAvatar: true)
|
||||
let currentBadges: [OWSUserProfileBadgeInfo] = snapshot.profileBadgeInfo ?? []
|
||||
let currentSubscriberBadgeIDs = currentBadges.compactMap { (badge: OWSUserProfileBadgeInfo) -> String? in
|
||||
if badge.badgeId != "BOOST" {
|
||||
return badge.badgeId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let currentBoostBadgeIDs = currentBadges.compactMap { (badge: OWSUserProfileBadgeInfo) -> String? in
|
||||
if badge.badgeId == "BOOST" {
|
||||
return badge.badgeId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var persistedSubscriberBadgeIDs: [String] = [], persistedBoostBadgeIDs: [String] = [], expiringBadgeID: String?, userManuallyCancelled = false, showExpiryOnHomeScreen = false
|
||||
|
||||
SDSDatabaseStorage.shared.read { transaction in
|
||||
persistedSubscriberBadgeIDs = self.knownUserSubscriptionBadgeIDs(transaction: transaction)
|
||||
persistedBoostBadgeIDs = self.knownUserBoostBadgeIDs(transaction: transaction)
|
||||
expiringBadgeID = self.mostRecentlyExpiredBadgeID(transaction: transaction)
|
||||
userManuallyCancelled = self.userManuallyCancelledSubscription(transaction: transaction)
|
||||
showExpiryOnHomeScreen = self.showExpirySheetOnHomeScreenKey(transaction: transaction)
|
||||
}
|
||||
|
||||
var newExpiringBadgeID: String?
|
||||
if currentSubscriberBadgeIDs.count == 0 && persistedSubscriberBadgeIDs.count > 0 && !userManuallyCancelled {
|
||||
newExpiringBadgeID = persistedSubscriberBadgeIDs.first
|
||||
} else if currentBoostBadgeIDs.count == 0 && persistedBoostBadgeIDs.count > 0 {
|
||||
newExpiringBadgeID = persistedBoostBadgeIDs.first
|
||||
}
|
||||
|
||||
if let newExpiringBadgeID = newExpiringBadgeID, newExpiringBadgeID != expiringBadgeID {
|
||||
expiringBadgeID = newExpiringBadgeID
|
||||
showExpiryOnHomeScreen = true
|
||||
}
|
||||
|
||||
Logger.info("[Subscriptions] Current sub badges \(currentSubscriberBadgeIDs.count), persisted sub badges \(persistedSubscriberBadgeIDs.count)")
|
||||
Logger.info("[Subscriptions] Current boost badges \(currentBoostBadgeIDs.count), persisted sub badges \(persistedBoostBadgeIDs.count)")
|
||||
Logger.info("[Subscriptions] mostRecentlyExpiredBadgeID is \(expiringBadgeID ?? "none")")
|
||||
|
||||
// Persist new values
|
||||
SDSDatabaseStorage.shared.write { transaction in
|
||||
self.setKnownUserSubscriptionBadgeIDs(badgeIDs: currentSubscriberBadgeIDs, transaction: transaction)
|
||||
self.setKnownUserBoostBadgeIDs(badgeIDs: currentBoostBadgeIDs, transaction: transaction)
|
||||
self.setMostRecentlyExpiredBadgeID(badgeID: expiringBadgeID, transaction: transaction)
|
||||
self.setShowExpirySheetOnHomeScreenKey(show: showExpiryOnHomeScreen, transaction: transaction)
|
||||
}
|
||||
|
||||
return expiringBadgeID
|
||||
}
|
||||
|
||||
private static func updateSubscriptionHeartbeatDate() {
|
||||
SDSDatabaseStorage.shared.write { transaction in
|
||||
// Update keepalive
|
||||
@ -799,6 +810,129 @@ public class SubscriptionManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
extension SubscriptionManager {
|
||||
|
||||
public static func getSubscriberID(transaction: SDSAnyReadTransaction) -> Data? {
|
||||
guard let subscriberID = subscriptionKVS.getObject(
|
||||
forKey: subscriberIDKey,
|
||||
transaction: transaction
|
||||
) as? Data else {
|
||||
return nil
|
||||
}
|
||||
return subscriberID
|
||||
}
|
||||
|
||||
public static func setSubscriberID(_ subscriberID: Data?, transaction: SDSAnyWriteTransaction) {
|
||||
subscriptionKVS.setObject(subscriberID,
|
||||
key: subscriberIDKey,
|
||||
transaction: transaction)
|
||||
}
|
||||
|
||||
public static func getSubscriberCurrencyCode(transaction: SDSAnyReadTransaction) -> String? {
|
||||
guard let subscriberCurrencyCode = subscriptionKVS.getObject(
|
||||
forKey: subscriberCurrencyCodeKey,
|
||||
transaction: transaction
|
||||
) as? String else {
|
||||
return nil
|
||||
}
|
||||
return subscriberCurrencyCode
|
||||
}
|
||||
|
||||
public static func setSubscriberCurrencyCode(_ currencyCode: String?, transaction: SDSAnyWriteTransaction) {
|
||||
subscriptionKVS.setObject(currencyCode,
|
||||
key: subscriberCurrencyCodeKey,
|
||||
transaction: transaction)
|
||||
}
|
||||
|
||||
public static func setLastReceiptRedemptionFailed(failureReason: SubscriptionRedemptionFailureReason, transaction: SDSAnyWriteTransaction) {
|
||||
subscriptionKVS.setInt(failureReason.rawValue, key: lastSubscriptionReceiptRedemptionFailedKey, transaction: transaction)
|
||||
}
|
||||
|
||||
public static func lastReceiptRedemptionFailed(transaction: SDSAnyReadTransaction) -> SubscriptionRedemptionFailureReason {
|
||||
let intValue = subscriptionKVS.getInt(lastSubscriptionReceiptRedemptionFailedKey, transaction: transaction)
|
||||
guard let intValue = intValue else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if let reason = SubscriptionRedemptionFailureReason(rawValue: intValue) {
|
||||
return reason
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
public static func userManuallyCancelledSubscription(transaction: SDSAnyReadTransaction) -> Bool {
|
||||
return subscriptionKVS.getBool(userManuallyCancelledSubscriptionKey, transaction: transaction) ?? false
|
||||
}
|
||||
|
||||
private static func setUserManuallyCancelledSubscription(_ userCancelled: Bool, transaction: SDSAnyWriteTransaction) {
|
||||
subscriptionKVS.setBool(userCancelled, key: userManuallyCancelledSubscriptionKey, transaction: transaction)
|
||||
}
|
||||
|
||||
fileprivate static func lastSubscriptionExpirationDate(transaction: SDSAnyReadTransaction) -> Date? {
|
||||
return subscriptionKVS.getDate(lastSubscriptionExpirationKey, transaction: transaction)
|
||||
}
|
||||
|
||||
fileprivate static func setLastSubscriptionExpirationDate(_ expirationDate: Date?, transaction: SDSAnyWriteTransaction) {
|
||||
guard let expirationDate = expirationDate else {
|
||||
subscriptionKVS.removeValue(forKey: lastSubscriptionExpirationKey, transaction: transaction)
|
||||
return
|
||||
}
|
||||
|
||||
subscriptionKVS.setDate(expirationDate, key: lastSubscriptionExpirationKey, transaction: transaction)
|
||||
}
|
||||
|
||||
fileprivate static func setKnownUserSubscriptionBadgeIDs(badgeIDs: [String], transaction: SDSAnyWriteTransaction) {
|
||||
subscriptionKVS.setObject(badgeIDs, key: knownUserSubscriptionBadgeIDsKey, transaction: transaction)
|
||||
}
|
||||
|
||||
fileprivate static func knownUserSubscriptionBadgeIDs(transaction: SDSAnyReadTransaction) -> [String] {
|
||||
let ids = subscriptionKVS.getObject(forKey: knownUserSubscriptionBadgeIDsKey, transaction: transaction) as? [String]
|
||||
return ids ?? []
|
||||
}
|
||||
|
||||
fileprivate static func setKnownUserBoostBadgeIDs(badgeIDs: [String], transaction: SDSAnyWriteTransaction) {
|
||||
subscriptionKVS.setObject(badgeIDs, key: knownUserBoostBadgeIDsKey, transaction: transaction)
|
||||
}
|
||||
|
||||
fileprivate static func knownUserBoostBadgeIDs(transaction: SDSAnyReadTransaction) -> [String] {
|
||||
guard let ids = subscriptionKVS.getObject(forKey: knownUserBoostBadgeIDsKey, transaction: transaction) as? [String] else {
|
||||
return []
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
fileprivate static func setMostRecentlyExpiredBadgeID(badgeID: String?, transaction: SDSAnyWriteTransaction) {
|
||||
guard let badgeID = badgeID else {
|
||||
subscriptionKVS.removeValue(forKey: mostRecentlyExpiredBadgeIDKey, transaction: transaction)
|
||||
return
|
||||
}
|
||||
|
||||
subscriptionKVS.setString(badgeID, key: mostRecentlyExpiredBadgeIDKey, transaction: transaction)
|
||||
|
||||
}
|
||||
|
||||
public static func mostRecentlyExpiredBadgeID(transaction: SDSAnyReadTransaction) -> String? {
|
||||
subscriptionKVS.getString(mostRecentlyExpiredBadgeIDKey, transaction: transaction)
|
||||
}
|
||||
|
||||
public static func clearMostRecentlyExpiredBadgeIDWithSneakyTransaction() {
|
||||
SDSDatabaseStorage.shared.write { transaction in
|
||||
self.setMostRecentlyExpiredBadgeID(badgeID: nil, transaction: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
public static func setShowExpirySheetOnHomeScreenKey(show: Bool, transaction: SDSAnyWriteTransaction) {
|
||||
subscriptionKVS.setBool(show, key: showExpirySheetOnHomeScreenKey, transaction: transaction)
|
||||
}
|
||||
|
||||
public static func showExpirySheetOnHomeScreenKey(transaction: SDSAnyReadTransaction) -> Bool {
|
||||
return subscriptionKVS.getBool(showExpirySheetOnHomeScreenKey, transaction: transaction) ?? false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc
|
||||
public class OWSRetryableSubscriptionError: NSObject, CustomNSError, IsRetryableProvider {
|
||||
@objc
|
||||
|
||||
Loading…
Reference in New Issue
Block a user