From 53334e5fb87213f2920384719bd9a57f79dd39ca Mon Sep 17 00:00:00 2001 From: Trey t Date: Mon, 18 Jul 2022 20:14:48 -0500 Subject: [PATCH] IAP --- Shared/FeelsApp.swift | 3 +- Shared/IAPManager.swift | 51 +++++++++++++++++--- Shared/Models/UserDefaultsStore.swift | 2 + Shared/views/AddMoodHeaderView.swift | 1 + Shared/views/DayView/DayView.swift | 4 ++ Shared/views/MonthView/MonthView.swift | 1 + Shared/views/PurchaseButtonView.swift | 35 +++++++++++++- Shared/views/SettingsView/SettingsView.swift | 44 ++++++++++++++++- Shared/views/YearView/YearView.swift | 1 + en.lproj/Localizable.strings | 2 + 10 files changed, 134 insertions(+), 10 deletions(-) diff --git a/Shared/FeelsApp.swift b/Shared/FeelsApp.swift index e7890df..28c684d 100644 --- a/Shared/FeelsApp.swift +++ b/Shared/FeelsApp.swift @@ -16,7 +16,8 @@ struct FeelsApp: App { let persistenceController = PersistenceController.shared @StateObject var iapManager = IAPManager() - + @AppStorage(UserDefaultsStore.Keys.firstLaunchDate.rawValue, store: GroupUserDefaults.groupDefaults) private var firstLaunchDate = Date() + init() { BGTaskScheduler.shared.cancelAllTaskRequests() BGTaskScheduler.shared.register(forTaskWithIdentifier: BGTask.updateDBMissingID, using: nil) { (task) in diff --git a/Shared/IAPManager.swift b/Shared/IAPManager.swift index 76eec3e..f6c25f7 100644 --- a/Shared/IAPManager.swift +++ b/Shared/IAPManager.swift @@ -20,7 +20,8 @@ public enum StoreError: Error { class IAPManager: ObservableObject { @Published private(set) var showIAP = false @Published private(set) var subscriptions = [Product: (status: [Product.SubscriptionInfo.Status], renewalInfo: RenewalInfo)?]() - + @AppStorage(UserDefaultsStore.Keys.firstLaunchDate.rawValue, store: GroupUserDefaults.groupDefaults) private var firstLaunchDate = Date() + public var sortedSubscriptionKeysByPriceOptions: [Product] { subscriptions.keys.sorted(by: { $0.price < $1.price @@ -74,23 +75,52 @@ class IAPManager: ObservableObject { var updateListenerTask: Task? = nil + public var daysLeftBeforeIAP: Int { + let daysSinceInstall = Calendar.current.dateComponents([.day, .hour, .minute, .second], from: firstLaunchDate, to: Date()) + if let days = daysSinceInstall.day { + return 30 - days + } + return 0 + } + + public var expireDate: Date? { + Calendar.current.date(byAdding: .day, value: 30, to: firstLaunchDate) ?? nil + } + private let iapIdentifiers = Set([ "com.88oakapps.ifeel.IAP.subscription.weekly", "com.88oakapps.ifeel.IAP.subscription.monthly", "com.88oakapps.ifeel.IAP.subscription.yearly" ]) + var expireOnTimer: Timer? + init() { //Start a transaction listener as close to app launch as possible so you don't miss any transactions. updateListenerTask = listenForTransactions() refresh() + + setUpdateTimer() } deinit { updateListenerTask?.cancel() } + func setUpdateTimer() { + if let expireDate = expireDate { + expireOnTimer = Timer.init(fire: expireDate, interval: 0, repeats: false, block: { _ in + self.decideShowIAP() + }) + RunLoop.main.add(expireOnTimer!, forMode: .common) + } else { + if let expireOnTimer = expireOnTimer { + expireOnTimer.invalidate() + } + } + } + func refresh() { Task { //During store initialization, request products from the App Store. @@ -104,16 +134,25 @@ class IAPManager: ObservableObject { } func decideShowIAP() { - guard !subscriptions.isEmpty else { - return - } - var tmpShowIAP = true + // if we have a sub in the subscriptions dict + // then we dont need to show for (_, value) in self.subscriptions { if value != nil { - tmpShowIAP = false + DispatchQueue.main.async { + self.showIAP = false + } return } } + + var tmpShowIAP = true + // if its passed 30 days with no sub + if daysLeftBeforeIAP <= 0 { + tmpShowIAP = true + } else { + tmpShowIAP = false + } + DispatchQueue.main.async { self.showIAP = tmpShowIAP } diff --git a/Shared/Models/UserDefaultsStore.swift b/Shared/Models/UserDefaultsStore.swift index 3cd03bb..e268761 100644 --- a/Shared/Models/UserDefaultsStore.swift +++ b/Shared/Models/UserDefaultsStore.swift @@ -25,6 +25,7 @@ class UserDefaultsStore { case showNSFW case shape case daysFilter + case firstLaunchDate case contentViewCurrentSelectedHeaderViewBackDays case contentViewHeaderTag @@ -53,6 +54,7 @@ class UserDefaultsStore { } static func moodMoodImagable() -> MoodImagable.Type { + return MoodImages.Emoji.moodImages if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.moodImages.rawValue) as? Int, let model = MoodImages.init(rawValue: data) { return model.moodImages diff --git a/Shared/views/AddMoodHeaderView.swift b/Shared/views/AddMoodHeaderView.swift index a7d492c..b9648f6 100644 --- a/Shared/views/AddMoodHeaderView.swift +++ b/Shared/views/AddMoodHeaderView.swift @@ -40,6 +40,7 @@ struct AddMoodHeaderView: View { }, label: { mood.icon .resizable() + .aspectRatio(contentMode: .fit) .frame(width: CGFloat(50), height: CGFloat(50), alignment: .center) .foregroundColor(moodTint.color(forMood: mood)) }) diff --git a/Shared/views/DayView/DayView.swift b/Shared/views/DayView/DayView.swift index c85260c..c4c6608 100644 --- a/Shared/views/DayView/DayView.swift +++ b/Shared/views/DayView/DayView.swift @@ -88,8 +88,12 @@ struct DayView: View { } } } + .onAppear{ + iapManager.decideShowIAP() + } } + // MARK: Views public var mainView: some View { VStack { diff --git a/Shared/views/MonthView/MonthView.swift b/Shared/views/MonthView/MonthView.swift index 05720a9..657b246 100644 --- a/Shared/views/MonthView/MonthView.swift +++ b/Shared/views/MonthView/MonthView.swift @@ -85,6 +85,7 @@ struct MonthView: View { } .onAppear(perform: { EventLogger.log(event: "show_month_view") + iapManager.decideShowIAP() }) .padding([.top]) .background( diff --git a/Shared/views/PurchaseButtonView.swift b/Shared/views/PurchaseButtonView.swift index fbca2ba..6bf9c37 100644 --- a/Shared/views/PurchaseButtonView.swift +++ b/Shared/views/PurchaseButtonView.swift @@ -17,9 +17,12 @@ struct PurchaseButtonView: View { } @AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system @AppStorage(UserDefaultsStore.Keys.textColor.rawValue, store: GroupUserDefaults.groupDefaults) private var textColor: Color = DefaultTextColor.textColor + @AppStorage(UserDefaultsStore.Keys.firstLaunchDate.rawValue, store: GroupUserDefaults.groupDefaults) private var firstLaunchDate = Date() + var iapManager: IAPManager private let height: Float + private let showCountdownTimer: Bool private let showManageSubClosure: (() -> Void)? @State var isPurchasing: Bool = false @@ -30,10 +33,11 @@ struct PurchaseButtonView: View { } } - public init(height: Float, iapManager: IAPManager, showManageSubClosure: (() -> Void)? = nil) { + public init(height: Float, iapManager: IAPManager, showManageSubClosure: (() -> Void)? = nil, showCountdownTimer: Bool = false) { self.height = height self.showManageSubClosure = showManageSubClosure self.iapManager = iapManager + self.showCountdownTimer = showCountdownTimer } var body: some View { @@ -95,6 +99,7 @@ struct PurchaseButtonView: View { private var buyOptionsView: some View { VStack { ZStack { + theme.currentTheme.secondaryBGColor VStack(spacing: 20) { Text(String(localized: "purchase_view_title")) .font(.body) @@ -102,6 +107,34 @@ struct PurchaseButtonView: View { .foregroundColor(textColor) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + if showCountdownTimer { + if let date = Calendar.current.date(byAdding: .day, value: 30, to: firstLaunchDate) { + HStack { + if iapManager.daysLeftBeforeIAP > 0 { + Text(String(localized: "purchase_view_current_subscription_expires_in")) + .font(.body) + .bold() + .foregroundColor(textColor) + + Text(date, style: .relative) + .font(.body) + .bold() + .foregroundColor(textColor) + } else { + Text(String(localized: "purchase_view_current_subscription_expired_on")) + .font(.body) + .bold() + .foregroundColor(textColor) + + Text(date, style: .date) + .font(.body) + .bold() + .foregroundColor(textColor) + } + } + .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + } + } HStack { ForEach(iapManager.sortedSubscriptionKeysByPriceOptions) { product in Button(action: { diff --git a/Shared/views/SettingsView/SettingsView.swift b/Shared/views/SettingsView/SettingsView.swift index bac9726..0ece0ce 100644 --- a/Shared/views/SettingsView/SettingsView.swift +++ b/Shared/views/SettingsView/SettingsView.swift @@ -28,6 +28,7 @@ struct SettingsView: View { @AppStorage(UserDefaultsStore.Keys.deleteEnable.rawValue, store: GroupUserDefaults.groupDefaults) private var deleteEnabled = true @AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system @AppStorage(UserDefaultsStore.Keys.textColor.rawValue, store: GroupUserDefaults.groupDefaults) private var textColor: Color = DefaultTextColor.textColor + @AppStorage(UserDefaultsStore.Keys.firstLaunchDate.rawValue, store: GroupUserDefaults.groupDefaults) private var firstLaunchDate = Date() var body: some View { ScrollView { @@ -56,6 +57,8 @@ struct SettingsView: View { // fixWeekday exportData importData + editFirstLaunchDatePast + resetLaunchDate Divider() } Spacer() @@ -73,6 +76,7 @@ struct SettingsView: View { } .onAppear(perform: { EventLogger.log(event: "show_settings_view") + iapManager.setUpdateTimer() }) .background( theme.currentTheme.bg @@ -142,12 +146,12 @@ struct SettingsView: View { private var subscriptionInfoView: some View { ZStack { theme.currentTheme.secondaryBGColor - PurchaseButtonView(height: iapManager.currentSubscription != nil ? 300 : 175, iapManager: iapManager, showManageSubClosure: { + PurchaseButtonView(height: iapManager.currentSubscription != nil ? 300 : 200, iapManager: iapManager, showManageSubClosure: { Task { await self.showManageSubscription() } - }) + }, showCountdownTimer: true) } .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) } @@ -157,6 +161,7 @@ struct SettingsView: View { Spacer() Button(action: { EventLogger.log(event: "tap_settings_close") + iapManager.decideShowIAP() dismiss() }, label: { Text(String(localized: "settings_view_exit")) @@ -214,6 +219,41 @@ struct SettingsView: View { .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) } + private var editFirstLaunchDatePast: some View { + ZStack { + theme.currentTheme.secondaryBGColor + Button(action: { + var tmpDate = Date() + tmpDate = Calendar.current.date(byAdding: .day, value: -29, to: tmpDate)! + tmpDate = Calendar.current.date(byAdding: .hour, value: -23, to: tmpDate)! + tmpDate = Calendar.current.date(byAdding: .minute, value: -59, to: tmpDate)! + tmpDate = Calendar.current.date(byAdding: .second, value: -45, to: tmpDate)! + firstLaunchDate = tmpDate + }, label: { + Text("Set first launch date back 29 days, 23 hrs, 45 seconds") + .foregroundColor(textColor) + }) + .padding() + } + .fixedSize(horizontal: false, vertical: true) + .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) + } + + private var resetLaunchDate: some View { + ZStack { + theme.currentTheme.secondaryBGColor + Button(action: { + firstLaunchDate = Date() + }, label: { + Text("Reset luanch date to current date") + .foregroundColor(textColor) + }) + .padding() + } + .fixedSize(horizontal: false, vertical: true) + .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) + } + private var clearDB: some View { ZStack { theme.currentTheme.secondaryBGColor diff --git a/Shared/views/YearView/YearView.swift b/Shared/views/YearView/YearView.swift index a8c0192..beada34 100644 --- a/Shared/views/YearView/YearView.swift +++ b/Shared/views/YearView/YearView.swift @@ -66,6 +66,7 @@ struct YearView: View { } .onAppear(perform: { self.viewModel.filterEntries(startDate: Date(timeIntervalSince1970: 0), endDate: Date()) + iapManager.decideShowIAP() }) .background( theme.currentTheme.bg diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index fbe8e08..23c08a7 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -112,6 +112,8 @@ "purchase_view_other_options" = "Other Options"; "purchase_view_cancel" = "Cancel"; "purchase_view_current_subscription" = "Current Subscription"; +"purchase_view_current_subscription_expires_in" = "Trial expires in:"; +"purchase_view_current_subscription_expired_on" = "Trial expired on:"; /* not used */ "onboarding_title_title" = "What would you like the reminder to say?";