Skip to content

Commit

Permalink
Added support to allow all subs from all groups to be displayed
Browse files Browse the repository at this point in the history
  • Loading branch information
russell-archer committed Sep 19, 2024
1 parent f1ef01f commit 8e32c9b
Show file tree
Hide file tree
Showing 28 changed files with 323 additions and 99 deletions.
27 changes: 11 additions & 16 deletions Sources/SKHelper/Core/SKHelper+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ public extension SKHelper {
/// A collection of all configured `ProductId` that represent auto-renewable subscriptions.
var allSubscriptionProductIds: [ProductId] { products.filter { $0.product.type == .autoRenewable }.map { $0.id }}

/// A collection of all configured `Product` that represent auto-renewable subscriptions in all subscription groups.
var allSubscriptions: [Product] { products.filter { $0.product.type == .autoRenewable }.map { $0.product }}

/// Finds all subscription group names (`groupDisplayName`) in `SKHelper.products`.
/// An empty collection is returned if there are no auto-renewable subscription products.
var allSubscriptionGroupNames: [String] { Array(Set(products.filter { $0.product.type == .autoRenewable }.map { $0.groupName ?? "" }.filter { !$0.isEmpty }))}

/// Finds all subscription group ids (`subscriptionGroupID`) in `SKHelper.products`.
/// An empty collection is returned if there are no auto-renewable subscription products.
var allSubscriptionGroupIds: [String] { Array(Set(products.filter { $0.product.type == .autoRenewable }.map { $0.groupId ?? "" }.filter { !$0.isEmpty }))}

/// All `ProductId` that represent purchased products.
var allPurchasedProductIds: [ProductId] { products.filter { $0.hasEntitlement }.map { $0.id }}

Expand Down Expand Up @@ -169,22 +180,6 @@ public extension SKHelper {
productIds.compactMap { subscription(from: $0) }
}

/// Finds all subscription group names (`groupDisplayName`) in `SKHelper.products`.
///
/// - Returns: Returns all subscription group names (`groupDisplayName`) in `SKHelper.products`. An empty collection is returned if there are no auto-renewable subscription products.
///
func allSubscriptionGroupNames() -> [String] {
Array(Set(products.filter { $0.product.type == .autoRenewable }.map { $0.groupName ?? "" }.filter { !$0.isEmpty }))
}

/// Finds all subscription group ids (`subscriptionGroupID`) in `SKHelper.products`.
///
/// - Returns: Returns all subscription group ids (`subscriptionGroupID`) in `SKHelper.products`. An empty collection is returned if there are no auto-renewable subscription products.
///
func allSubscriptionGroupIds() -> [String] {
Array(Set(products.filter { $0.product.type == .autoRenewable }.map { $0.groupId ?? "" }.filter { !$0.isEmpty }))
}

/// Finds all subscription products in `SKHelper.products` that match the supplied subscription group name.
///
/// - Parameter groupName: The group name (`groupDisplayName`) to search for in `SKHelper.products`.
Expand Down
2 changes: 1 addition & 1 deletion Sources/SKHelper/Core/SKHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ public class SKHelper: Observable {
/// - Parameter productId: The `ProductId` to check.
/// - Returns: Returns true if user is entitled to use the product, false otherwise.
///
public func isPurchased(productId: ProductId) async throws -> Bool {
public func isPurchased(productId: ProductId) async -> Bool {
guard let product = skhelperProduct(for: productId) else { return false }
guard isNonConsumable(productId: productId) || isAutoRenewable(productId: productId) else { return false }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
//
// SKHelperDemoApp.swift
// SKHelperDemo
//

import SwiftUI
import SKHelper

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
//
// ContentView.swift
// SKHelperDemo
//

import SwiftUI
import SKHelper

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
//
// ContentView.swift
// SKHelperDemo
//

import SwiftUI
import SKHelper

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
//
// ContentView.swift
// SKHelperDemo
//

import SwiftUI
import SKHelper

Expand All @@ -12,23 +7,13 @@ struct ContentView: View {
var body: some View {
NavigationStack {
List {
// SKHelperStoreView() lists all available products for this app.
// Products that have been purchased will be grayed-out.
// Tapping on the product's image shows details for the product.
NavigationLink("List all products") {
SKHelperStoreView() { productId in
Group {
Image(productId + ".info").resizable().scaledToFit()
Text("Here is some text about why you might want to buy this product.")
}
.padding()
}
}
:
:

// SKHelperSubscriptionStoreView() lists all subscription products for this app.
// Trials, upgrades and downgrades are handled automatically.
NavigationLink("List all subscriptions") {
SKHelperSubscriptionStoreView(subscriptionGroupName: "vip")
SKHelperSubscriptionStoreView()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
//
// ContentView.swift
// SKHelperDemo
//

import SwiftUI
import SKHelper

Expand All @@ -12,24 +7,13 @@ struct ContentView: View {
var body: some View {
NavigationStack {
List {
// SKHelperStoreView() lists all available products for this app.
// Products that have been purchased will be grayed-out.
// Tapping on the product's image shows details for the product.
NavigationLink("List all products") {
SKHelperStoreView() { productId in
Group {
Image(productId + ".info").resizable().scaledToFit()
Text("Here is some text about why you might want to buy this product.")
}
.padding()
}
}
:
:

// SKHelperSubscriptionStoreView() lists all subscription products for this app.
// Trials, upgrades and downgrades are handled automatically.
NavigationLink("List all subscriptions") {
SKHelperSubscriptionStoreView(
subscriptionGroupName: "vip",
subscriptionHeader: {
VStack {
Image("plant-services").resizable().scaledToFit()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import SwiftUI
import SKHelper

struct ContentView: View {
@Environment(SKHelper.self) private var store

var body: some View {
NavigationStack {
List {
:
:

// Show all purchases the user has made.
NavigationLink("List all purchases") {
SKHelperPurchasesView()
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import SwiftUI
import SKHelper

struct ContentView: View {
@Environment(SKHelper.self) private var store

var body: some View {
NavigationStack {
List {
:
:

// Show the small flowers purchase-related view
NavigationLink("Access Small Flowers") {
SmallFlowersView()
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import SwiftUI
import SKHelper

struct SmallFlowersView: View {
@Environment(SKHelper.self) private var store
@State private var isPurchased = false
private let smallFlowersProductId = "com.rarcher.nonconsumable.flowers.small"

var body: some View {
VStack {
if isPurchased { FullAccess() }
else { NoAccess() }
}
.task { isPurchased = await store.isPurchased(productId: smallFlowersProductId) }
}

func FullAccess() -> some View {
VStack {
Text("🥰 🌹").font(.system(size: 100)).padding()
Text("You've purchased the small flowers - here they are, enjoy!").padding()
Image(smallFlowersProductId).resizable().scaledToFit()
}
.padding()
}

func NoAccess() -> some View {
VStack {
Text("😢").font(.system(size: 100)).padding()
Text("You haven't purchased the small flowers and don't have access.").padding()
ProductNavLink()
Spacer()
}
.padding()
}

func ProductNavLink() -> some View {
NavigationLink("Review Small Flowers Info") {
SKHelperStoreView(productIds: [smallFlowersProductId]) { productId in
Group {
Image(productId + ".info").resizable().scaledToFit()
Text("Here is some text about why you might want to buy this product.")
}
.padding()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import SwiftUI
import SKHelper

struct SmallFlowersView: View {
@Environment(SKHelper.self) private var store
@State private var isPurchased = false
private let smallFlowersProductId = "com.rarcher.nonconsumable.flowers.small"

var body: some View {
VStack {
if isPurchased { FullAccess() }
else { NoAccess() }
}
.task { isPurchased = await store.isPurchased(productId: smallFlowersProductId) }
}

:
:
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
@Tutorial(time: 5) {
@XcodeRequirement(title: "Xcode 16", destination: "https://developer.apple.com/download/applications/")

@Intro(title: "Check a purchase") {
Add code to check if the user has purchased a specific product.
}

@Section(title: "Check a purchase") {
@ContentAndMedia {
In this section add code to check if the user has purchased a specific product. We also review how to grant or deny access to a
purchase-dependent resource.

@Image(source: "quickstart29.png", alt: "Check a purchase.")
}

@Steps {
@Step {
Add the following code at the end of **ContentView.swift** to enable our demo to display a new purchase-dependent
View that we'll create in the next step.

@Code(name: "ContentView.swift", file: "quickstart-contentview6.swift", previousFile: "quickstart-contentview5.swift", reset: false) {}
}

@Step {
Create a new SwiftUI and name it **SmallFlowersView**. Replace the contents of the file with the following code.

@Code(name: "SmallFlowersView.swift", file: "quickstart-flowers1.swift", reset: true) {}
}

@Step {
Notice how check the purchase status of the small flowers product in a `.task { ... }` before the view is displayed.
We then use the purchase status to grant or deny the user access to the purchase-related resource.

@Code(name: "SmallFlowersView.swift", file: "quickstart-flowers2.swift", reset: true) {}
}

@Step {
Build and run the app. For the purposes of this demo we'll assume you haven't yet purchased the small flowers product.
Navigate to the **SmallFlowersView** via the **Access Small Flowers** link.
}

@Step {
You should see that you've not been granted access to the product.

@Image(source: "quickstart29.png", alt: "Check a purchase.")
}

@Step {
Tap the **Review Small Flowers Info** button. You should now see the **SKHelperStoreView** displaying information about the
small flowers product.

@Image(source: "quickstart30.png", alt: "Check a purchase.")

Note that by default **SKHelperStoreView** will display a list of all available products. However, in this case we pass the
`ProductId` of the small flowers product which overrides the default behavior.
}

@Step {
Purchase the small flowers product via the price button displayed on **SKHelperStoreView**. Tap the **Back** button
to navigate back to the **SmallFlowersView**. You should now see that you have been granted access.

@Image(source: "quickstart31.png", alt: "Check a purchase.")
}

This concludes the **Quick Start** tutorial, but we've only scratched the surface of what you can do with **StoreKit** and **SKHelper**.
Explore more resources for learning about `SKHelper`.

- [Documentation landing page](https://russell-archer.github.io/SKHelper/documentation/skhelper)
- [In-depth guide to in-app purchases and **SKHelper**](https://russell-archer.github.io/SKHelper/documentation/skhelper/guide)
- [SKHelperDemo Xcode project](https://github.com/russell-archer/SKHelperDemo)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
- **Products.plist** which provides a collection of product ids
- **Products.storekit** which provides localized product information during Xcode StoreKit testing

We'll also add product images to the demo project.

@Image(source: "quickstart7.png", alt: "How Product.plist and Products.storekit are used by SKHelper.")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
@XcodeRequirement(title: "Xcode 16", destination: "https://developer.apple.com/download/applications/")

@Intro(title: "Display all products") {
In this section we add code to display all products and support user purchases.
Add code to display all products and support user purchases.
}

@Section(title: "Display all products") {
@ContentAndMedia {
In this section we'll add the code required to display all products and support user purchases.

We also see how to customize the display of product information.

@Image(source: "quickstart14.png", alt: "Display products.")
}

Expand Down
Loading

0 comments on commit 8e32c9b

Please sign in to comment.