diff --git a/BookPlayer.xcodeproj/project.pbxproj b/BookPlayer.xcodeproj/project.pbxproj index c8aec272..fae8be82 100644 --- a/BookPlayer.xcodeproj/project.pbxproj +++ b/BookPlayer.xcodeproj/project.pbxproj @@ -395,6 +395,8 @@ 6357F11A2A8BA084007947FC /* BPURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6357F1182A8BA084007947FC /* BPURLSession.swift */; }; 635907A02B161B14002FA524 /* DebugInformationFileActivityItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6359079F2B161B14002FA524 /* DebugInformationFileActivityItemProvider.swift */; }; 636086012C5B3EB400341D78 /* CustomRewindIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 636086002C5B3EB400341D78 /* CustomRewindIntent.swift */; }; + 636ED1E52D51254E00BFF3FD /* TipOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 636ED1E42D51254E00BFF3FD /* TipOption.swift */; }; + 636ED1E82D51331E00BFF3FD /* TipJarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 636ED1E72D51331E00BFF3FD /* TipJarViewModel.swift */; }; 63717EAA2B792E350006291E /* RefreshTaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63717EA92B792E350006291E /* RefreshTaskOperation.swift */; }; 637609192ADCD81400A01D5D /* AppShortcuts.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6376091B2ADCD81400A01D5D /* AppShortcuts.strings */; }; 637DAB0B2AEB3F6D006DC2D1 /* WidgetEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 637DAB092AEB3E0D006DC2D1 /* WidgetEntries.swift */; }; @@ -456,6 +458,8 @@ 63CD85272CE3064600EDBEA8 /* BP+ErrorAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63CD85262CE3064600EDBEA8 /* BP+ErrorAlerts.swift */; }; 63CD85432CE3105300EDBEA8 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63CD85422CE3105300EDBEA8 /* ProfileView.swift */; }; 63E54C322D494E110040355D /* RemoteItemListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E54C312D494E110040355D /* RemoteItemListViewModel.swift */; }; + 63E54C342D50576C0040355D /* TipJarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E54C332D50576C0040355D /* TipJarView.swift */; }; + 63E54C362D50626A0040355D /* TipOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E54C352D50626A0040355D /* TipOptionView.swift */; }; 63E7DCC02D076185005B5E1F /* View+BookPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E7DCBF2D076185005B5E1F /* View+BookPlayer.swift */; }; 63E893922CAFA89000946CD4 /* BPPlayerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E893912CAFA89000946CD4 /* BPPlayerError.swift */; }; 63E893932CAFA89000946CD4 /* BPPlayerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E893912CAFA89000946CD4 /* BPPlayerError.swift */; }; @@ -1197,6 +1201,8 @@ 636086022C5B589900341D78 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = el; path = el.lproj/Localizable.stringsdict; sourceTree = ""; }; 636086032C5B589900341D78 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; }; 636086042C5B589900341D78 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/AppShortcuts.strings; sourceTree = ""; }; + 636ED1E42D51254E00BFF3FD /* TipOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipOption.swift; sourceTree = ""; }; + 636ED1E72D51331E00BFF3FD /* TipJarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipJarViewModel.swift; sourceTree = ""; }; 63717EA92B792E350006291E /* RefreshTaskOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshTaskOperation.swift; sourceTree = ""; }; 6376091A2ADCD81400A01D5D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppShortcuts.strings; sourceTree = ""; }; 6376091C2ADCD82100A01D5D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/AppShortcuts.strings; sourceTree = ""; }; @@ -1257,6 +1263,8 @@ 63CD85262CE3064600EDBEA8 /* BP+ErrorAlerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BP+ErrorAlerts.swift"; sourceTree = ""; }; 63CD85422CE3105300EDBEA8 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; 63E54C312D494E110040355D /* RemoteItemListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteItemListViewModel.swift; sourceTree = ""; }; + 63E54C332D50576C0040355D /* TipJarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipJarView.swift; sourceTree = ""; }; + 63E54C352D50626A0040355D /* TipOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipOptionView.swift; sourceTree = ""; }; 63E7DCBF2D076185005B5E1F /* View+BookPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+BookPlayer.swift"; sourceTree = ""; }; 63E893912CAFA89000946CD4 /* BPPlayerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BPPlayerError.swift; sourceTree = ""; }; 63E893942CAFAB8F00946CD4 /* PlayerLoaderService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerLoaderService.swift; sourceTree = ""; }; @@ -2309,6 +2317,7 @@ 634BA58E2C14FB010015314D /* Support */ = { isa = PBXGroup; children = ( + 636ED1E32D51254400BFF3FD /* Tips */, 63B760E72C31FFC600AA98C7 /* SupportFlowCoordinator.swift */, 63B760FB2C33B77F00AA98C7 /* SupportProfileView.swift */, ); @@ -2347,6 +2356,17 @@ path = AppIntents; sourceTree = ""; }; + 636ED1E32D51254400BFF3FD /* Tips */ = { + isa = PBXGroup; + children = ( + 63E54C332D50576C0040355D /* TipJarView.swift */, + 636ED1E72D51331E00BFF3FD /* TipJarViewModel.swift */, + 63E54C352D50626A0040355D /* TipOptionView.swift */, + 636ED1E42D51254E00BFF3FD /* TipOption.swift */, + ); + path = Tips; + sourceTree = ""; + }; 63833DF82AAC134600496246 /* PerformanceTests */ = { isa = PBXGroup; children = ( @@ -3755,6 +3775,7 @@ 631C75CC2AB92FA60013E7E5 /* BPModalPresentationFlow.swift in Sources */, 9FF710AC2A212221006490E0 /* QueuedSyncTasksViewController.swift in Sources */, 9FEC87B227FA9F1C006C71D5 /* LoginViewModel.swift in Sources */, + 636ED1E52D51254E00BFF3FD /* TipOption.swift in Sources */, 63C1A8B22B09166F00C4B418 /* WidgetEntries.swift in Sources */, 9F17D5CD29159297004F4929 /* SearchListViewModel.swift in Sources */, 41AD3DA3221C7DCE00DC41E1 /* IconsViewController.swift in Sources */, @@ -3897,6 +3918,7 @@ 9F2681B628898A7300359BD3 /* LoginDisclaimerView.swift in Sources */, 9FD8D95829DC53750074C2D8 /* CoreServices.swift in Sources */, 9F4691F22800D58A00A8F0E8 /* CompleteAccountCoordinator.swift in Sources */, + 63E54C342D50576C0040355D /* TipJarView.swift in Sources */, 9F00A5F6294F793F005EA316 /* ItemDetailsView.swift in Sources */, 4158386E26EAF76700F4A12B /* FolderListCoordinator.swift in Sources */, 9F64C6262793D5B100B2493C /* PlayerControlsViewController.swift in Sources */, @@ -3914,6 +3936,7 @@ 41A1B12D226FC7E500EA0400 /* ImportManager.swift in Sources */, 9F22DE46288E517600056FCD /* AccountProBenefitsView.swift in Sources */, 8A9D0D242CCED53C007A924D /* JellyfinLibraryViewModel.swift in Sources */, + 636ED1E82D51331E00BFF3FD /* TipJarViewModel.swift in Sources */, 9F2E00DE2A5C2B810001FE20 /* StorageCloudDeletedViewModel.swift in Sources */, 4151A6A626E3A40600E49DBE /* SpeedService.swift in Sources */, 6356F9B92AC7CDDE00B7A027 /* EndChapterSleepTimerIntent.swift in Sources */, @@ -3942,6 +3965,7 @@ 41188D2A26ED4D8E0017124E /* ItemListViewModel.swift in Sources */, C398559C20C492FF00BE9EC0 /* AddButton.swift in Sources */, 8A9D0D222CCD00D2007A924D /* JellyfinCoordinator.swift in Sources */, + 63E54C362D50626A0040355D /* TipOptionView.swift in Sources */, 8ADD46262CF67592002E9C50 /* JellyfinAudiobookDetailsViewModel.swift in Sources */, 9F89D89C27EDFCA400F73947 /* SceneDelegate.swift in Sources */, 9F3D0CE928C2BFC600E9E8A3 /* ButtonFreeCoordinator.swift in Sources */, diff --git a/BookPlayer/Coordinators/SettingsCoordinator.swift b/BookPlayer/Coordinators/SettingsCoordinator.swift index d6f9803a..7d2c706d 100644 --- a/BookPlayer/Coordinators/SettingsCoordinator.swift +++ b/BookPlayer/Coordinators/SettingsCoordinator.swift @@ -192,7 +192,6 @@ class SettingsCoordinator: Coordinator, AlertPresenter { func showTipJar() { let viewModel = PlusViewModel(accountService: self.accountService) - viewModel.coordinator = self let vc = PlusViewController.instantiate(from: .Settings) vc.viewModel = viewModel vc.navigationItem.largeTitleDisplayMode = .never diff --git a/BookPlayer/SecondOnboarding/SecondOnboardingCoordinator.swift b/BookPlayer/SecondOnboarding/SecondOnboardingCoordinator.swift index cefad198..c6b6f977 100644 --- a/BookPlayer/SecondOnboarding/SecondOnboardingCoordinator.swift +++ b/BookPlayer/SecondOnboarding/SecondOnboardingCoordinator.swift @@ -80,7 +80,9 @@ class SecondOnboardingCoordinator: Coordinator { ), sliderOptions: action.sliderOptions, button: action.button, - dismiss: action.dismiss + dismiss: action.dismiss, + tipJar: action.tipJar, + tipJarDisclaimer: action.tipJarDisclaimer ) } diff --git a/BookPlayer/SecondOnboarding/SecondOnboardingResponse.swift b/BookPlayer/SecondOnboarding/SecondOnboardingResponse.swift index 918beee2..a909ef7d 100644 --- a/BookPlayer/SecondOnboarding/SecondOnboardingResponse.swift +++ b/BookPlayer/SecondOnboarding/SecondOnboardingResponse.swift @@ -33,9 +33,13 @@ struct StoryActionResponseModel: Codable { var sliderOptions: SliderOptions? var button: String var dismiss: String? + var tipJar: String? + var tipJarDisclaimer: String? enum CodingKeys: String, CodingKey { case options, button, dismiss + case tipJar = "tip_jar" + case tipJarDisclaimer = "tip_jar_disclaimer" case defaultOption = "default_option" case sliderOptions = "slider_options" } diff --git a/BookPlayer/SecondOnboarding/Support/SupportFlowCoordinator.swift b/BookPlayer/SecondOnboarding/Support/SupportFlowCoordinator.swift index 3fc60b4d..235034aa 100644 --- a/BookPlayer/SecondOnboarding/Support/SupportFlowCoordinator.swift +++ b/BookPlayer/SecondOnboarding/Support/SupportFlowCoordinator.swift @@ -45,6 +45,10 @@ class SupportFlowCoordinator: Coordinator, AlertPresenter { viewModel.onTransition = { route in switch route { + case .tipJar(let disclaimer): + Task { @MainActor in + self.showTipJar(disclaimer: disclaimer) + } case .dismiss: self.dismiss() case .showAlert(let model): @@ -99,6 +103,50 @@ class SupportFlowCoordinator: Coordinator, AlertPresenter { } } + @MainActor + func showTipJar(disclaimer: String?) { + let viewModel = TipJarViewModel( + disclaimer: disclaimer, + accountService: accountService + ) + + viewModel.onTransition = { route in + switch route { + case .showLoader(let flag): + if flag { + self.showLoader() + } else { + self.stopLoader() + } + case .showAlert(let model): + self.presentedController?.getTopVisibleViewController()?.showAlert(model) + case .success(let message): + self.showCongratsTip(message) + case .dismiss: + self.flow.finishPresentation(animated: true) + } + } + + let vc = UIHostingController(rootView: TipJarView(viewModel: viewModel)) + vc.modalPresentationStyle = .overFullScreen + + presentedController?.present(vc, animated: true) + } + + func showCongratsTip(_ message: String) { + eventsService.sendEvent( + "second_onboarding_tip", + payload: [ + "rc_id": anonymousId, + "onboarding_id": onboardingId, + ] + ) + presentedController?.getTopVisibleViewController()?.view.startConfetti() + presentedController?.getTopVisibleViewController()?.showAlert(message, message: nil) { [weak self] in + self?.flow.finishPresentation(animated: true) + } + } + func showCongrats() { eventsService.sendEvent( "second_onboarding_subscription", diff --git a/BookPlayer/SecondOnboarding/Support/Tips/TipJarView.swift b/BookPlayer/SecondOnboarding/Support/Tips/TipJarView.swift new file mode 100644 index 00000000..6d030d98 --- /dev/null +++ b/BookPlayer/SecondOnboarding/Support/Tips/TipJarView.swift @@ -0,0 +1,82 @@ +// +// TipJarView.swift +// BookPlayer +// +// Created by Gianni Carlo on 2/2/25. +// Copyright © 2025 BookPlayer LLC. All rights reserved. +// + +import BookPlayerKit +import SwiftUI + +struct TipJarView: View { + @ObservedObject var viewModel: TipJarViewModel + @StateObject var themeViewModel = ThemeViewModel() + @State var selected: TipOption = TipOption.excellent + @State var error: Error? + + var body: some View { + NavigationView { + VStack { + if let disclaimer = viewModel.disclaimer { + Text(disclaimer) + .foregroundColor(themeViewModel.primaryColor) + .font(Font(Fonts.titleRegular)) + .padding() + } + + HStack(spacing: Spacing.S1) { + Spacer() + ForEach(TipOption.allCases) { option in + TipOptionView( + title: .constant(option.title), + price: .constant(option.price), + isSelected: .constant(selected == option) + ) + .onTapGesture { + selected = option + } + } + Spacer() + } + Button(action: { + Task { @MainActor in + await viewModel.donate(selected) + } + }) { + Text("Donate") + .contentShape(Rectangle()) + .font(Font(Fonts.headline)) + .frame(height: 45) + .frame(maxWidth: .infinity) + .foregroundColor(.white) + .background(Color(UIColor(hex: "687AB7"))) + .cornerRadius(6) + .padding(.top, Spacing.S1) + } + Spacer() + } + .padding(.horizontal, Spacing.M) + .background(themeViewModel.systemGroupedBackgroundColor) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button { + viewModel.dismiss() + } label: { + Image(systemName: "xmark") + } + } + + ToolbarItem(placement: .confirmationAction) { + Button("Restore") { + Task { @MainActor in + await viewModel.restorePurchases() + } + } + } + } + .navigationTitle("Tip Jar") + .navigationBarTitleDisplayMode(.inline) + } + } +} diff --git a/BookPlayer/SecondOnboarding/Support/Tips/TipJarViewModel.swift b/BookPlayer/SecondOnboarding/Support/Tips/TipJarViewModel.swift new file mode 100644 index 00000000..769d427e --- /dev/null +++ b/BookPlayer/SecondOnboarding/Support/Tips/TipJarViewModel.swift @@ -0,0 +1,93 @@ +// +// TipJarViewModel.swift +// BookPlayer +// +// Created by Gianni Carlo on 3/2/25. +// Copyright © 2025 BookPlayer LLC. All rights reserved. +// + +import BookPlayerKit +import Foundation +import RevenueCat + +@MainActor +public final class TipJarViewModel: ObservableObject { + enum Routes { + case showLoader(Bool) + case showAlert(BPAlertContent) + case success(message: String) + case dismiss + } + + let disclaimer: String? + let accountService: AccountServiceProtocol + /// Callback to handle actions on this screen + var onTransition: BPTransition? + + init( + disclaimer: String?, + accountService: AccountServiceProtocol + ) { + self.disclaimer = disclaimer + self.accountService = accountService + } + + func donate(_ tip: TipOption) async { + onTransition?(.showLoader(true)) + do { + let product = await Purchases.shared.products([tip.rawValue]).first! + let result = try await Purchases.shared.purchase(product: product) + onTransition?(.showLoader(false)) + if !result.userCancelled { + accountService.updateAccount( + id: nil, + email: nil, + donationMade: true, + hasSubscription: nil + ) + onTransition?(.success(message: "thanks_amazing_title".localized)) + } + } catch { + onTransition?(.showLoader(false)) + onTransition?( + .showAlert( + BPAlertContent.errorAlert(message: error.localizedDescription) + ) + ) + } + } + + func restorePurchases() async { + onTransition?(.showLoader(true)) + do { + let customerInfo = try await Purchases.shared.restorePurchases() + onTransition?(.showLoader(false)) + if customerInfo.nonSubscriptions.isEmpty { + onTransition?( + .showAlert( + BPAlertContent.errorAlert(message: "tip_missing_title".localized) + ) + ) + } else { + accountService.updateAccount( + id: nil, + email: nil, + donationMade: true, + hasSubscription: nil + ) + onTransition?(.success(message: "purchases_restored_title".localized)) + } + } catch { + onTransition?(.showLoader(false)) + onTransition?( + .showAlert( + BPAlertContent.errorAlert(message: error.localizedDescription) + ) + ) + } + } + + func dismiss() { + onTransition?(.dismiss) + } +} diff --git a/BookPlayer/SecondOnboarding/Support/Tips/TipOption.swift b/BookPlayer/SecondOnboarding/Support/Tips/TipOption.swift new file mode 100644 index 00000000..9968aca6 --- /dev/null +++ b/BookPlayer/SecondOnboarding/Support/Tips/TipOption.swift @@ -0,0 +1,38 @@ +// +// TipOption.swift +// BookPlayer +// +// Created by Gianni Carlo on 3/2/25. +// Copyright © 2025 BookPlayer LLC. All rights reserved. +// + +import Foundation + +enum TipOption: String, Identifiable, CaseIterable { + public var id: Self { self } + case kind = "com.tortugapower.audiobookplayer.tip.kind" + case excellent = "com.tortugapower.audiobookplayer.tip.excellent" + case incredible = "com.tortugapower.audiobookplayer.tip.incredible" + + var title: String { + switch self { + case .kind: + return "Kind\ntip of" + case .excellent: + return "Excellent\ntip of" + case .incredible: + return "Incredible\ntip of" + } + } + + var price: String { + switch self { + case .kind: + return "$2.99" + case .excellent: + return "$4.99" + case .incredible: + return "$9.99" + } + } +} diff --git a/BookPlayer/SecondOnboarding/Support/Tips/TipOptionView.swift b/BookPlayer/SecondOnboarding/Support/Tips/TipOptionView.swift new file mode 100644 index 00000000..66c21df0 --- /dev/null +++ b/BookPlayer/SecondOnboarding/Support/Tips/TipOptionView.swift @@ -0,0 +1,64 @@ +// +// TipOptionView.swift +// BookPlayer +// +// Created by Gianni Carlo on 2/2/25. +// Copyright © 2025 BookPlayer LLC. All rights reserved. +// + +import BookPlayerKit +import SwiftUI + +struct TipOptionView: View { + @Binding var title: String + @Binding var price: String + @Binding var isSelected: Bool + + var imageLength: CGFloat = 16 + var imageName: String { + isSelected ? "checkmark.circle" : "circle" + } + var foregroundColor: Color { + isSelected + ? Color(UIColor(hex: "3488D1")) + : Color(UIColor(hex: "334046")) + } + + var body: some View { + VStack(spacing: 0) { + HStack { + Spacer() + Image(systemName: imageName) + .resizable() + .frame(width: imageLength, height: imageLength) + .foregroundColor(foregroundColor) + .padding([.trailing, .top], Spacing.S3) + } + Text(title) + .font(Font(Fonts.titleRegular)) + .foregroundColor(foregroundColor.opacity(0.7)) + .multilineTextAlignment(.center) + Text(price) + .font(Font(Fonts.titleLarge)) + .foregroundColor(foregroundColor) + } + .padding([.bottom]) + .background(Color(UIColor(hex: "F8F8F8"))) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .contentShape(Rectangle()) + .accessibilityElement(children: .combine) + .accessibilityAddTraits(.isButton) + .frame(maxWidth: 88) + + } +} + +#Preview { + ZStack { + TipOptionView( + title: .constant("Kind tip\nof"), + price: .constant("1.99"), + isSelected: .constant(true) + ) + } +} diff --git a/BookPlayer/Settings/Plus Screen/PlusViewModel.swift b/BookPlayer/Settings/Plus Screen/PlusViewModel.swift index 895f90e4..86296e7e 100644 --- a/BookPlayer/Settings/Plus Screen/PlusViewModel.swift +++ b/BookPlayer/Settings/Plus Screen/PlusViewModel.swift @@ -10,7 +10,6 @@ import BookPlayerKit import Combine final class PlusViewModel { - weak var coordinator: SettingsCoordinator! let accountService: AccountServiceProtocol @Published var account: Account? diff --git a/BookPlayer/Utils/Views/StoryViewer/StoryActionView.swift b/BookPlayer/Utils/Views/StoryViewer/StoryActionView.swift index acfc402e..899bc20a 100644 --- a/BookPlayer/Utils/Views/StoryViewer/StoryActionView.swift +++ b/BookPlayer/Utils/Views/StoryViewer/StoryActionView.swift @@ -29,17 +29,20 @@ struct StoryActionView: View { } var onSubscription: (PricingModel) -> Void var onDismiss: () -> Void + var onTipJar: (String?) -> Void init( action: Binding, onSubscription: @escaping (PricingModel) -> Void, - onDismiss: @escaping () -> Void + onDismiss: @escaping () -> Void, + onTipJar: @escaping (String?) -> Void ) { self._action = action self.selected = action.wrappedValue.defaultOption self.sliderValue = action.wrappedValue.defaultOption.price self.onSubscription = onSubscription self.onDismiss = onDismiss + self.onTipJar = onTipJar } var body: some View { @@ -147,6 +150,20 @@ struct StoryActionView: View { ) .padding([.top], Spacing.S5) } + if let tipJar = action.tipJar { + Button( + action: { + onTipJar(action.tipJarDisclaimer) + }, + label: { + Text(tipJar) + .underline() + .font(Font(Fonts.body)) + .foregroundColor(.white) + } + ) + .padding([.top], Spacing.S5) + } } } } @@ -168,7 +185,8 @@ struct StoryActionView: View { ) ), onSubscription: { option in print(option.title) }, - onDismiss: {} + onDismiss: {}, + onTipJar: { _ in } ) } } diff --git a/BookPlayer/Utils/Views/StoryViewer/StoryView.swift b/BookPlayer/Utils/Views/StoryViewer/StoryView.swift index 52741593..b345736b 100644 --- a/BookPlayer/Utils/Views/StoryViewer/StoryView.swift +++ b/BookPlayer/Utils/Views/StoryViewer/StoryView.swift @@ -17,6 +17,7 @@ struct StoryView: View { var onResume: () -> Void var onSubscription: (PricingModel) -> Void var onDismiss: () -> Void + var onTipJar: (String?) -> Void var body: some View { ZStack { @@ -108,7 +109,8 @@ struct StoryView: View { StoryActionView( action: action, onSubscription: onSubscription, - onDismiss: onDismiss + onDismiss: onDismiss, + onTipJar: onTipJar ) .padding([.leading, .trailing]) .padding([.top], Spacing.L1) @@ -160,6 +162,8 @@ struct StoryView: View { print(option.title) }, onDismiss: { print("Dismiss") + }, onTipJar: { _ in + print("Tip Jar") }) .foregroundColor(.white) } diff --git a/BookPlayer/Utils/Views/StoryViewer/StoryViewModel.swift b/BookPlayer/Utils/Views/StoryViewer/StoryViewModel.swift index 886adc03..c205341e 100644 --- a/BookPlayer/Utils/Views/StoryViewer/StoryViewModel.swift +++ b/BookPlayer/Utils/Views/StoryViewer/StoryViewModel.swift @@ -15,9 +15,13 @@ struct StoryActionType: Codable { var sliderOptions: SliderOptions? var button: String var dismiss: String? - + var tipJar: String? + var tipJarDisclaimer: String? + enum CodingKeys: String, CodingKey { case options, button, dismiss + case tipJar = "tip_jar" + case tipJarDisclaimer = "tip_jar_disclaimer" case defaultOption = "default_option" case sliderOptions = "slider_options" } diff --git a/BookPlayer/Utils/Views/StoryViewer/StoryViewer.swift b/BookPlayer/Utils/Views/StoryViewer/StoryViewer.swift index f09aa474..5cb521af 100644 --- a/BookPlayer/Utils/Views/StoryViewer/StoryViewer.swift +++ b/BookPlayer/Utils/Views/StoryViewer/StoryViewer.swift @@ -17,12 +17,14 @@ struct StoryViewer: View { ZStack(alignment: .top) { StoryBackgroundView() .accessibilityHidden(true) - StoryProgress( - storiesCount: .constant(viewModel.storiesCount), - progress: $viewModel.progress - ) - .padding([.trailing, .leading, .bottom]) - .accessibilityHidden(true) + if viewModel.storiesCount > 1 { + StoryProgress( + storiesCount: .constant(viewModel.storiesCount), + progress: $viewModel.progress + ) + .padding([.trailing, .leading, .bottom]) + .accessibilityHidden(true) + } StoryView( model: $viewModel.currentModel, onPrevious: viewModel.previous, @@ -30,7 +32,8 @@ struct StoryViewer: View { onPause: viewModel.pause, onResume: viewModel.start, onSubscription: viewModel.handleSubscription(option:), - onDismiss: viewModel.handleDismiss + onDismiss: viewModel.handleDismiss, + onTipJar: viewModel.handleTipJar ) .foregroundColor(Color.white) .padding() diff --git a/BookPlayer/Utils/Views/StoryViewer/StoryViewerViewModel.swift b/BookPlayer/Utils/Views/StoryViewer/StoryViewerViewModel.swift index 379e3fb4..7c23fe88 100644 --- a/BookPlayer/Utils/Views/StoryViewer/StoryViewerViewModel.swift +++ b/BookPlayer/Utils/Views/StoryViewer/StoryViewerViewModel.swift @@ -14,6 +14,7 @@ class StoryViewerViewModel: ObservableObject { enum Routes { case showLoader(Bool) case showAlert(BPAlertContent) + case tipJar(String?) case success case dismiss } @@ -99,4 +100,8 @@ class StoryViewerViewModel: ObservableObject { func handleDismiss() { onTransition?(.dismiss) } + + func handleTipJar(disclaimer: String?) { + onTransition?(.tipJar(disclaimer)) + } }