2021-01-29 14:36:54 -05:00
//
// PaywallView.swift
// Magic Weather SwiftUI
//
// Created by Cody Kerns on 1/19/21.
//
import SwiftUI
2021-09-03 14:17:37 -03:00
import RevenueCat
2021-01-29 14:36:54 -05:00
/*
An example paywall that uses the current offering.
*/
struct PaywallView : View {
/// - This binding is passed from ContentView: `paywallPresented`
@ Binding var isPresented : Bool
2023-02-20 02:43:09 +00:00
/// - This can change during the lifetime of the PaywallView (e.g. if poor network conditions mean that loading offerings is slow)
/// So set this as an observed object to trigger view updates as necessary
@ ObservedObject var userViewModel = UserViewModel . shared
2021-01-29 14:36:54 -05:00
/// - The current offering saved from PurchasesDelegateHandler
2023-02-20 02:43:09 +00:00
/// if this is nil, then you might want to show a loading indicator or similar
private var offering : Offering ? {
userViewModel . offerings ?. current
}
2023-06-29 14:06:24 -07:00
var body : some View {
PaywallContent ( offering : self . offering , isPresented : self . $ isPresented )
}
}
private struct PaywallContent : View {
var offering : Offering ?
var isPresented : Binding < Bool >
/// - State for displaying an overlay view
@ State private var isPurchasing : Bool = false
2022-01-25 15:38:32 -08:00
@ State private var error : NSError ?
@ State private var displayError : Bool = false
2021-01-29 14:36:54 -05:00
var body : some View {
NavigationView {
ZStack {
/// - The paywall view list displaying each package
List {
2023-06-29 14:06:24 -07:00
Section ( header : Text ( " \n Magic Weather Premium" ), footer : Text ( Self . footerText )) {
2021-01-29 14:36:54 -05:00
ForEach ( offering ?. availablePackages ?? []) { package in
PackageCellView ( package : package ) { ( package ) in
2023-06-29 14:06:24 -07:00
2021-01-29 14:36:54 -05:00
/// - Set 'isPurchasing' state to `true`
isPurchasing = true
2023-06-29 14:06:24 -07:00
2021-01-29 14:36:54 -05:00
/// - Purchase a package
2022-08-17 10:28:24 -07:00
do {
let result = try await Purchases . shared . purchase ( package : package )
2021-01-29 14:36:54 -05:00
/// - Set 'isPurchasing' state to `false`
2022-08-17 10:28:24 -07:00
self . isPurchasing = false
if ! result . userCancelled {
2023-06-29 14:06:24 -07:00
self . isPresented . wrappedValue = false
2021-01-29 14:36:54 -05:00
}
2022-08-17 10:28:24 -07:00
} catch {
self . isPurchasing = false
self . error = error as NSError
self . displayError = true
2021-01-29 14:36:54 -05:00
}
}
}
}
2021-12-15 15:43:18 -08:00
}
. listStyle ( InsetGroupedListStyle ())
2021-01-29 14:36:54 -05:00
. navigationBarTitle ( "✨ Magic Weather Premium" )
. navigationBarTitleDisplayMode (. inline )
. frame ( maxWidth : . infinity , maxHeight : . infinity )
. edgesIgnoringSafeArea (. bottom )
2023-06-29 14:06:24 -07:00
2021-01-29 14:36:54 -05:00
/// - Display an overlay during a purchase
Rectangle ()
. foregroundColor ( Color . black )
. opacity ( isPurchasing ? 0.5 : 0.0 )
. edgesIgnoringSafeArea (. all )
}
}
. navigationViewStyle ( StackNavigationViewStyle ())
. colorScheme (. dark )
2022-01-25 15:38:32 -08:00
. alert (
isPresented : self . $ displayError ,
error : self . error ,
actions : { _ in
Button ( role : . cancel ,
action : { self . displayError = false },
label : { Text ( "OK" ) })
},
message : { Text ( $0 . recoverySuggestion ?? "Please try again" ) }
)
2021-01-29 14:36:54 -05:00
}
2023-06-29 14:06:24 -07:00
private static let footerText = "Don't forget to add your subscription terms and conditions. Read more about this here: https://www.revenuecat.com/blog/schedule-2-section-3-8-b"
2021-01-29 14:36:54 -05:00
}
/* The cell view for each package */
2023-06-29 14:06:24 -07:00
private struct PackageCellView : View {
2022-08-17 10:28:24 -07:00
2021-10-22 15:59:13 -07:00
let package : Package
2022-08-17 10:28:24 -07:00
let onSelection : ( Package ) async -> Void
2021-01-29 14:36:54 -05:00
var body : some View {
2022-08-17 10:28:24 -07:00
Button {
Task {
await self . onSelection ( self . package )
}
} label : {
self . buttonLabel
}
. buttonStyle (. plain )
}
private var buttonLabel : some View {
2021-01-29 14:36:54 -05:00
HStack {
VStack {
HStack {
2021-12-13 13:21:43 -08:00
Text ( package . storeProduct . localizedTitle )
2021-01-29 14:36:54 -05:00
. font (. title3 )
. bold ()
Spacer ()
}
HStack {
2023-04-13 06:43:10 -06:00
Text ( package . terms )
2021-01-29 14:36:54 -05:00
. font (. subheadline )
. foregroundColor (. secondary )
Spacer ()
}
}
. padding ([. top , . bottom ], 8.0 )
Spacer ()
Text ( package . localizedPriceString )
. font (. title3 )
. bold ()
2021-12-15 15:43:18 -08:00
}
. contentShape ( Rectangle ()) // Make the whole cell tappable
2021-01-29 14:36:54 -05:00
}
2022-08-17 10:28:24 -07:00
2021-01-29 14:36:54 -05:00
}
2022-01-25 15:38:32 -08:00
extension NSError : LocalizedError {
public var errorDescription : String ? {
return self . localizedDescription
}
}
2023-06-29 14:06:24 -07:00
struct PaywallView_Previews : PreviewProvider {
private static let product1 = TestStoreProduct (
localizedTitle : "PRO monthly" ,
price : 3.99 ,
localizedPriceString : "$3.99" ,
productIdentifier : "com.revenuecat.product" ,
productType : . autoRenewableSubscription ,
localizedDescription : "Description" ,
subscriptionGroupIdentifier : "group" ,
subscriptionPeriod : . init ( value : 1 , unit : . month ),
introductoryDiscount : . init (
identifier : "intro" ,
price : 0 ,
localizedPriceString : "$0.00" ,
paymentMode : . freeTrial ,
subscriptionPeriod : . init ( value : 1 , unit : . week ),
numberOfPeriods : 1 ,
type : . introductory
),
discounts : []
)
private static let product2 = TestStoreProduct (
localizedTitle : "PRO annual" ,
price : 34.99 ,
localizedPriceString : "$34.99" ,
productIdentifier : "com.revenuecat.product" ,
productType : . autoRenewableSubscription ,
localizedDescription : "Description" ,
subscriptionGroupIdentifier : "group" ,
subscriptionPeriod : . init ( value : 1 , unit : . year ),
introductoryDiscount : nil ,
discounts : []
)
private static let offering = Offering (
identifier : Self . offeringIdentifier ,
serverDescription : "Main offering" ,
metadata : [:],
availablePackages : [
. init (
identifier : "monthly" ,
packageType : . monthly ,
storeProduct : product1 . toStoreProduct (),
offeringIdentifier : Self . offeringIdentifier
),
. init (
identifier : "annual" ,
packageType : . annual ,
storeProduct : product2 . toStoreProduct (),
offeringIdentifier : Self . offeringIdentifier
)
]
)
private static let offeringIdentifier = "offering"
static var previews : some View {
PaywallContent ( offering : Self . offering , isPresented : . constant ( true ))
}
}