Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c341acb
StoreCreationPlanView: hide back button since there is already a clos…
jaclync Nov 15, 2022
dac0d76
Purchase plan success --> in-progress view while waiting for the site…
jaclync Nov 15, 2022
228be38
Update the purchase action to be async and show a spinner in the purc…
jaclync Nov 16, 2022
2362452
Show the correct title in in-progress view while waiting for the site…
jaclync Nov 16, 2022
91beece
Merge branch 'feat/8117-store-name-form' into feat/8108-iap-integration
jaclync Nov 16, 2022
4594e24
Merge branch 'feat/8117-store-name-form' into feat/8108-iap-integration
jaclync Nov 16, 2022
b6021e8
Show a temporary in-progress view while fetching IAP eligibility.
jaclync Nov 16, 2022
6500b6b
Merge branch 'trunk' into feat/8108-iap-integration
jaclync Nov 17, 2022
7d80afc
Only continue from plan purchase step if the result is successful.
jaclync Nov 17, 2022
f60a9e2
Create a mock for `InAppPurchasesForWPComPlansProtocol` to unblock st…
jaclync Nov 17, 2022
da4633b
Merge branch 'feat/8118-store-creation-success' into feat/8108-iap-in…
jaclync Nov 17, 2022
d058fea
Show success screen after the created site becomes a Jetpack site.
jaclync Nov 17, 2022
04910dd
Merge branch 'trunk' into feat/8108-iap-integration
jaclync Nov 18, 2022
0da63f4
Replace debug macro with a feature flag `storeCreationM2WithInAppPurc…
jaclync Nov 18, 2022
1a97dc2
Present the in-progress view while waiting for IAP eligibility check …
jaclync Nov 18, 2022
7482c6d
Use `WooNavigationController` to disable "Back" text in the navigatio…
jaclync Nov 18, 2022
7fdefc4
StoreCreationCoordinator: minor renaming on the store plan variable.
jaclync Nov 18, 2022
65a5fa9
Store creation flow: move navigation title and large title style from…
jaclync Nov 18, 2022
c60a21e
Update comments to improve readability, remove some TODOs.
jaclync Nov 18, 2022
a83d483
Add test cases for `StoreCreationCoordinator` with different IAP elig…
jaclync Nov 18, 2022
1943833
Only use `MockInAppPurchases` in DEBUG builds.
jaclync Nov 18, 2022
bdb17f6
MockInAppPurchases: update comments.
jaclync Nov 21, 2022
662f14a
Merge branch 'trunk' into feat/8108-iap-integration
jaclync Nov 21, 2022
9fdf543
Merge branch 'trunk' into feat/8108-iap-integration
jaclync Nov 22, 2022
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
2 changes: 2 additions & 0 deletions Experiments/Experiments/DefaultFeatureFlagService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
return true
case .storeCreationM2:
return buildConfig == .localDeveloper || buildConfig == .alpha
case .storeCreationM2WithInAppPurchasesEnabled:
return false
case .justInTimeMessagesOnDashboard:
return true
case .systemStatusReportInSupportRequest:
Expand Down
5 changes: 5 additions & 0 deletions Experiments/Experiments/FeatureFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ public enum FeatureFlag: Int {
///
case storeCreationM2

/// Whether in-app purchases are enabled for store creation milestone 2 behind `storeCreationM2` feature flag.
/// If disabled, mock in-app purchases are provided by `MockInAppPurchases`.
///
case storeCreationM2WithInAppPurchasesEnabled

/// Just In Time Messages on Dashboard
///
case justInTimeMessagesOnDashboard
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,15 @@ extension StorePickerViewModel {
return possibleURLs.contains(siteURL)
})
}

/// Returns the site that matches the given site ID.
///
func site(thatMatchesSiteID siteID: Int64) -> Site? {
guard resultsController.numberOfObjects > 0 else {
return nil
}
return resultsController.fetchedObjects.first(where: { $0.siteID == siteID })
}
}

private extension StorePickerViewModel {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import Foundation
import StoreKit

#if DEBUG

/// Only used during store creation development before IAP server side is ready.
struct MockInAppPurchases {
struct Plan: WPComPlanProduct {
let displayName: String
let description: String
let id: String
let displayPrice: String
}

private let fetchProductsDuration: UInt64
private let products: [WPComPlanProduct]
private let userIsEntitledToProduct: Bool

/// - Parameter fetchProductsDuration: How long to wait until the mock plan is returned, in nanoseconds.
/// - Parameter products: WPCOM products to return for purchase.
/// - Parameter userIsEntitledToProduct: Whether the user is entitled to the matched IAP product.
init(fetchProductsDuration: UInt64 = 1_000_000_000,
products: [WPComPlanProduct] = Defaults.products,
userIsEntitledToProduct: Bool = false) {
self.fetchProductsDuration = fetchProductsDuration
self.products = products
self.userIsEntitledToProduct = userIsEntitledToProduct
}
}

extension MockInAppPurchases: InAppPurchasesForWPComPlansProtocol {
func fetchProducts() async throws -> [WPComPlanProduct] {
try await Task.sleep(nanoseconds: fetchProductsDuration)
return products
}

func userIsEntitledToProduct(with id: String) async throws -> Bool {
userIsEntitledToProduct
}

func purchaseProduct(with id: String, for remoteSiteId: Int64) async throws -> InAppPurchaseResult {
// Returns `.pending` in case of success because `StoreKit.Transaction` cannot be easily mocked.
.pending
}

func retryWPComSyncForPurchasedProduct(with id: String) async throws {
// no-op
}

func inAppPurchasesAreSupported() async -> Bool {
true
}
}

private extension MockInAppPurchases {
enum Defaults {
static let products: [WPComPlanProduct] = [
Plan(displayName: "Debug Monthly",
description: "1 Month of Debug Woo",
id: "debug.woocommerce.ecommerce.monthly",
displayPrice: "$69.99")
]
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import SwiftUI

/// Hosting controller that wraps the `StoreCreationPlanView`.
final class StoreCreationPlanHostingController: UIHostingController<StoreCreationPlanView> {
private let onPurchase: () -> Void
private let onPurchase: () async -> Void
private let onClose: () -> Void

init(viewModel: StoreCreationPlanViewModel,
onPurchase: @escaping () -> Void,
onPurchase: @escaping () async -> Void,
onClose: @escaping () -> Void) {
self.onPurchase = onPurchase
self.onClose = onClose
super.init(rootView: StoreCreationPlanView(viewModel: viewModel))

rootView.onPurchase = { [weak self] in
self?.onPurchase()
await self?.onPurchase()
}
}

Expand All @@ -32,6 +32,12 @@ final class StoreCreationPlanHostingController: UIHostingController<StoreCreatio
func configureNavigationBarAppearance() {
addCloseNavigationBarButton(target: self, action: #selector(closeButtonTapped))

// If large title is only disabled with `navigationBarTitleDisplayMode(.inline)` in the SwiftUI view,
// navigating from a screen with large title results in a gap below the navigation bar briefly.
// Also disabling large title in the hosting controller smoothens the transition.
navigationItem.largeTitleDisplayMode = .never
navigationController?.navigationBar.prefersLargeTitles = false

let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundColor = .withColorStudio(.wooCommercePurple, shade: .shade90)
Expand All @@ -49,10 +55,12 @@ final class StoreCreationPlanHostingController: UIHostingController<StoreCreatio
/// Displays the WPCOM eCommerce plan for purchase during the store creation flow.
struct StoreCreationPlanView: View {
/// Set in the hosting controller.
var onPurchase: (() -> Void) = {}
var onPurchase: (() async -> Void) = {}

let viewModel: StoreCreationPlanViewModel

@State private var isPurchaseInProgress: Bool = false

var body: some View {
VStack(alignment: .leading, spacing: 0) {
ScrollView {
Expand Down Expand Up @@ -126,9 +134,13 @@ struct StoreCreationPlanView: View {

// Continue button.
Button(String(format: Localization.continueButtonTitleFormat, viewModel.plan.displayPrice)) {
onPurchase()
Task { @MainActor in
isPurchaseInProgress = true
await onPurchase()
isPurchaseInProgress = false
}
}
.buttonStyle(PrimaryButtonStyle())
.buttonStyle(PrimaryLoadingButtonStyle(isLoading: isPurchaseInProgress))
.padding(Layout.defaultButtonPadding)

// Refund information.
Expand All @@ -144,6 +156,10 @@ struct StoreCreationPlanView: View {
.background(Color(.withColorStudio(.wooCommercePurple, shade: .shade90)))
// This screen is using the dark theme for both light and dark modes.
.environment(\.colorScheme, .dark)
// Disables large title to avoid a large gap below the navigation bar.
.navigationBarTitleDisplayMode(.inline)
// Hides the back button and shows a close button in the hosting controller instead.
.navigationBarBackButtonHidden(true)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ struct StoreNameForm: View {
.buttonStyle(PrimaryButtonStyle())
.disabled(name.isEmpty)
}
// Disables large title to avoid a large gap below the navigation bar.
.navigationBarTitleDisplayMode(.inline)
}
}

Expand Down
Loading