Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ extension CouponReport {
}

extension Customer {
func copy(
public func copy(
customerID: CopiableProp<Int64> = .copy,
email: CopiableProp<String> = .copy,
firstName: NullableCopiableProp<String> = .copy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ extension GeneralAppSettings {
isViewAddOnsSwitchEnabled: CopiableProp<Bool> = .copy,
isProductSKUInputScannerSwitchEnabled: CopiableProp<Bool> = .copy,
isCouponManagementSwitchEnabled: CopiableProp<Bool> = .copy,
isInAppPurchasesSwitchEnabled: CopiableProp<Bool> = .copy,
knownCardReaders: CopiableProp<[String]> = .copy,
lastEligibilityErrorInfo: NullableCopiableProp<EligibilityErrorInfo> = .copy,
lastJetpackBenefitsBannerDismissedTime: NullableCopiableProp<Date> = .copy,
Expand All @@ -36,6 +37,7 @@ extension GeneralAppSettings {
let isViewAddOnsSwitchEnabled = isViewAddOnsSwitchEnabled ?? self.isViewAddOnsSwitchEnabled
let isProductSKUInputScannerSwitchEnabled = isProductSKUInputScannerSwitchEnabled ?? self.isProductSKUInputScannerSwitchEnabled
let isCouponManagementSwitchEnabled = isCouponManagementSwitchEnabled ?? self.isCouponManagementSwitchEnabled
let isInAppPurchasesSwitchEnabled = isInAppPurchasesSwitchEnabled ?? self.isInAppPurchasesSwitchEnabled
let knownCardReaders = knownCardReaders ?? self.knownCardReaders
let lastEligibilityErrorInfo = lastEligibilityErrorInfo ?? self.lastEligibilityErrorInfo
let lastJetpackBenefitsBannerDismissedTime = lastJetpackBenefitsBannerDismissedTime ?? self.lastJetpackBenefitsBannerDismissedTime
Expand All @@ -47,6 +49,7 @@ extension GeneralAppSettings {
isViewAddOnsSwitchEnabled: isViewAddOnsSwitchEnabled,
isProductSKUInputScannerSwitchEnabled: isProductSKUInputScannerSwitchEnabled,
isCouponManagementSwitchEnabled: isCouponManagementSwitchEnabled,
isInAppPurchasesSwitchEnabled: isInAppPurchasesSwitchEnabled,
knownCardReaders: knownCardReaders,
lastEligibilityErrorInfo: lastEligibilityErrorInfo,
lastJetpackBenefitsBannerDismissedTime: lastJetpackBenefitsBannerDismissedTime,
Expand Down
10 changes: 10 additions & 0 deletions Storage/Storage/Model/GeneralAppSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ public struct GeneralAppSettings: Codable, Equatable, GeneratedCopiable {
///
public var isCouponManagementSwitchEnabled: Bool

/// The state for the In-app Purchases feature switch.
///
public var isInAppPurchasesSwitchEnabled: Bool

/// A list (possibly empty) of known card reader IDs - i.e. IDs of card readers that should be reconnected to automatically
/// e.g. ["CHB204909005931"]
///
Expand All @@ -53,6 +57,7 @@ public struct GeneralAppSettings: Codable, Equatable, GeneratedCopiable {
isViewAddOnsSwitchEnabled: Bool,
isProductSKUInputScannerSwitchEnabled: Bool,
isCouponManagementSwitchEnabled: Bool,
isInAppPurchasesSwitchEnabled: Bool,
knownCardReaders: [String],
lastEligibilityErrorInfo: EligibilityErrorInfo? = nil,
lastJetpackBenefitsBannerDismissedTime: Date? = nil,
Expand All @@ -66,6 +71,7 @@ public struct GeneralAppSettings: Codable, Equatable, GeneratedCopiable {
self.lastEligibilityErrorInfo = lastEligibilityErrorInfo
self.lastJetpackBenefitsBannerDismissedTime = lastJetpackBenefitsBannerDismissedTime
self.featureAnnouncementCampaignSettings = featureAnnouncementCampaignSettings
self.isInAppPurchasesSwitchEnabled = isInAppPurchasesSwitchEnabled
}

public static var `default`: Self {
Expand All @@ -74,6 +80,7 @@ public struct GeneralAppSettings: Codable, Equatable, GeneratedCopiable {
isViewAddOnsSwitchEnabled: false,
isProductSKUInputScannerSwitchEnabled: false,
isCouponManagementSwitchEnabled: false,
isInAppPurchasesSwitchEnabled: false,
knownCardReaders: [],
lastEligibilityErrorInfo: nil,
featureAnnouncementCampaignSettings: [:])
Expand Down Expand Up @@ -102,6 +109,7 @@ public struct GeneralAppSettings: Codable, Equatable, GeneratedCopiable {
isViewAddOnsSwitchEnabled: isViewAddOnsSwitchEnabled,
isProductSKUInputScannerSwitchEnabled: isProductSKUInputScannerSwitchEnabled,
isCouponManagementSwitchEnabled: isCouponManagementSwitchEnabled,
isInAppPurchasesSwitchEnabled: isInAppPurchasesSwitchEnabled,
knownCardReaders: knownCardReaders,
lastEligibilityErrorInfo: lastEligibilityErrorInfo,
featureAnnouncementCampaignSettings: featureAnnouncementCampaignSettings
Expand All @@ -121,6 +129,7 @@ public struct GeneralAppSettings: Codable, Equatable, GeneratedCopiable {
isViewAddOnsSwitchEnabled: isViewAddOnsSwitchEnabled,
isProductSKUInputScannerSwitchEnabled: isProductSKUInputScannerSwitchEnabled,
isCouponManagementSwitchEnabled: isCouponManagementSwitchEnabled,
isInAppPurchasesSwitchEnabled: isInAppPurchasesSwitchEnabled,
knownCardReaders: knownCardReaders,
lastEligibilityErrorInfo: lastEligibilityErrorInfo,
featureAnnouncementCampaignSettings: updatedSettings
Expand All @@ -140,6 +149,7 @@ extension GeneralAppSettings {
self.isViewAddOnsSwitchEnabled = try container.decodeIfPresent(Bool.self, forKey: .isViewAddOnsSwitchEnabled) ?? false
self.isProductSKUInputScannerSwitchEnabled = try container.decodeIfPresent(Bool.self, forKey: .isProductSKUInputScannerSwitchEnabled) ?? false
self.isCouponManagementSwitchEnabled = try container.decodeIfPresent(Bool.self, forKey: .isCouponManagementSwitchEnabled) ?? false
self.isInAppPurchasesSwitchEnabled = try container.decodeIfPresent(Bool.self, forKey: .isInAppPurchasesSwitchEnabled) ?? false
self.knownCardReaders = try container.decodeIfPresent([String].self, forKey: .knownCardReaders) ?? []
self.lastEligibilityErrorInfo = try container.decodeIfPresent(EligibilityErrorInfo.self, forKey: .lastEligibilityErrorInfo)
self.lastJetpackBenefitsBannerDismissedTime = try container.decodeIfPresent(Date.self, forKey: .lastJetpackBenefitsBannerDismissedTime)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ final class GeneralAppSettingsTests: XCTestCase {
isViewAddOnsSwitchEnabled: true,
isProductSKUInputScannerSwitchEnabled: true,
isCouponManagementSwitchEnabled: true,
isInAppPurchasesSwitchEnabled: false,
knownCardReaders: readers,
lastEligibilityErrorInfo: eligibilityInfo,
lastJetpackBenefitsBannerDismissedTime: jetpackBannerDismissedDate,
Expand Down Expand Up @@ -101,6 +102,7 @@ private extension GeneralAppSettingsTests {
isViewAddOnsSwitchEnabled: Bool = false,
isProductSKUInputScannerSwitchEnabled: Bool = false,
isCouponManagementSwitchEnabled: Bool = false,
isInAppPurchasesSwitchEnabled: Bool = false,
knownCardReaders: [String] = [],
lastEligibilityErrorInfo: EligibilityErrorInfo? = nil,
lastJetpackBenefitsBannerDismissedTime: Date? = nil,
Expand All @@ -111,6 +113,7 @@ private extension GeneralAppSettingsTests {
isViewAddOnsSwitchEnabled: isViewAddOnsSwitchEnabled,
isProductSKUInputScannerSwitchEnabled: isProductSKUInputScannerSwitchEnabled,
isCouponManagementSwitchEnabled: isCouponManagementSwitchEnabled,
isInAppPurchasesSwitchEnabled: isInAppPurchasesSwitchEnabled,
knownCardReaders: knownCardReaders,
lastEligibilityErrorInfo: lastEligibilityErrorInfo,
lastJetpackBenefitsBannerDismissedTime: lastJetpackBenefitsBannerDismissedTime,
Expand Down
32 changes: 31 additions & 1 deletion WooCommerce/Classes/Model/BetaFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ enum BetaFeature: String, CaseIterable {
case viewAddOns
case productSKUScanner
case couponManagement
case inAppPurchases
}

extension BetaFeature {
Expand All @@ -15,6 +16,8 @@ extension BetaFeature {
return Localization.productSKUScannerTitle
case .couponManagement:
return Localization.couponManagementTitle
case .inAppPurchases:
return Localization.inAppPurchasesManagementTitle
}
}

Expand All @@ -26,6 +29,8 @@ extension BetaFeature {
return Localization.productSKUScannerDescription
case .couponManagement:
return Localization.couponManagementDescription
case .inAppPurchases:
return Localization.inAppPurchasesManagementDescription
}
}

Expand All @@ -37,6 +42,8 @@ extension BetaFeature {
return \.isProductSKUInputScannerSwitchEnabled
case .couponManagement:
return \.isCouponManagementSwitchEnabled
case .inAppPurchases:
return \.isInAppPurchasesSwitchEnabled
}
}

Expand All @@ -51,6 +58,19 @@ extension BetaFeature {
}
}

var isAvailable: Bool {
switch self {
case .inAppPurchases:
return ServiceLocator.featureFlagService.isFeatureFlagEnabled(.inAppPurchases)
default:
return true
}
}

static var availableFeatures: [Self] {
allCases.filter(\.isAvailable)
}

func analyticsProperties(toggleState enabled: Bool) -> [String: WooAnalyticsEventPropertyType] {
var properties = ["state": enabled ? "on" : "off"]
if analyticsStat == .settingsBetaFeatureToggled {
Expand All @@ -62,7 +82,10 @@ extension BetaFeature {

extension GeneralAppSettingsStorage {
func betaFeatureEnabled(_ feature: BetaFeature) -> Bool {
value(for: feature.settingsKey)
guard feature.isAvailable else {
return false
}
return value(for: feature.settingsKey)
}

func betaFeatureEnabledBinding(_ feature: BetaFeature) -> Binding<Bool> {
Expand Down Expand Up @@ -109,5 +132,12 @@ private extension BetaFeature {
static let couponManagementDescription = NSLocalizedString(
"Test out managing coupons as we get ready to launch",
comment: "Cell description on beta features screen to enable coupon management")

static let inAppPurchasesManagementTitle = NSLocalizedString(
"In-app purchases",
comment: "Cell title on beta features screen to enable in-app purchases")
static let inAppPurchasesManagementDescription = NSLocalizedString(
"Test out in-app purchases as we get ready to launch",
comment: "Cell description on beta features screen to enable in-app purchases")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct BetaFeaturesConfiguration: View {

var body: some View {
List {
ForEach(BetaFeature.allCases) { feature in
ForEach(BetaFeature.availableFeatures) { feature in
Section(footer: Text(feature.description)) {
TitleAndToggleRow(title: feature.title, isOn: appSettings.betaFeatureEnabledBinding(feature))
}
Expand Down
6 changes: 6 additions & 0 deletions WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct HubMenu: View {
@State private var showingInbox = false
@State private var showingReviews = false
@State private var showingCoupons = false
@State private var showingIAPDebug = false

/// State to disable multiple taps on menu items
/// Make sure to reset the value to false after dismissing sub-flows
Expand Down Expand Up @@ -73,6 +74,8 @@ struct HubMenu: View {
showingReviews = true
case HubMenuViewModel.Coupons.id:
showingCoupons = true
case HubMenuViewModel.InAppPurchases.id:
showingIAPDebug = true
default:
break
}
Expand Down Expand Up @@ -110,6 +113,9 @@ struct HubMenu: View {
NavigationLink(destination: CouponListView(siteID: viewModel.siteID), isActive: $showingCoupons) {
EmptyView()
}.hidden()
NavigationLink(destination: InAppPurchasesDebugView(), isActive: $showingIAPDebug) {
EmptyView()
}.hidden()
LazyNavigationLink(destination: viewModel.getReviewDetailDestination(), isActive: $viewModel.showingReviewDetail) {
EmptyView()
}
Expand Down
20 changes: 19 additions & 1 deletion WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SwiftUI
import Combine
import Experiments
import Yosemite
import Storage

extension NSNotification.Name {
/// Posted whenever the hub menu view did appear.
Expand Down Expand Up @@ -54,6 +55,7 @@ final class HubMenuViewModel: ObservableObject {

private let stores: StoresManager
private let featureFlagService: FeatureFlagService
private let generalAppSettings: GeneralAppSettingsStorage

private var productReviewFromNoteParcel: ProductReviewFromNoteParcel?

Expand All @@ -64,11 +66,13 @@ final class HubMenuViewModel: ObservableObject {
init(siteID: Int64,
navigationController: UINavigationController? = nil,
featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService,
stores: StoresManager = ServiceLocator.stores) {
stores: StoresManager = ServiceLocator.stores,
generalAppSettings: GeneralAppSettingsStorage = ServiceLocator.generalAppSettings) {
self.siteID = siteID
self.navigationController = navigationController
self.stores = stores
self.featureFlagService = featureFlagService
self.generalAppSettings = generalAppSettings
observeSiteForUIUpdates()
}

Expand All @@ -80,6 +84,9 @@ final class HubMenuViewModel: ObservableObject {
///
func setupMenuElements() {
menuElements = [Payments(), WoocommerceAdmin(), ViewStore(), Reviews()]
if generalAppSettings.betaFeatureEnabled(.inAppPurchases) {
menuElements.append(InAppPurchases())
}

let inboxUseCase = InboxEligibilityUseCase(stores: stores, featureFlagService: featureFlagService)
inboxUseCase.isEligibleForInbox(siteID: siteID) { [weak self] isInboxMenuShown in
Expand Down Expand Up @@ -259,6 +266,17 @@ extension HubMenuViewModel {
let trackingOption: String = "reviews"
}

struct InAppPurchases: HubMenuItem {
static var id = "iap"

let title: String = "[Debug] IAP"
let icon: UIImage = UIImage(systemName: "ladybug.fill")!
let iconColor: UIColor = .red
let badge: HubMenuBadgeType = .number(number: 0)
let accessibilityIdentifier: String = "menu-iap"
let trackingOption: String = "debug-iap"
}

private enum Localization {
static let payments = NSLocalizedString("Payments",
comment: "Title of the hub menu payments button")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import SwiftUI
import StoreKit

struct InAppPurchasesDebugView: View {
var body: some View {
Group {
Text("No products available")
}
.navigationTitle("IAP Debug")
}
}

struct InAppPurchasesDebugView_Previews: PreviewProvider {
static var previews: some View {
InAppPurchasesDebugView()
}
}
12 changes: 12 additions & 0 deletions WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1776,6 +1776,7 @@
E12AF69B26BA8B2000C371C1 /* CardPresentPaymentsOnboardingUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12AF69A26BA8B2000C371C1 /* CardPresentPaymentsOnboardingUseCaseTests.swift */; };
E12FB786266E0CAE0039E9C2 /* ApllicationLogDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12FB785266E0CAE0039E9C2 /* ApllicationLogDetailView.swift */; };
E1308381270311E200D5A68D /* CardPresentModalUpdateFailedLowBattery.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1308380270311E200D5A68D /* CardPresentModalUpdateFailedLowBattery.swift */; };
E1325EFB28FD544E00EC9B2A /* InAppPurchasesDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1325EFA28FD544E00EC9B2A /* InAppPurchasesDebugView.swift */; };
E138D4F4269ED9C3006EA5C6 /* InPersonPaymentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E138D4F3269ED9C3006EA5C6 /* InPersonPaymentsViewController.swift */; };
E138D4FC269EEAFE006EA5C6 /* InPersonPaymentsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E138D4FB269EEAFE006EA5C6 /* InPersonPaymentsViewModel.swift */; };
E15F163126C5117300D3059B /* InPersonPaymentsNoConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E15F163026C5117300D3059B /* InPersonPaymentsNoConnectionView.swift */; };
Expand Down Expand Up @@ -3706,6 +3707,7 @@
E12AF69A26BA8B2000C371C1 /* CardPresentPaymentsOnboardingUseCaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardPresentPaymentsOnboardingUseCaseTests.swift; sourceTree = "<group>"; };
E12FB785266E0CAE0039E9C2 /* ApllicationLogDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApllicationLogDetailView.swift; sourceTree = "<group>"; };
E1308380270311E200D5A68D /* CardPresentModalUpdateFailedLowBattery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalUpdateFailedLowBattery.swift; sourceTree = "<group>"; };
E1325EFA28FD544E00EC9B2A /* InAppPurchasesDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesDebugView.swift; sourceTree = "<group>"; };
E138D4F3269ED9C3006EA5C6 /* InPersonPaymentsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsViewController.swift; sourceTree = "<group>"; };
E138D4FB269EEAFE006EA5C6 /* InPersonPaymentsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsViewModel.swift; sourceTree = "<group>"; };
E15F163026C5117300D3059B /* InPersonPaymentsNoConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsNoConnectionView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6757,6 +6759,7 @@
B56DB3EF2049C06D00D4AA8E /* ViewRelated */ = {
isa = PBXGroup;
children = (
E1325EF928FD543B00EC9B2A /* InAppPurchases */,
03FBDA9B263AD47200ACE257 /* Coupons */,
571B850024CF7E1600CF58A7 /* InAppFeedback */,
0235594024496414004BE2B8 /* BottomSheet */,
Expand Down Expand Up @@ -8490,6 +8493,14 @@
path = Settings;
sourceTree = "<group>";
};
E1325EF928FD543B00EC9B2A /* InAppPurchases */ = {
isa = PBXGroup;
children = (
E1325EFA28FD544E00EC9B2A /* InAppPurchasesDebugView.swift */,
);
path = InAppPurchases;
sourceTree = "<group>";
};
E138D4F2269ED99A006EA5C6 /* In-Person Payments */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -9576,6 +9587,7 @@
D8C11A4E22DD235F00D4A88D /* OrderDetailsResultsControllers.swift in Sources */,
D8C251D9230D256F00F49782 /* NoticePresenter.swift in Sources */,
77E53EB8250E6A4E003D385F /* ProductDownloadListViewController.swift in Sources */,
E1325EFB28FD544E00EC9B2A /* InAppPurchasesDebugView.swift in Sources */,
74460D4022289B7600D7316A /* Coordinator.swift in Sources */,
B57C743D20F5493300EEFC87 /* AccountHeaderView.swift in Sources */,
31AD0B1126E9575F000B6391 /* CardPresentModalConnectingFailed.swift in Sources */,
Expand Down
Loading