Skip to content

Commit 90d2f2e

Browse files
committed
Improved December 2024 promo
1 parent 534ca9d commit 90d2f2e

File tree

8 files changed

+71
-58
lines changed

8 files changed

+71
-58
lines changed

Cryptomator/MainCoordinator.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ class MainCoordinator: NSObject, Coordinator, UINavigationControllerDelegate {
7777
rootViewController.showDetailViewController(detailNavigationController, sender: nil)
7878
}
7979

80+
// Temporarily added for December 2024 Sale
81+
func showPurchase() {
82+
let modalNavigationController = BaseNavigationController()
83+
let child = PurchaseCoordinator(navigationController: modalNavigationController)
84+
childCoordinators.append(child)
85+
navigationController.topViewController?.present(modalNavigationController, animated: true)
86+
child.start()
87+
}
88+
8089
// MARK: - UINavigationControllerDelegate
8190

8291
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {

Cryptomator/Purchase/Cells/PurchaseCell.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import UIKit
1212

1313
struct PurchaseCellViewModel: Hashable {
1414
let productName: String
15+
let productDetail: String?
1516
let price: String
1617
let purchaseDetail: String?
1718
let purchaseButtonViewModel = PurchaseButtonViewModel()
@@ -36,6 +37,7 @@ class PurchaseCell: IAPCell {
3637

3738
func configure(with viewModel: PurchaseCellViewModel) {
3839
productTitleLabel.text = viewModel.productName
40+
productDetailLabel.text = viewModel.productDetail
3941
accessory.button.setTitle(viewModel.price, for: .normal)
4042
accessory.detailLabel.text = viewModel.purchaseDetail
4143
accessory.configure(with: viewModel.purchaseButtonViewModel)

Cryptomator/Purchase/PurchaseViewModel.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ class PurchaseViewModel: BaseIAPViewModel, ProductFetching {
3232
override var title: String? {
3333
return LocalizedString.getValue("purchase.title")
3434
}
35+
// Temporarily added for December 2024 Sale
36+
override var infoText: NSAttributedString? {
37+
let currentYear = Calendar.current.component(.year, from: Date())
38+
let currentMonth = Calendar.current.component(.month, from: Date())
39+
// TODO: Change `currentMonth == 11` to `12`
40+
if currentYear == 2024 && currentMonth == 11 {
41+
return .textWithLeadingSystemImage("info.circle.fill",
42+
text: LocalizedString.getValue("purchase.discount"),
43+
font: .preferredFont(forTextStyle: .body),
44+
color: .secondaryLabel)
45+
} else {
46+
return nil
47+
}
48+
}
3549

3650
private let cryptomatorSettings: CryptomatorSettings
3751

@@ -56,6 +70,7 @@ class PurchaseViewModel: BaseIAPViewModel, ProductFetching {
5670
cells.append(.trialCell(TrialCellViewModel(expirationDate: trialExpirationDate)))
5771
} else {
5872
cells.append(.purchaseCell(PurchaseCellViewModel(productName: LocalizedString.getValue("purchase.product.trial"),
73+
productDetail: nil,
5974
price: LocalizedString.getValue("purchase.product.pricing.free"),
6075
purchaseDetail: LocalizedString.getValue("purchase.product.trial.duration"),
6176
productIdentifier: .thirtyDayTrial)))
@@ -65,6 +80,7 @@ class PurchaseViewModel: BaseIAPViewModel, ProductFetching {
6580
private func addSubscriptionItem() {
6681
if let product = products[.yearlySubscription], let localizedPrice = product.localizedPrice {
6782
let viewModel = PurchaseCellViewModel(productName: LocalizedString.getValue("purchase.product.yearlySubscription"),
83+
productDetail: nil,
6884
price: localizedPrice,
6985
purchaseDetail: LocalizedString.getValue("purchase.product.yearlySubscription.duration"),
7086
productIdentifier: .yearlySubscription)
@@ -75,6 +91,7 @@ class PurchaseViewModel: BaseIAPViewModel, ProductFetching {
7591
private func addLifetimeLicenseItem() {
7692
if let product = products[.fullVersion], let localizedPrice = product.localizedPrice {
7793
let viewModel = PurchaseCellViewModel(productName: LocalizedString.getValue("purchase.product.lifetimeLicense"),
94+
productDetail: LocalizedString.getValue("purchase.product.lifetimeLicense.detail"),
7895
price: localizedPrice,
7996
purchaseDetail: LocalizedString.getValue("purchase.product.lifetimeLicense.duration"),
8097
productIdentifier: .fullVersion)

Cryptomator/Purchase/UpgradeViewModel.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class UpgradeViewModel: BaseIAPViewModel, ProductFetching {
3434
func addFreeUpgradeItem() {
3535
guard products[.freeUpgrade] != nil else { return }
3636
let viewModel = PurchaseCellViewModel(productName: LocalizedString.getValue("purchase.product.freeUpgrade"),
37+
productDetail: nil,
3738
price: LocalizedString.getValue("purchase.product.pricing.free"),
3839
purchaseDetail: nil,
3940
productIdentifier: .freeUpgrade)
@@ -43,6 +44,7 @@ class UpgradeViewModel: BaseIAPViewModel, ProductFetching {
4344
func addPaidUpgradeItem() {
4445
if let product = products[.paidUpgrade], let localizedPrice = product.localizedPrice {
4546
let viewModel = PurchaseCellViewModel(productName: LocalizedString.getValue("purchase.product.donateAndUpgrade"),
47+
productDetail: nil,
4648
price: localizedPrice,
4749
purchaseDetail: LocalizedString.getValue("purchase.product.lifetimeLicense.duration"),
4850
productIdentifier: .paidUpgrade)

Cryptomator/VaultList/VaultListViewController.swift

Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,9 @@ class VaultListViewController: ListViewController<VaultCellViewModel> {
2121
@Dependency(\.fullVersionChecker) private var fullVersionChecker
2222
@Dependency(\.cryptomatorSettings) private var cryptomatorSettings
2323

24+
#if !ALWAYS_PREMIUM
2425
private var bannerView: UIView?
25-
private var bannerDismissed: Bool {
26-
get {
27-
return CryptomatorUserDefaults.shared.bannerDismissed
28-
}
29-
set {
30-
CryptomatorUserDefaults.shared.bannerDismissed = newValue
31-
}
32-
}
26+
#endif
3327

3428
init(with viewModel: VaultListViewModelProtocol) {
3529
self.viewModel = viewModel
@@ -61,7 +55,9 @@ class VaultListViewController: ListViewController<VaultCellViewModel> {
6155
}
6256
}
6357

58+
#if !ALWAYS_PREMIUM
6459
checkAndShowBanner()
60+
#endif
6561
}
6662

6763
override func viewWillAppear(_ animated: Bool) {
@@ -124,14 +120,12 @@ class VaultListViewController: ListViewController<VaultCellViewModel> {
124120

125121
// MARK: - Discount Banner
126122

123+
#if !ALWAYS_PREMIUM
127124
private func checkAndShowBanner() {
128-
let calendar = Calendar.current
129-
let isTargetMonth = calendar.component(.year, from: Date()) == 2024 &&
130-
calendar.component(.month, from: Date()) == 11
131-
132-
if isTargetMonth &&
133-
!(cryptomatorSettings.fullVersionUnlocked || cryptomatorSettings.hasRunningSubscription) &&
134-
!bannerDismissed {
125+
let currentYear = Calendar.current.component(.year, from: Date())
126+
let currentMonth = Calendar.current.component(.month, from: Date())
127+
// TODO: Change `currentMonth == 11` to `12`
128+
if currentYear == 2024 && currentMonth == 11 && !(cryptomatorSettings.fullVersionUnlocked || cryptomatorSettings.hasRunningSubscription) && !cryptomatorSettings.december2024BannerDismissed {
135129
showBanner()
136130
}
137131
}
@@ -146,19 +140,23 @@ class VaultListViewController: ListViewController<VaultCellViewModel> {
146140
let emojiLabel = UILabel()
147141
emojiLabel.text = "🎁"
148142
emojiLabel.translatesAutoresizingMaskIntoConstraints = false
143+
emojiLabel.setContentHuggingPriority(.required, for: .horizontal)
144+
emojiLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
145+
149146

150147
let textLabel = UILabel()
151148
textLabel.text = LocalizedString.getValue("purchase.discount")
152149
textLabel.textColor = .white
153-
textLabel.font = UIFont.boldSystemFont(ofSize: 14)
150+
textLabel.font = UIFont.preferredFont(forTextStyle: .body)
151+
textLabel.adjustsFontSizeToFitWidth = true
152+
textLabel.minimumScaleFactor = 0.5
154153
textLabel.translatesAutoresizingMaskIntoConstraints = false
155154

156-
let dismissButton = UIButton(type: .system)
157-
dismissButton.setTitle("X", for: .normal)
158-
dismissButton.setTitleColor(.white, for: .normal)
159-
dismissButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 12)
155+
let dismissButton = UIButton(type: .close)
160156
dismissButton.addTarget(self, action: #selector(dismissBanner), for: .touchUpInside)
161157
dismissButton.translatesAutoresizingMaskIntoConstraints = false
158+
dismissButton.setContentHuggingPriority(.required, for: .horizontal)
159+
dismissButton.setContentCompressionResistancePriority(.required, for: .horizontal)
162160

163161
banner.addSubview(emojiLabel)
164162
banner.addSubview(textLabel)
@@ -193,34 +191,17 @@ class VaultListViewController: ListViewController<VaultCellViewModel> {
193191
}
194192

195193
@objc private func dismissBanner() {
196-
bannerView?.removeFromSuperview()
197-
bannerDismissed = true
194+
UIView.animate(withDuration: 0.3, animations: {
195+
self.bannerView?.alpha = 0
196+
}, completion: { _ in
197+
self.bannerView?.removeFromSuperview()
198+
self.bannerView = nil
199+
})
200+
CryptomatorUserDefaults.shared.december2024BannerDismissed = true
198201
}
199202

200203
@objc private func bannerTapped() {
201-
let purchaseViewModel = PurchaseViewModel()
202-
let purchaseViewController = PurchaseViewController(viewModel: purchaseViewModel)
203-
purchaseViewController.modalPresentationStyle = .pageSheet
204-
205-
let navigationController = UINavigationController(rootViewController: purchaseViewController)
206-
navigationController.modalPresentationStyle = .pageSheet
207-
208-
let appearance = UINavigationBarAppearance()
209-
appearance.configureWithOpaqueBackground()
210-
appearance.backgroundColor = UIColor.cryptomatorPrimary
211-
appearance.titleTextAttributes = [.foregroundColor: UIColor.white]
212-
213-
navigationController.navigationBar.standardAppearance = appearance
214-
navigationController.navigationBar.scrollEdgeAppearance = appearance
215-
navigationController.navigationBar.tintColor = .white
216-
217-
let closeButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissIAPView))
218-
purchaseViewController.navigationItem.rightBarButtonItem = closeButton
219-
220-
present(navigationController, animated: true, completion: nil)
221-
}
222-
223-
@objc private func dismissIAPView() {
224-
dismiss(animated: true, completion: nil)
204+
coordinator?.showPurchase()
225205
}
206+
#endif
226207
}

CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorUserDefaults.swift

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ public protocol CryptomatorSettings {
1515
var trialExpirationDate: Date? { get set }
1616
var fullVersionUnlocked: Bool { get set }
1717
var hasRunningSubscription: Bool { get set }
18-
var bannerDismissed: Bool { get set }
18+
#if !ALWAYS_PREMIUM
19+
var december2024BannerDismissed: Bool { get set }
20+
#endif
1921
}
2022

2123
private enum CryptomatorSettingsKey: DependencyKey {
@@ -109,14 +111,11 @@ extension CryptomatorUserDefaults: CryptomatorSettings {
109111
get { read() ?? false }
110112
set { write(value: newValue) }
111113
}
112-
113-
public var bannerDismissed: Bool {
114-
get {
115-
let value = read(property: "bannerDismissed") ?? false
116-
return value
117-
}
118-
set {
119-
write(value: newValue, to: "bannerDismissed")
120-
}
114+
115+
#if !ALWAYS_PREMIUM
116+
public var december2024BannerDismissed: Bool {
117+
get { read() ?? false }
118+
set { write(value: newValue) }
121119
}
120+
#endif
122121
}

CryptomatorCommon/Sources/CryptomatorCommonCore/Mocks/CryptomatorSettingsMock.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ class CryptomatorSettingsMock: CryptomatorSettings {
1414
var debugModeEnabled: Bool = false
1515
var fullVersionUnlocked: Bool = false
1616
var hasRunningSubscription: Bool = false
17-
var bannerDismissed: Bool = false
17+
#if !ALWAYS_PREMIUM
18+
var december2024BannerDismissed: Bool = false
19+
#endif
1820
}
1921
#endif

SharedResources/en.lproj/Localizable.strings

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
"onboarding.button.continue" = "Continue";
163163

164164
"purchase.beginFreeTrial.alert.title" = "Trial Unlocked";
165-
"purchase.discount" = "Full Version IAP is 30% off in December";
165+
"purchase.discount" = "Lifetime License is 33% off in December!";
166166
"purchase.expiredTrial" = "Your trial has expired.";
167167
"purchase.footer.privacyPolicy" = "Privacy Policy";
168168
"purchase.footer.termsOfUse" = "Terms of Use";
@@ -172,6 +172,7 @@
172172
"purchase.product.donateAndUpgrade" = "Donate & Upgrade";
173173
"purchase.product.freeUpgrade" = "Free Upgrade";
174174
"purchase.product.lifetimeLicense" = "Lifetime License";
175+
"purchase.product.lifetimeLicense.detail" = "🎁 33% off in December";
175176
"purchase.product.lifetimeLicense.duration" = "one-time";
176177
"purchase.product.pricing.free" = "Free";
177178
"purchase.product.trial" = "30-Day Trial";

0 commit comments

Comments
 (0)