Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v5.5.3 #1249

Merged
merged 6 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 62 additions & 18 deletions BookPlayer.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
1 change: 0 additions & 1 deletion BookPlayer/Coordinators/SettingsCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}

Expand Down
4 changes: 4 additions & 0 deletions BookPlayer/SecondOnboarding/SecondOnboardingResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
48 changes: 48 additions & 0 deletions BookPlayer/SecondOnboarding/Support/SupportFlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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",
Expand Down
82 changes: 82 additions & 0 deletions BookPlayer/SecondOnboarding/Support/Tips/TipJarView.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
93 changes: 93 additions & 0 deletions BookPlayer/SecondOnboarding/Support/Tips/TipJarViewModel.swift
Original file line number Diff line number Diff line change
@@ -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<Routes>?

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)
}
}
38 changes: 38 additions & 0 deletions BookPlayer/SecondOnboarding/Support/Tips/TipOption.swift
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
64 changes: 64 additions & 0 deletions BookPlayer/SecondOnboarding/Support/Tips/TipOptionView.swift
Original file line number Diff line number Diff line change
@@ -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)
)
}
}
1 change: 0 additions & 1 deletion BookPlayer/Settings/Plus Screen/PlusViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import BookPlayerKit
import Combine

final class PlusViewModel {
weak var coordinator: SettingsCoordinator!
let accountService: AccountServiceProtocol

@Published var account: Account?
Expand Down
Loading