diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsEvent+DomainSettings.swift b/WooCommerce/Classes/Analytics/WooAnalyticsEvent+DomainSettings.swift deleted file mode 100644 index 8e68a79b5ea..00000000000 --- a/WooCommerce/Classes/Analytics/WooAnalyticsEvent+DomainSettings.swift +++ /dev/null @@ -1,64 +0,0 @@ -import enum Yosemite.CreateAccountError - -extension WooAnalyticsEvent { - enum DomainSettings { - /// Event property keys. - private enum Key { - static let source = "source" - static let step = "step" - static let useDomainCredit = "use_domain_credit" - } - - /// Tracked step for each step in the custom domains. - static func domainSettingsStep(source: DomainSettingsCoordinator.Source, step: Step) -> WooAnalyticsEvent { - WooAnalyticsEvent(statName: .domainSettingsStep, properties: [ - Key.source: source.analyticsValue, - Key.step: step.rawValue - ]) - } - - /// Tracked when the domain contact info validation fails when redeeming a domain with domain credit. - static func domainContactInfoValidationFailed(source: DomainSettingsCoordinator.Source, error: Error) -> WooAnalyticsEvent { - WooAnalyticsEvent(statName: .domainContactInfoValidationFailed, - properties: [Key.source: source.analyticsValue], - error: error) - } - - /// Tracked when the custom domain purchase or redemption succeeds. - static func domainSettingsCustomDomainPurchaseSuccess(source: DomainSettingsCoordinator.Source, useDomainCredit: Bool) -> WooAnalyticsEvent { - WooAnalyticsEvent(statName: .domainSettingsCustomDomainPurchaseSuccess, - properties: [Key.source: source.analyticsValue, Key.useDomainCredit: useDomainCredit]) - } - - /// Tracked when the custom domain purchase or redemption fails. - static func domainSettingsCustomDomainPurchaseFailed(source: DomainSettingsCoordinator.Source, - useDomainCredit: Bool, - error: Error) -> WooAnalyticsEvent { - WooAnalyticsEvent(statName: .domainSettingsCustomDomainPurchaseFailed, - properties: [Key.source: source.analyticsValue, Key.useDomainCredit: useDomainCredit], - error: error) - } - } -} - -extension WooAnalyticsEvent.DomainSettings { - /// Steps of the domain settings flow. The raw value is used for the event property. - enum Step: String { - case dashboard = "dashboard" - case domainSelector = "picker" - case webCheckout = "web_checkout" - case contactInfo = "contact_info" - case purchaseSuccess = "purchase_success" - } -} - -private extension DomainSettingsCoordinator.Source { - var analyticsValue: String { - switch self { - case .settings: - return "settings" - case .dashboardOnboarding: - return "onboarding" - } - } -} diff --git a/WooCommerce/Classes/Extensions/UIImage+Woo.swift b/WooCommerce/Classes/Extensions/UIImage+Woo.swift index 292a130def0..c9827e7fd9f 100644 --- a/WooCommerce/Classes/Extensions/UIImage+Woo.swift +++ b/WooCommerce/Classes/Extensions/UIImage+Woo.swift @@ -301,12 +301,6 @@ extension UIImage { return UIImage.gridicon(.cross, size: CGSize(width: 22, height: 22)) } - /// Domain credit image. - /// - static var domainCreditImage: UIImage { - return UIImage(named: "domain-credit")! - } - /// Domain purchase success image. /// static var domainPurchaseSuccessImage: UIImage { diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainContactInfoForm.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainContactInfoForm.swift deleted file mode 100644 index 4a7a97cb4fb..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainContactInfoForm.swift +++ /dev/null @@ -1,158 +0,0 @@ -import SwiftUI -import Yosemite - -final class DomainContactInfoFormHostingController: UIHostingController { - /// - Parameters: - /// - viewModel: View model for the domain contact info form. - /// - onCompletion: Called when the contact info is complete and validated. - init(viewModel: DomainContactInfoFormViewModel, - onCompletion: @escaping (DomainContactInfo) async -> Void) { - super.init(rootView: DomainContactInfoForm(viewModel: viewModel, - onCompletion: onCompletion)) - } - - required dynamic init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - configureTransparentNavigationBar() - } -} - -/// Allows the user to edit contact info when claiming a domain with domain credit. -struct DomainContactInfoForm: View { - @Environment(\.safeAreaInsets) var safeAreaInsets: EdgeInsets - @State private var showingCustomerSearch: Bool = false - @StateObject private var viewModel: DomainContactInfoFormViewModel - private let onCompletion: (DomainContactInfo) async -> Void - - init(viewModel: DomainContactInfoFormViewModel, - onCompletion: @escaping (DomainContactInfo) async -> Void) { - self._viewModel = StateObject(wrappedValue: viewModel) - self.onCompletion = onCompletion - } - - var body: some View { - Group { - ScrollView { - SingleAddressForm(fields: $viewModel.fields, - countryViewModelClosure: viewModel.createCountryViewModel, - stateViewModelClosure: viewModel.createStateViewModel, - sectionTitle: viewModel.sectionTitle, - showEmailField: viewModel.showEmailField, - showPhoneCountryCodeField: viewModel.showPhoneCountryCodeField, - showStateFieldAsSelector: viewModel.showStateFieldAsSelector) - .accessibilityElement(children: .contain) - - Spacer(minLength: safeAreaInsets.bottom) - } - .disableAutocorrection(true) - .background(Color(.listBackground)) - .ignoresSafeArea(.container, edges: [.horizontal, .bottom]) - } - .navigationTitle(viewModel.viewTitle) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .confirmationAction) { - navigationBarTrailingItem() - } - } - .wooNavigationBarStyle() - .redacted(reason: viewModel.showPlaceholders ? .placeholder : []) - .shimmering(active: viewModel.showPlaceholders) - .onAppear { - viewModel.onLoadTrigger.send() - } - .notice($viewModel.notice) - } - - /// Decides if the navigation trailing item should be a done button or a loading indicator. - /// - @ViewBuilder func navigationBarTrailingItem() -> some View { - switch viewModel.navigationTrailingItem { - case .done(let enabled): - Button(Localization.done) { - Task { @MainActor in - do { - viewModel.performingNetworkRequest.send(true) - let contactInfo = try await viewModel.validateContactInfo() - await onCompletion(contactInfo) - viewModel.performingNetworkRequest.send(false) - } catch { - viewModel.performingNetworkRequest.send(false) - } - } - } - .disabled(!enabled) - case .loading: - ProgressView() - } - } -} - -private extension DomainContactInfoForm { - enum Localization { - static let done = NSLocalizedString("Done", comment: "Text for the done button in the domain contact info form.") - } -} - -#if DEBUG - -import Yosemite - -/// StoresManager that specifically handles `DomainAction` for `DomainSelectorView` previews. -final private class DomainContactInfoFormStores: DefaultStoresManager { - init() { - super.init(sessionManager: ServiceLocator.stores.sessionManager) - } - - override func dispatch(_ action: Action) { - if let action = action as? DataAction { - if case let .synchronizeCountries(_, completion) = action { - completion(.success([.init(code: "US", name: "United States", states: [.init(code: "CA", name: "California")])])) - } - } - } -} - - -struct DomainContactInfoForm_Previews: PreviewProvider { - private static let viewModelWithoutContactInfo = DomainContactInfoFormViewModel(siteID: 134, - contactInfoToEdit: nil, - domain: "", - source: .settings, - stores: DomainContactInfoFormStores()) - private static let contactInfo = DomainContactInfo(firstName: "Woo", - lastName: "Testing", - organization: "WooCommerce org", - address1: "335 2nd St", - address2: "Apt 222", - postcode: "94111", - city: "San Francisco", - state: "CA", - countryCode: "US", - phone: "+886.911123456", - email: "woo@store.com") - private static let viewModelWithContactInfo = DomainContactInfoFormViewModel(siteID: 134, - contactInfoToEdit: contactInfo, - domain: "", - source: .settings, - stores: DomainContactInfoFormStores()) - - static var previews: some View { - NavigationView { - DomainContactInfoForm(viewModel: viewModelWithoutContactInfo) { _ in } - } - .previewDisplayName("Empty contact info") - - NavigationView { - DomainContactInfoForm(viewModel: viewModelWithContactInfo) { _ in } - } - .previewDisplayName("Pre-filled contact info") - } -} - -#endif diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainContactInfoFormViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainContactInfoFormViewModel.swift deleted file mode 100644 index 138b965b545..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainContactInfoFormViewModel.swift +++ /dev/null @@ -1,176 +0,0 @@ -import Combine -import Foundation -import Yosemite -import protocol Storage.StorageManagerType -import protocol WooFoundation.Analytics - -/// View model for `DomainContactInfoForm`. -final class DomainContactInfoFormViewModel: AddressFormViewModel, AddressFormViewModelProtocol { - let siteID: Int64 - private let domain: String - private let source: DomainSettingsCoordinator.Source - - private var contactInfo: DomainContactInfo { - let phone: String = { - // The user can enter characters like `-` or `.` into the field, and the API expects digits only. - let countryCode = fields.phoneCountryCode.filter { $0.isNumber } - let number = fields.phone.filter { $0.isNumber } - return "\(Constants.phoneNumberPrefix)\(countryCode)\(Constants.phoneNumberSeparator)\(number)" - }() - return .init(firstName: fields.firstName, - lastName: fields.lastName, - organization: fields.company, - address1: fields.address1, - address2: fields.address2, - postcode: fields.postcode, - city: fields.city, - state: fields.selectedState?.code ?? "", - countryCode: fields.selectedCountry?.code ?? "", - phone: phone, - email: fields.email) - } - - init(siteID: Int64, - contactInfoToEdit: DomainContactInfo?, - domain: String, - source: DomainSettingsCoordinator.Source, - storageManager: StorageManagerType = ServiceLocator.storageManager, - stores: StoresManager = ServiceLocator.stores, - analytics: Analytics = ServiceLocator.analytics) { - self.siteID = siteID - self.domain = domain - self.source = source - - let (addressToEdit, phoneCountryCode): (Address, String?) = { - guard let contactInfoToEdit else { - return (.empty, nil) - } - - let (phoneCountryCode, phoneNumber): (String?, String?) = { - // The phone number in the contact info API is in the format of `+\(countryCode).\(phoneNumber)`. - let phoneParts = contactInfoToEdit.phone? - .replacingOccurrences(of: Constants.phoneNumberPrefix, with: "") - .split(separator: Constants.phoneNumberSeparator) - guard let phoneParts, phoneParts.count == 2 else { - return (nil, nil) - } - return (String(phoneParts[0]), String(phoneParts[1])) - }() - return (.init(firstName: contactInfoToEdit.firstName, - lastName: contactInfoToEdit.lastName, - company: contactInfoToEdit.organization, - address1: contactInfoToEdit.address1, - address2: contactInfoToEdit.address2, - city: contactInfoToEdit.city, - state: contactInfoToEdit.state ?? "", - postcode: contactInfoToEdit.postcode, - country: contactInfoToEdit.countryCode, - phone: phoneNumber, - email: contactInfoToEdit.email), phoneCountryCode) - }() - - super.init(siteID: siteID, - address: addressToEdit, - phoneCountryCode: phoneCountryCode ?? "", - isDoneButtonAlwaysEnabled: true, - storageManager: storageManager, - stores: stores, - analytics: analytics) - } - - /// Validates and returns the contact info on success. - /// - Returns: Contact info after it is validated remotely. - @MainActor - func validateContactInfo() async throws -> DomainContactInfo { - guard fields.email.isNotEmpty, validateEmail() else { - notice = AddressFormViewModel.NoticeFactory.createInvalidEmailNotice() - throw ContactInfoError.invalidEmail - } - - do { - try await validate() - return contactInfo - } catch DomainContactInfoError.invalid(let messages) { - analytics.track(event: .DomainSettings.domainContactInfoValidationFailed(source: source, error: DomainContactInfoError.invalid(messages: messages))) - let message = messages?.joined(separator: "\n") ?? Localization.defaultValidationErrorMessage - notice = .init(title: Localization.validationErrorTitle, message: message, feedbackType: .error) - throw DomainContactInfoError.invalid(messages: messages) - } catch { - analytics.track(event: .DomainSettings.domainContactInfoValidationFailed(source: source, error: error)) - notice = .init(title: error.localizedDescription, feedbackType: .error) - throw error - } - } - - // MARK: - Protocol conformance - - /// Email is a required field for domain contact info. - let showEmailField: Bool = true - - /// Phone country code is a required field for domain contact info. - let showPhoneCountryCodeField: Bool = true - - let viewTitle: String = Localization.title - - let sectionTitle: String = Localization.addressSection - - let secondarySectionTitle: String = "" - - let showAlternativeUsageToggle: Bool = false - - let alternativeUsageToggleTitle: String? = nil - - let showDifferentAddressToggle: Bool = false - - let differentAddressToggleTitle: String? = nil - - func saveAddress(onFinish: @escaping (Bool) -> Void) { - fatalError("Please call `validateContactInfo` instead.") - } - - override func trackOnLoad() { - // TODO: 8558 - analytics - } - - func userDidCancelFlow() { - // TODO: 8558 - analytics - } -} - -private extension DomainContactInfoFormViewModel { - @MainActor - func validate() async throws { - try await withCheckedThrowingContinuation { continuation in - stores.dispatch(DomainAction.validate(domainContactInfo: contactInfo, domain: domain) { result in - continuation.resume(with: result) - }) - } - } -} - -private extension DomainContactInfoFormViewModel { - enum ContactInfoError: Error { - case invalidEmail - } -} - -private extension DomainContactInfoFormViewModel { - // MARK: Constants - enum Localization { - static let title = NSLocalizedString("Register domain", comment: "Title of the domain contact info form.") - static let addressSection = NSLocalizedString("ADDRESS", comment: "Address section title in the domain contact info form.") - static let validationErrorTitle = NSLocalizedString( - "Form Validation Error", - comment: "Title in the error notice when a validation error occurs after submitting domain contact info." - ) - static let defaultValidationErrorMessage = NSLocalizedString( - "Some unexpected error with the validation. Please check the fields and try again.", - comment: "Message in the error notice when an unknown validation error occurs after submitting domain contact info." - ) - } - - enum Constants { - static let phoneNumberPrefix: String = "+" - static let phoneNumberSeparator: Character = "." - } -} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainPurchaseSuccessView.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainPurchaseSuccessView.swift deleted file mode 100644 index 6c20f49aa18..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainPurchaseSuccessView.swift +++ /dev/null @@ -1,105 +0,0 @@ -import SwiftUI - -/// Hosting controller that wraps the `DomainPurchaseSuccessView` view. -final class DomainPurchaseSuccessHostingController: UIHostingController { - init(viewModel: DomainPurchaseSuccessView.ViewModel, onContinue: @escaping () -> Void) { - super.init(rootView: DomainPurchaseSuccessView(viewModel: viewModel, - onContinue: onContinue)) - } - - required dynamic init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - configureTransparentNavigationBar() - } -} - -/// Shows congratulatory UI after a domain is purchased or redeemed successfully. -struct DomainPurchaseSuccessView: View { - /// Necessary data to show the success UI. - struct ViewModel { - /// Domain name that is purchased. - let domainName: String - } - - let viewModel: ViewModel - /// Called when the user taps to continue. - let onContinue: () -> Void - - var body: some View { - ScrollView { - VStack(alignment: .center, spacing: Layout.contentSpacing) { - Image(uiImage: .domainPurchaseSuccessImage) - Text(Localization.title) - .secondaryTitleStyle() - .multilineTextAlignment(.center) - Text(viewModel.domainName) - .frame(maxWidth: .infinity) - .padding(Layout.domainNamePadding) - .headlineStyle() - .background(Color(.systemGray6)) - .cornerRadius(Layout.domainNameCornerRadius) - Text(Localization.subtitle) - .multilineTextAlignment(.center) - Text(Localization.instructions) - .subheadlineStyle() - .multilineTextAlignment(.center) - } - .padding(Layout.contentPadding) - } - .safeAreaInset(edge: .bottom) { - VStack { - Divider() - .dividerStyle() - Button(Localization.continueButtonTitle) { - onContinue() - } - .buttonStyle(PrimaryButtonStyle()) - .padding(Layout.defaultPadding) - } - .background(Color(.systemBackground)) - } - .navigationBarBackButtonHidden() - } -} - -private extension DomainPurchaseSuccessView { - enum Localization { - static let title = NSLocalizedString( - "Congratulations on your purchase", - comment: "Title of the domain purchase success screen." - ) - static let subtitle = NSLocalizedString( - "Your site address is being set up. It may take up to 30 minutes for your domain to start working.", - comment: "Subtitle of the domain purchase success screen." - ) - static let instructions = NSLocalizedString( - "You can find the domain settings in menu > settings", - comment: "Instructions of the domain purchase success screen." - ) - static let continueButtonTitle = NSLocalizedString( - "Done", - comment: "Title of the button to finish the domain purchase success screen." - ) - } - - enum Layout { - static let contentSpacing: CGFloat = 24 - static let contentPadding: EdgeInsets = .init(top: 88, leading: 16, bottom: 88, trailing: 16) - static let domainNameCornerRadius: CGFloat = 8 - static let domainNamePadding: EdgeInsets = .init(top: 8, leading: 8, bottom: 8, trailing: 8) - static let defaultPadding: EdgeInsets = .init(top: 10, leading: 16, bottom: 10, trailing: 16) - } -} - -struct DomainPurchaseSuccessView_Previews: PreviewProvider { - static var previews: some View { - NavigationView { - DomainPurchaseSuccessView(viewModel: .init(domainName: "go.trees")) {} - } - } -} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainRowView.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainRowView.swift deleted file mode 100644 index f82350779f5..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainRowView.swift +++ /dev/null @@ -1,80 +0,0 @@ -import SwiftUI - -/// View model for a row in a list of domain suggestions. -struct DomainRowViewModel { - /// The domain name is used for the selected state. - let name: String - /// Attributed name to be displayed in the row. - let attributedName: AttributedString - /// Attributed detail to be displayed in the row. - let attributedDetail: AttributedString? - /// Whether the domain is selected. - let isSelected: Bool - - init(domainName: String, attributedDetail: AttributedString?, searchQuery: String, isSelected: Bool) { - self.name = domainName - self.attributedDetail = attributedDetail - self.isSelected = isSelected - self.attributedName = { - var attributedName = AttributedString(domainName) - attributedName.font = isSelected ? .body.bold(): .body - attributedName.foregroundColor = .init(.label) - - if let rangeOfSearchQuery = attributedName - .range(of: searchQuery - // Removes leading/trailing spaces in the search query. - .trimmingCharacters(in: .whitespacesAndNewlines) - // Removes spaces in the search query. - .split(separator: " ").joined() - .lowercased()) { - attributedName[rangeOfSearchQuery].font = .body - attributedName[rangeOfSearchQuery].foregroundColor = .init(.secondaryLabel) - } - return attributedName - }() - } -} - -/// A row that shows an attributed domain name with a checkmark if the domain is selected. -struct DomainRowView: View { - let viewModel: DomainRowViewModel - - var body: some View { - HStack { - VStack(alignment: .leading, spacing: Layout.spacingBetweenNameAndDetail) { - Text(viewModel.attributedName) - if let attributedDetail = viewModel.attributedDetail { - Text(attributedDetail) - } - } - if viewModel.isSelected { - Spacer() - Image(uiImage: .checkmarkImage) - .foregroundColor(Color(.brand)) - } - } - .padding(Layout.insets) - } -} - -private extension DomainRowView { - enum Layout { - static let insets: EdgeInsets = .init(top: 10, leading: 16, bottom: 10, trailing: 16) - static let spacingBetweenNameAndDetail: CGFloat = 4 - } -} - -struct DomainRowView_Previews: PreviewProvider { - static var previews: some View { - VStack(alignment: .leading) { - DomainRowView(viewModel: .init(domainName: "whitechristmastrees.mywc.mysite", - attributedDetail: nil, - searchQuery: "White Christmas Trees", - isSelected: true)) - DomainRowView(viewModel: .init(domainName: "whitechristmastrees.mywc.mysite", - attributedDetail: nil, - searchQuery: "White Christmas", - isSelected: false)) - } - } -} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorDataProvider.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorDataProvider.swift deleted file mode 100644 index 24a63aae64e..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorDataProvider.swift +++ /dev/null @@ -1,131 +0,0 @@ -import Foundation -import Yosemite - -/// Provides domain suggestions data of a generic type. -/// The generic type allows different domain suggestion schemas, like free and paid domains. -protocol DomainSelectorDataProvider { - associatedtype DomainSuggestion - - /// Loads domain suggestions async from the remote. - /// - Parameter query: Search query for the domain suggestions. - /// - Returns: A list of domain suggestions. - func loadDomainSuggestions(query: String) async throws -> [DomainSuggestion] -} - -/// View model for free domain suggestion UI that shows the domain name. -struct FreeDomainSuggestionViewModel: DomainSuggestionViewProperties, Equatable { - let name: String - let attributedDetail: AttributedString? = nil - - init(domainSuggestion: FreeDomainSuggestion) { - self.name = domainSuggestion.name - } -} - -/// Provides domain suggestions that are free. -final class FreeDomainSelectorDataProvider: DomainSelectorDataProvider { - private let stores: StoresManager - - init(stores: StoresManager = ServiceLocator.stores) { - self.stores = stores - } - - @MainActor - func loadDomainSuggestions(query: String) async throws -> [FreeDomainSuggestionViewModel] { - try await withCheckedThrowingContinuation { continuation in - stores.dispatch(DomainAction.loadFreeDomainSuggestions(query: query) { result in - continuation.resume(with: result.map { $0 - .filter { $0.isFree } - .map { FreeDomainSuggestionViewModel(domainSuggestion: $0) } - }) - }) - } - } -} - -/// View model for paid domain suggestion UI that shows the domain name and attributed price info. -/// The product ID is for creating a cart after a domain is selected. -struct PaidDomainSuggestionViewModel: DomainSuggestionViewProperties, Equatable { - let name: String - let attributedDetail: AttributedString? - - /// Whether the domain is a premium domain. A premium domain cannot be redeemed with domain credit. - let isPremium: Bool - - // Properties for cart creation after a domain is selected. - let productID: Int64 - let supportsPrivacy: Bool - let hasDomainCredit: Bool - - init(domainSuggestion: PaidDomainSuggestion, hasDomainCredit: Bool) { - self.name = domainSuggestion.name - self.attributedDetail = { - var attributedCost = AttributedString(.init(format: Localization.priceFormat, domainSuggestion.cost, domainSuggestion.term)) - attributedCost.font = .body - attributedCost.foregroundColor = .init(.secondaryLabel) - - if hasDomainCredit && !domainSuggestion.isPremium { - // Strikethrough style for the original cost string. - attributedCost.strikethroughStyle = .single - - var attributedDomainCreditPricing = AttributedString(Localization.domainCreditPricing) - attributedDomainCreditPricing.font = .body - attributedDomainCreditPricing.foregroundColor = .init(.domainCreditPricing) - - return attributedCost + .init(" ") + attributedDomainCreditPricing - } else if let saleCost = domainSuggestion.saleCost { - // Strikethrough style for the original cost string. - if let range = attributedCost.range(of: domainSuggestion.cost) { - attributedCost[range].strikethroughStyle = .single - } - - var attributedSaleCost = AttributedString(saleCost) - attributedSaleCost.font = .body - attributedSaleCost.foregroundColor = .init(.domainSalePrice) - - return attributedSaleCost + .init(" ") + attributedCost - } else { - return attributedCost - } - }() - self.isPremium = domainSuggestion.isPremium - self.productID = domainSuggestion.productID - self.supportsPrivacy = domainSuggestion.supportsPrivacy - self.hasDomainCredit = hasDomainCredit - } -} - -extension PaidDomainSuggestionViewModel { - enum Localization { - static let priceFormat = NSLocalizedString( - "%1$@ / %2$@", - comment: "The original price of a domain. %1$@ is the price per term. " + - "%2$@ is the duration of each pricing term, usually year." - ) - static let domainCreditPricing = NSLocalizedString( - "Free for the first year", - comment: "Label shown for domains that are free for the first year with available domain credit." - ) - } -} - -/// Provides domain suggestions that are paid. -final class PaidDomainSelectorDataProvider: DomainSelectorDataProvider { - private let stores: StoresManager - private let hasDomainCredit: Bool - - init(stores: StoresManager = ServiceLocator.stores, hasDomainCredit: Bool) { - self.stores = stores - self.hasDomainCredit = hasDomainCredit - } - - @MainActor - func loadDomainSuggestions(query: String) async throws -> [PaidDomainSuggestionViewModel] { - return try await withCheckedThrowingContinuation { [hasDomainCredit] continuation in - stores.dispatch(DomainAction.loadPaidDomainSuggestions(query: query, currencySettings: ServiceLocator.currencySettings) { result in - continuation.resume(with: result.map { $0.map { PaidDomainSuggestionViewModel(domainSuggestion: $0, - hasDomainCredit: hasDomainCredit) } }) - }) - } - } -} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorView.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorView.swift deleted file mode 100644 index c6585e547d3..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorView.swift +++ /dev/null @@ -1,348 +0,0 @@ -import SwiftUI - -/// Hosting controller that wraps the `DomainSelectorView` view with free domains. -final class FreeDomainSelectorHostingController: UIHostingController> { - /// - Parameters: - /// - viewModel: View model for the domain selector. - /// - onDomainSelection: Called when the user continues with a selected domain. - /// - onSupport: Called when the user taps to contact support. - init(viewModel: DomainSelectorViewModel, - onDomainSelection: @escaping (FreeDomainSuggestionViewModel) async -> Void, - onSupport: @escaping () -> Void) { - super.init(rootView: DomainSelectorView(viewModel: viewModel, - onDomainSelection: onDomainSelection, - onSupport: onSupport)) - } - - required dynamic init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - configureTransparentNavigationBar() - } -} - -/// Hosting controller that wraps the `DomainSelectorView` view with paid domains. -final class PaidDomainSelectorHostingController: UIHostingController> { - /// - Parameters: - /// - viewModel: View model for the domain selector. - /// - onDomainSelection: Called when the user continues with a selected domain. - /// - onSupport: Called when the user taps to contact support. If `nil`, the contact support CTA is not shown. - init(viewModel: DomainSelectorViewModel, - onDomainSelection: @escaping (PaidDomainSuggestionViewModel) async -> Void, - onSupport: (() -> Void)?) { - super.init(rootView: DomainSelectorView(viewModel: viewModel, - onDomainSelection: onDomainSelection, - onSupport: onSupport)) - } - - required dynamic init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - configureTransparentNavigationBar() - } -} - -/// Allows the user to search for a domain and then select one to continue. -struct DomainSelectorView: View -where DataProvider.DomainSuggestion == DomainSuggestion { - private let onDomainSelection: (DomainSuggestion) async -> Void - private let onSupport: (() -> Void)? - - /// View model to drive the view. - @ObservedObject private var viewModel: DomainSelectorViewModel - - /// Currently selected domain. - /// If this property is kept in the view model, a SwiftUI error appears `Publishing changes from within view updates` - /// when a domain row is selected. - @State private var selectedDomain: DomainSuggestion? - - @State private var isWaitingForDomainSelectionCompletion: Bool = false - - @FocusState private var textFieldIsFocused: Bool - - init(viewModel: DomainSelectorViewModel, - onDomainSelection: @escaping (DomainSuggestion) async -> Void, - onSupport: (() -> Void)?) { - self.viewModel = viewModel - self.onDomainSelection = onDomainSelection - self.onSupport = onSupport - } - - var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 0) { - // Header label. - // The subtitle is in an `.init` in order to support markdown. - Text(.init(viewModel.subtitle)) - .foregroundColor(Color(.secondaryLabel)) - .bodyStyle() - .padding(.horizontal, Layout.defaultHorizontalPadding) - .padding(.top, 16) - - Spacer() - .frame(height: 30) - - // Search text field. - SearchHeader(text: $viewModel.searchTerm, - placeholder: Localization.searchPlaceholder, - customizations: .init(backgroundColor: .clear, - borderColor: .separator, - internalHorizontalPadding: 21, - internalVerticalPadding: 12, - iconSize: .init(width: 14, height: 14))) - .focused($textFieldIsFocused) - - switch viewModel.state { - case .placeholder: - // Placeholder image when search query is empty. - Spacer() - .frame(height: 30) - - HStack { - Spacer() - Image(uiImage: .domainSearchPlaceholderImage) - Spacer() - } - case .loading: - // Progress indicator when loading domain suggestions. - Spacer() - .frame(height: 23) - - HStack { - Spacer() - ProgressView() - Spacer() - } - case .error(let errorMessage): - // Error message when there is an error loading domain suggestions. - Spacer() - .frame(height: 23) - - Text(errorMessage) - .foregroundColor(Color(.secondaryLabel)) - .bodyStyle() - .frame(maxWidth: .infinity, alignment: .center) - .multilineTextAlignment(.center) - .padding(Layout.defaultPadding) - case .results(let domains): - // Domain suggestions. - Text(Localization.suggestionsHeader) - .foregroundColor(Color(.secondaryLabel)) - .footnoteStyle() - .padding(.horizontal, Layout.defaultHorizontalPadding) - .padding(.vertical, insets: .init(top: 14, leading: 0, bottom: 8, trailing: 0)) - - LazyVStack { - ForEach(domains, id: \.name) { domain in - Button { - textFieldIsFocused = false - selectedDomain = domain - } label: { - VStack(alignment: .leading) { - DomainRowView(viewModel: - .init(domainName: domain.name, - attributedDetail: domain.attributedDetail, - searchQuery: viewModel.searchTerm, - isSelected: domain == selectedDomain)) - Divider() - .dividerStyle() - .padding(.leading, Layout.defaultHorizontalPadding) - } - } - } - } - } - } - } - .safeAreaInset(edge: .bottom) { - // Continue button when a domain is selected. - if let selectedDomain { - VStack { - Divider() - .dividerStyle() - Button(Localization.continueButtonTitle) { - Task { @MainActor in - isWaitingForDomainSelectionCompletion = true - await onDomainSelection(selectedDomain) - isWaitingForDomainSelectionCompletion = false - } - } - .buttonStyle(PrimaryLoadingButtonStyle(isLoading: isWaitingForDomainSelectionCompletion)) - .padding(Layout.defaultPadding) - } - .background(Color(.systemBackground)) - } - } - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - if let onSupport { - SupportButton { - onSupport() - } - } - } - } - .navigationTitle(viewModel.title) - .navigationBarTitleDisplayMode(.large) - .onChange(of: viewModel.isLoadingDomainSuggestions) { isLoadingDomainSuggestions in - // Resets selected domain when loading domain suggestions. - if isLoadingDomainSuggestions { - selectedDomain = nil - } - } - } -} - -/// Constants are computed static properties since stored properties are not supported in generic types. -private extension DomainSelectorView { - enum Layout { - static var defaultHorizontalPadding: CGFloat { 16 } - static var defaultPadding: EdgeInsets { .init(top: 10, leading: 16, bottom: 10, trailing: 16) } - } - - enum Localization { - static var searchPlaceholder: String { - NSLocalizedString("Type a name for your store", comment: "Placeholder of the search text field on the domain selector.") - } - static var suggestionsHeader: String { - NSLocalizedString("SUGGESTIONS", comment: "Header label of the domain suggestions on the domain selector.") - } - static var continueButtonTitle: String { - NSLocalizedString("Continue", comment: "Title of the button to continue with a selected domain.") - } - } -} - -#if DEBUG - -import Yosemite -import enum Networking.DotcomError - -/// StoresManager that specifically handles `DomainAction` for `DomainSelectorView` previews. -final class DomainSelectorViewStores: DefaultStoresManager { - private let freeDomainsResult: Result<[FreeDomainSuggestion], Error>? - private let paidDomainsResult: Result<[PaidDomainSuggestion], Error>? - - init(freeDomainsResult: Result<[FreeDomainSuggestion], Error>? = nil, - paidDomainsResult: Result<[PaidDomainSuggestion], Error>? = nil) { - self.freeDomainsResult = freeDomainsResult - self.paidDomainsResult = paidDomainsResult - super.init(sessionManager: ServiceLocator.stores.sessionManager) - } - - override func dispatch(_ action: Action) { - if let action = action as? DomainAction { - if case let .loadFreeDomainSuggestions(_, completion) = action { - if let freeDomainsResult { - completion(freeDomainsResult) - } - } else if case let .loadPaidDomainSuggestions(_, _, completion) = action { - if let paidDomainsResult { - completion(paidDomainsResult) - } - } - } - } -} - -struct DomainSelectorView_Previews: PreviewProvider { - static var previews: some View { - Group { - // Empty query state. - DomainSelectorView( - viewModel: - .init(title: "Choose a domain", - subtitle: "Your domain", - initialSearchTerm: "", - dataProvider: FreeDomainSelectorDataProvider( - stores: DomainSelectorViewStores() - )), - onDomainSelection: { _ in }, - onSupport: {} - ) - // Results state for free domains. - DomainSelectorView( - viewModel: - .init(title: "Choose a domain", - subtitle: "Your domain", - initialSearchTerm: "", - dataProvider: FreeDomainSelectorDataProvider( - stores: DomainSelectorViewStores(freeDomainsResult: .success([ - .init(name: "grapefruitsmoothie.com", isFree: true), - .init(name: "fruitsmoothie.com", isFree: true), - .init(name: "grapefruitsmoothiee.com", isFree: true), - .init(name: "freesmoothieeee.com", isFree: true), - .init(name: "greatfruitsmoothie1.com", isFree: true), - .init(name: "tropicalsmoothie.com", isFree: true) - ])) - )), - onDomainSelection: { _ in }, - onSupport: {} - ) - // Results state for paid domains. - DomainSelectorView( - viewModel: - .init(title: "Search domains", - subtitle: "Your domain mapped to **other.domain**", - initialSearchTerm: "fruit", - dataProvider: PaidDomainSelectorDataProvider( - stores: DomainSelectorViewStores(paidDomainsResult: .success([ - .init(productID: 1, - supportsPrivacy: true, - name: "grapefruitsmoothie.com", - term: "year", - cost: "NT$154.00", - isPremium: false), - .init(productID: 2, - supportsPrivacy: true, - name: "fruitsmoothie.com", - term: "year", - cost: "NT$610.00", - saleCost: "NT$154.00", - isPremium: false) - ])), - hasDomainCredit: true)), - onDomainSelection: { _ in }, - onSupport: {} - ) - // Error state. - DomainSelectorView( - viewModel: - .init(title: "Search domains", - subtitle: "Your domain", - initialSearchTerm: "test", - dataProvider: - FreeDomainSelectorDataProvider( - stores: DomainSelectorViewStores(freeDomainsResult: - .failure(DotcomError.unknown(code: "invalid_query", - message: "Domain searches must contain a word with the following characters.") - )))), - onDomainSelection: { _ in }, - onSupport: {} - ) - // Loading state. - DomainSelectorView( - viewModel: - .init(title: "Search domains", - subtitle: "Your domain", - initialSearchTerm: "", - dataProvider: FreeDomainSelectorDataProvider( - stores: DomainSelectorViewStores() - )), - onDomainSelection: { _ in }, - onSupport: {} - ) - } - } -} - -#endif diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorViewModel.swift deleted file mode 100644 index 2495b27d121..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorViewModel.swift +++ /dev/null @@ -1,157 +0,0 @@ -import Combine -import SwiftUI -import Yosemite -import enum Networking.DotcomError - -/// Properties that are necessary for displaying a domain suggestion in the UI. -protocol DomainSuggestionViewProperties { - /// Domain name. - var name: String { get } - /// Optional attributed detail, e.g. price info for paid domains. - var attributedDetail: AttributedString? { get } -} - -/// View model for `DomainSelectorView`. -final class DomainSelectorViewModel: ObservableObject -where DataProvider.DomainSuggestion == DomainSuggestion { - /// The state of the main view below the fixed header. - enum ViewState: Equatable { - /// When loading domain suggestions. - case loading - /// Shown when the search query is empty. - case placeholder - /// When there is an error loading domain suggestions. - case error(message: String) - /// When domain suggestions are displayed. - case results(domains: [DomainSuggestion]) - } - - let title: String - let subtitle: String - - /// Current search term entered by the user. - /// Each update will trigger a remote call for domain suggestions. - @Published var searchTerm: String = "" - - /// Domain names after domain suggestions are loaded remotely. - @Published private var domains: [DomainSuggestion] = [] - - /// Error message from loading domain suggestions. - @Published private var errorMessage: String? - - /// Whether domain suggestions are being loaded. - @Published private(set) var isLoadingDomainSuggestions: Bool = false - - /// The state of the main domain selector view based on the search query and loading state. - @Published private(set) var state: ViewState = .placeholder - - /// Subscription for search query changes for domain search. - private var searchQuerySubscription: AnyCancellable? - - private let dataProvider: DataProvider - private let debounceDuration: Double - - init(title: String, - subtitle: String, - initialSearchTerm: String = "", - dataProvider: DataProvider, - debounceDuration: Double = Constants.fieldDebounceDuration) { - self.title = title - self.subtitle = subtitle - - self.dataProvider = dataProvider - self.debounceDuration = debounceDuration - - // Sets the initial search term after related subscriptions are set up - // so that the initial value is always emitted. - // In `observeDomainQuery`, `share()` transforms the publisher to `PassthroughSubject` - // and thus the initial value isn't emitted in `observeDomainQuery` until setting the value afterward. - observeDomainQuery() - self.searchTerm = initialSearchTerm - - configureState() - } -} - -private extension DomainSelectorViewModel { - func observeDomainQuery() { - let searchQuery = $searchTerm - .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } - .filter { $0.isNotEmpty } - .removeDuplicates() - // In order to always trigger a network request for the initial search query while supporting debounce - // for the following user input, the first search query is concatenated with the debounce version. - searchQuerySubscription = Publishers.Concatenate( - prefix: searchQuery.first(), - suffix: searchQuery - .debounce(for: .seconds(debounceDuration), scheduler: DispatchQueue.main) - ) - .removeDuplicates() - .eraseToAnyPublisher() - .sink { [weak self] searchTerm in - guard let self = self else { return } - Task { @MainActor in - self.errorMessage = nil - self.isLoadingDomainSuggestions = true - - do { - self.domains = try await self.loadDomainSuggestions(query: searchTerm) - self.isLoadingDomainSuggestions = false - } catch { - self.isLoadingDomainSuggestions = false - self.handleError(error) - } - } - } - } - - func configureState() { - Publishers.CombineLatest4($searchTerm, $isLoadingDomainSuggestions, $errorMessage, $domains) - .map { searchTerm, isLoadingDomainSuggestions, errorMessage, domains in - if searchTerm.isEmpty { - return .placeholder - } else if isLoadingDomainSuggestions { - return .loading - } else if let errorMessage { - return .error(message: errorMessage) - } else { - return .results(domains: domains) - } - } - .assign(to: &$state) - } - - @MainActor - func loadDomainSuggestions(query: String) async throws -> [DomainSuggestion] { - try await dataProvider.loadDomainSuggestions(query: query) - } - - @MainActor - func handleError(_ error: Error) { - if let dotcomError = error as? DotcomError, - case let .unknown(_, message) = dotcomError { - errorMessage = message - } else { - errorMessage = Localization.defaultErrorMessage - } - DDLogError("Cannot load domain suggestions for \(searchTerm): \(error)") - } -} - -private extension DomainSelectorViewModel { - enum Constants { - static var fieldDebounceDuration: Double { - 0.3 - } - } -} - -extension DomainSelectorViewModel { - enum Localization { - static var defaultErrorMessage: String { - NSLocalizedString("Please try another query.", - comment: "Default message when there is an unexpected error loading domain suggestions on the domain selector.") - } - } -} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsCoordinator.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsCoordinator.swift deleted file mode 100644 index 9f20720395d..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsCoordinator.swift +++ /dev/null @@ -1,197 +0,0 @@ -import Combine -import UIKit -import Yosemite -import protocol WooFoundation.Analytics - -/// Coordinates navigation for domain settings flow. -final class DomainSettingsCoordinator: Coordinator { - /// Navigation source to domain settings. - enum Source { - /// Initiated from the settings. - case settings - /// Initiated from store onboarding in dashboard. - case dashboardOnboarding - } - - let navigationController: UINavigationController - - private let site: Site - private let stores: StoresManager - private let source: Source - private let analytics: Analytics - private let onDomainPurchased: (() -> Void)? - - init(source: Source, - site: Site, - navigationController: UINavigationController, - stores: StoresManager = ServiceLocator.stores, - analytics: Analytics = ServiceLocator.analytics, - onDomainPurchased: (() -> Void)? = nil) { - self.source = source - self.site = site - self.navigationController = navigationController - self.stores = stores - self.analytics = analytics - self.onDomainPurchased = onDomainPurchased - } - - @MainActor - func start() { - let settingsNavigationController = WooNavigationController() - let domainSettings = DomainSettingsHostingController(viewModel: .init(siteID: site.siteID, - stores: stores)) { [weak self] hasDomainCredit, freeStagingDomain in - guard let self else { return } - self.showDomainSelector(from: settingsNavigationController, hasDomainCredit: hasDomainCredit, freeStagingDomain: freeStagingDomain) - } onClose: { [weak self] in - self?.navigationController.dismiss(animated: true) - } - settingsNavigationController.pushViewController(domainSettings, animated: false) - navigationController.present(settingsNavigationController, animated: true) - analytics.track(event: .DomainSettings.domainSettingsStep(source: source, - step: .dashboard)) - } -} - -private extension DomainSettingsCoordinator { - @MainActor - func showDomainSelector(from navigationController: UINavigationController, hasDomainCredit: Bool, freeStagingDomain: String?) { - let subtitle = freeStagingDomain - .map { String(format: Localization.domainSelectorSubtitleFormat, $0) } ?? Localization.domainSelectorSubtitleWithoutFreeStagingDomain - let viewModel = DomainSelectorViewModel(title: Localization.domainSelectorTitle, - subtitle: subtitle, - initialSearchTerm: site.name, - dataProvider: PaidDomainSelectorDataProvider(stores: stores, - hasDomainCredit: hasDomainCredit)) - let domainSelector = PaidDomainSelectorHostingController(viewModel: viewModel, onDomainSelection: { [weak self] domain in - guard let self else { return } - let domainToPurchase = DomainToPurchase(name: domain.name, - productID: domain.productID, - supportsPrivacy: domain.supportsPrivacy) - if hasDomainCredit && domain.isPremium == false { - let contactInfo = try? await self.loadDomainContactInfo() - self.showContactInfoForm(from: navigationController, contactInfo: contactInfo, domain: domainToPurchase) - } else { - do { - try await self.createCart(domain: domainToPurchase) - self.showWebCheckout(from: navigationController, domain: domainToPurchase) - } catch { - // TODO: 8558 - error handling - DDLogError("⛔️ Error creating cart with the selected domain \(domain): \(error)") - } - } - }, onSupport: nil) - navigationController.show(domainSelector, sender: nil) - analytics.track(event: .DomainSettings.domainSettingsStep(source: source, - step: .domainSelector)) - } - - @MainActor - func showWebCheckout(from navigationController: UINavigationController, domain: DomainToPurchase) { - guard let siteURLHost = URLComponents(string: site.url)?.host else { - // TODO: 8558 - error handling - DDLogError("⛔️ Error showing web checkout for the selected domain \(domain) because of invalid site slug from site URL \(site.url)") - return - } - let checkoutViewModel = WebCheckoutViewModel(siteSlug: siteURLHost) { [weak self] in - guard let self else { return } - self.showSuccessView(from: navigationController, domainName: domain.name) - self.analytics.track(event: .DomainSettings.domainSettingsCustomDomainPurchaseSuccess(source: self.source, - useDomainCredit: false)) - } - let checkoutController = AuthenticatedWebViewController(viewModel: checkoutViewModel) - navigationController.pushViewController(checkoutController, animated: true) - analytics.track(event: .DomainSettings.domainSettingsStep(source: source, - step: .webCheckout)) - } -} - -private extension DomainSettingsCoordinator { - @MainActor - func showContactInfoForm(from navigationController: UINavigationController, - contactInfo: DomainContactInfo?, - domain: DomainToPurchase) { - let contactInfoForm = DomainContactInfoFormHostingController(viewModel: .init(siteID: site.siteID, - contactInfoToEdit: contactInfo, - domain: domain.name, - source: source, - stores: stores)) { [weak self] contactInfo in - guard let self else { return } - do { - try await self.redeemDomainCredit(domain: domain, contactInfo: contactInfo) - self.showSuccessView(from: navigationController, domainName: domain.name) - self.analytics.track(event: .DomainSettings.domainSettingsCustomDomainPurchaseSuccess(source: self.source, - useDomainCredit: true)) - } catch { - // TODO: 8558 - error handling - print("⛔️ Error redeeming domain credit with the selected domain \(domain): \(error)") - self.analytics.track(event: .DomainSettings.domainSettingsCustomDomainPurchaseFailed(source: self.source, - useDomainCredit: true, - error: error)) - } - } - navigationController.pushViewController(contactInfoForm, animated: true) - analytics.track(event: .DomainSettings.domainSettingsStep(source: source, - step: .contactInfo)) - } - - @MainActor - func showSuccessView(from navigationController: UINavigationController, - domainName: String) { - let successController = DomainPurchaseSuccessHostingController(viewModel: .init(domainName: domainName)) { - navigationController.popToRootViewController(animated: false) - } - navigationController.pushViewController(successController, animated: true) - onDomainPurchased?() - analytics.track(event: .DomainSettings.domainSettingsStep(source: source, - step: .purchaseSuccess)) - } -} - -private extension DomainSettingsCoordinator { - @MainActor - func createCart(domain: DomainToPurchase) async throws { - try await withCheckedThrowingContinuation { continuation in - stores.dispatch(DomainAction.createDomainShoppingCart(siteID: site.siteID, - domain: domain) { result in - continuation.resume(with: result) - }) - } - } - - @MainActor - func loadDomainContactInfo() async throws -> DomainContactInfo { - try await withCheckedThrowingContinuation { continuation in - stores.dispatch(DomainAction.loadDomainContactInfo { result in - continuation.resume(with: result) - }) - } - } - - @MainActor - func redeemDomainCredit(domain: DomainToPurchase, contactInfo: DomainContactInfo) async throws { - try await withCheckedThrowingContinuation { continuation in - stores.dispatch(DomainAction.redeemDomainCredit(siteID: site.siteID, - domain: domain, - contactInfo: contactInfo) { result in - continuation.resume(with: result) - }) - } - } -} - -private extension DomainSettingsCoordinator { - enum Localization { - static let domainSelectorTitle = NSLocalizedString( - "Search domains", - comment: "Title of the domain selector in domain settings." - ) - static let domainSelectorSubtitleFormat = NSLocalizedString( - "The domain purchased will redirect users to **%1$@**", - comment: "Subtitle of the domain selector in domain settings. %1$@ is the free domain of the site from WordPress.com." - ) - static let domainSelectorSubtitleWithoutFreeStagingDomain = NSLocalizedString( - "The domain purchased will redirect users to the current staging domain", - comment: "Subtitle of the domain selector in domain settings when a free staging domain is unavailable." - ) - } -} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsDomainCreditView.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsDomainCreditView.swift deleted file mode 100644 index f67d5c2de84..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsDomainCreditView.swift +++ /dev/null @@ -1,76 +0,0 @@ -import SwiftUI - -/// Shows a banner that explains what domain credit is and a CTA to redeem it. -struct DomainSettingsDomainCreditView: View { - let redeemDomainCreditTapped: () -> Void - - var body: some View { - VStack(alignment: .leading, spacing: 0) { - Divider() - .dividerStyle() - - HStack(alignment: .bottom) { - VStack(alignment: .leading) { - Text(Localization.title) - .bold() - Spacer() - .frame(height: Layout.spacingBetweenTitleAndSubtitle) - Text(Localization.subtitle) - .bodyStyle() - Spacer() - .frame(height: Layout.spacingBetweenSubtitleAndButton) - Button(Localization.buttonTitle) { - redeemDomainCreditTapped() - } - .buttonStyle(TextButtonStyle()) - } - .padding(Layout.textContainerInsets) - - Spacer() - - Image(uiImage: .domainCreditImage) - } - - Divider() - .dividerStyle() - - Text(Localization.footnote) - .footnoteStyle() - .padding(Layout.footnoteInsets) - } - } -} - -private extension DomainSettingsDomainCreditView { - enum Localization { - static let title = NSLocalizedString( - "Claim your free domain", - comment: "Title of the domain credit banner in domain settings." - ) - static let subtitle = NSLocalizedString( - "You have a free one-year domain registration included with your plan.", - comment: "Subtitle of the domain credit banner in domain settings." - ) - static let buttonTitle = NSLocalizedString( - "Claim Domain", - comment: "Title of button to redeem domain credit in the domain credit banner in domain settings." - ) - static let footnote = NSLocalizedString( - "The domain purchased will redirect users to your primary address.", - comment: "Footnote about the domain credit banner in domain settings." - ) - } - - enum Layout { - static let textContainerInsets: EdgeInsets = .init(top: 16, leading: 16, bottom: 16, trailing: 16) - static let footnoteInsets: EdgeInsets = .init(top: 8, leading: 16, bottom: 8, trailing: 16) - static let spacingBetweenTitleAndSubtitle: CGFloat = 8 - static let spacingBetweenSubtitleAndButton: CGFloat = 16 - } -} - -struct DomainSettingsDomainCreditView_Previews: PreviewProvider { - static var previews: some View { - DomainSettingsDomainCreditView {} - } -} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsListView.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsListView.swift deleted file mode 100644 index c29f409b5da..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsListView.swift +++ /dev/null @@ -1,75 +0,0 @@ -import SwiftUI - -/// Shows a list of domains in domain settings with a CTA to add a domain. -struct DomainSettingsListView: View { - let domains: [DomainSettingsViewModel.Domain] - let addDomain: () -> Void - - var body: some View { - VStack(alignment: .leading, spacing: 0) { - Text(Localization.header.uppercased()) - .padding(Layout.headerPadding) - .foregroundColor(Color(.secondaryLabel)) - .captionStyle() - - ForEach(domains, id: \.name) { domain in - VStack(alignment: .leading) { - Text(domain.name) - if let renewalDate = domain.autoRenewalDate { - Text(String(format: Localization.renewalDateFormat, renewalDate.toString(dateStyle: .medium, timeStyle: .none))) - .foregroundColor(Color(.secondaryLabel)) - } - } - .padding(Layout.domainPadding) - Divider() - .dividerStyle() - .padding(Layout.dividerPadding) - } - - Button(Localization.addDomain) { - addDomain() - } - .padding(Layout.actionButtonPadding) - .buttonStyle(PlusButtonStyle()) - - Divider() - .dividerStyle() - .padding(Layout.dividerPadding) - } - } -} - -private extension DomainSettingsListView { - enum Localization { - static let header = NSLocalizedString( - "Your site domains", - comment: "Header text of the site's domain list in domain settings." - ) - static let renewalDateFormat = NSLocalizedString( - "Renews on %1$@", - comment: "Renewal date of a site's domain in domain settings. " + - "Reads like `Renews on October 11, 2023`." - ) - static let addDomain = NSLocalizedString( - "Add a domain", - comment: "Title of button to add a domain in domain settings." - ) - } - - enum Layout { - static let headerPadding: EdgeInsets = .init(top: 18, leading: 16, bottom: 6, trailing: 16) - static let domainPadding: EdgeInsets = .init(top: 10, leading: 16, bottom: 10, trailing: 16) - static let actionButtonPadding: EdgeInsets = .init(top: 18, leading: 16, bottom: 18, trailing: 23) - static let dividerPadding: EdgeInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 0) - } -} - -struct DomainSettingsListView_Previews: PreviewProvider { - static var previews: some View { - DomainSettingsListView(domains: [ - .init(isPrimary: true, name: "play.store", autoRenewalDate: .distantFuture), - .init(isPrimary: false, name: "app.store", autoRenewalDate: nil), - .init(isPrimary: false, name: "woocommerce.store", autoRenewalDate: .now) - ], addDomain: {}) - } -} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsView.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsView.swift deleted file mode 100644 index 28b7fc87806..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsView.swift +++ /dev/null @@ -1,228 +0,0 @@ -import SwiftUI - -/// Hosting controller that wraps the `DomainSettingsView` view. -final class DomainSettingsHostingController: UIHostingController { - init(viewModel: DomainSettingsViewModel, - addDomain: @escaping (_ hasDomainCredit: Bool, _ freeStagingDomain: String?) -> Void, - onClose: @escaping () -> Void) { - super.init(rootView: DomainSettingsView(viewModel: viewModel, - addDomain: addDomain, - onClose: onClose)) - } - - required dynamic init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - configureTransparentNavigationBar() - } -} - -/// Shows a site's domains with actions to add a domain or redeem a domain credit. -struct DomainSettingsView: View { - @ObservedObject private var viewModel: DomainSettingsViewModel - @State private var isFetchingDataOnAppear: Bool = false - private let addDomain: (_ hasDomainCredit: Bool, _ freeStagingDomain: String?) -> Void - private let onClose: () -> Void - - init(viewModel: DomainSettingsViewModel, - addDomain: @escaping (Bool, String?) -> Void, - onClose: @escaping () -> Void) { - self.viewModel = viewModel - self.addDomain = addDomain - self.onClose = onClose - } - - var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: Layout.contentSpacing) { - if let freeDomain = viewModel.freeStagingDomain { - HStack { - FreeStagingDomainView(domain: freeDomain) - Spacer() - } - .padding(.horizontal, insets: Layout.defaultHorizontalPadding) - } - - if viewModel.hasDomainCredit { - DomainSettingsDomainCreditView() { - addDomain(viewModel.hasDomainCredit, viewModel.freeStagingDomain?.name) - } - } - - if viewModel.domains.isNotEmpty { - DomainSettingsListView(domains: viewModel.domains) { - addDomain(viewModel.hasDomainCredit, viewModel.freeStagingDomain?.name) - } - } - } - .padding(Layout.contentPadding) - } - .safeAreaInset(edge: .bottom) { - if viewModel.domains.isEmpty { - VStack { - Divider() - .frame(height: Layout.dividerHeight) - .foregroundColor(Color(.separator)) - - VStack(spacing: Layout.bottomContentSpacing) { - Button(Localization.searchDomainButton) { - addDomain(viewModel.hasDomainCredit, viewModel.freeStagingDomain?.name) - } - .buttonStyle(PrimaryButtonStyle()) - - HStack(alignment: .top, spacing: Layout.learnMoreImageAndTextSpacing) { - Image(uiImage: .infoOutlineFootnoteImage) - .foregroundColor(.init(uiColor: .textSubtle)) - LearnMoreAttributedText(format: Localization.learnMoreFormat, - tappableLearnMoreText: Localization.learnMore, - url: URLs.learnMore) - } - } - .padding(Layout.bottomContentPadding) - } - .background(Color(.systemBackground)) - } - } - .redacted(reason: isFetchingDataOnAppear ? .placeholder: []) - .shimmering(active: isFetchingDataOnAppear) - .navigationBarTitle(Localization.title) - .navigationBarTitleDisplayMode(.inline) - .task { - isFetchingDataOnAppear = true - await viewModel.onAppear() - isFetchingDataOnAppear = false - } - .toolbar { - ToolbarItem(placement: .confirmationAction) { - Button(Localization.doneButtonTitle) { - onClose() - } - .buttonStyle(TextButtonStyle()) - } - } - } -} - -private extension DomainSettingsView { - enum Localization { - static let title = NSLocalizedString("Domains", comment: "Navigation bar title of the domain settings screen.") - static let searchDomainButton = NSLocalizedString( - "Search for a Domain", - comment: "Title of the button on the domain settings screen to search for a domain." - ) - static let learnMoreFormat = NSLocalizedString( - "%1$@ about domains and how to take domain-related actions.", - comment: "Learn more format at the bottom of the domain settings screen. " + - "%1$@ is a tappable link like \"Learn more\" that opens a webview for the user to learn more about domains." - ) - static let learnMore = NSLocalizedString( - "Learn more", - comment: "Tappable text at the bottom of the domain settings screen that opens a webview." - ) - static let doneButtonTitle = NSLocalizedString( - "Done", - comment: "Navigation bar button on the domain settings screen to leave the flow." - ) - } - - enum URLs { - static let learnMore: URL = URL(string: "https://wordpress.com/go/tutorials/what-is-a-domain-name")! - } -} - -private extension DomainSettingsView { - enum Layout { - static let dividerHeight: CGFloat = 1 - static let bottomContentPadding: EdgeInsets = .init(top: 10, leading: 16, bottom: 10, trailing: 16) - static let contentPadding: EdgeInsets = .init(top: 39, leading: 0, bottom: 16, trailing: 0) - static let defaultHorizontalPadding: EdgeInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16) - static let contentSpacing: CGFloat = 36 - static let bottomContentSpacing: CGFloat = 16 - static let learnMoreImageAndTextSpacing: CGFloat = 12 - } -} - -#if DEBUG - -import Yosemite -import enum Networking.DotcomError - -/// StoresManager that specifically handles actions for `DomainSettingsView` previews. -final class DomainSettingsViewStores: DefaultStoresManager { - private let domainsResult: Result<[SiteDomain], Error>? - private let sitePlanResult: Result - - init(domainsResult: Result<[SiteDomain], Error>?, - sitePlanResult: Result) { - self.domainsResult = domainsResult - self.sitePlanResult = sitePlanResult - super.init(sessionManager: ServiceLocator.stores.sessionManager) - } - - override func dispatch(_ action: Action) { - if let action = action as? DomainAction { - if case let .loadDomains(_, completion) = action { - if let domainsResult { - completion(domainsResult) - } - } - } else if let action = action as? PaymentAction { - if case let .loadSiteCurrentPlan(_, completion) = action { - completion(sitePlanResult) - } - } - } -} - -struct DomainSettingsView_Previews: PreviewProvider { - static var previews: some View { - Group { - NavigationView { - DomainSettingsView(viewModel: - .init(siteID: 134, - stores: DomainSettingsViewStores( - // There is one free domain and two paid domains. - domainsResult: .success([ - .init(name: "free.test", isPrimary: true, isWPCOMStagingDomain: true, type: .wpcom), - .init(name: "one.test", isPrimary: false, isWPCOMStagingDomain: false, type: .mapping, renewalDate: .distantFuture), - .init(name: "duo.test", isPrimary: true, isWPCOMStagingDomain: false, type: .mapping, renewalDate: .now) - ]), - // The site has domain credit. - sitePlanResult: .success(.init(hasDomainCredit: true)))), - addDomain: { _, _ in }, - onClose: {}) - } - - NavigationView { - DomainSettingsView(viewModel: - .init(siteID: 134, - stores: DomainSettingsViewStores( - // There is one free domain and no other paid domains. - domainsResult: .success([ - .init(name: "free.test", isPrimary: true, isWPCOMStagingDomain: true, type: .wpcom) - ]), - sitePlanResult: .success(.init(hasDomainCredit: true)))), - addDomain: { _, _ in }, - onClose: {}) - } - - // Loading state. - NavigationView { - DomainSettingsView(viewModel: - .init(siteID: 134, - stores: DomainSettingsViewStores( - // No domains are returned to simulate loading state. - domainsResult: nil, - sitePlanResult: .success(.init(hasDomainCredit: true)))), - addDomain: { _, _ in }, - onClose: {}) - } - } - } -} - -#endif diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsViewModel.swift deleted file mode 100644 index 0a3e3a4579a..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSettingsViewModel.swift +++ /dev/null @@ -1,90 +0,0 @@ -import Foundation -import Yosemite - -/// View model for `DomainSettingsView`. -final class DomainSettingsViewModel: ObservableObject { - struct Domain: Equatable { - /// Whether the domain is the site's primary domain. - let isPrimary: Bool - - /// The address of the domain. - let name: String - - // The next renewal date. - let autoRenewalDate: Date? - } - - struct FreeStagingDomain: Equatable { - /// Whether the domain is the site's primary domain. - let isPrimary: Bool - - /// The address of the domain. - let name: String - } - - @Published private(set) var hasDomainCredit: Bool = false - @Published private(set) var domains: [Domain] = [] - @Published private(set) var freeStagingDomain: FreeStagingDomain? - - private let siteID: Int64 - private let stores: StoresManager - - init(siteID: Int64, stores: StoresManager = ServiceLocator.stores) { - self.siteID = siteID - self.stores = stores - } - - @MainActor - func onAppear() async { - async let domainsResult = loadDomains() - async let siteCurrentPlanResult = loadSiteCurrentPlan() - handleDomainsResult(await domainsResult) - handleSiteCurrentPlanResult(await siteCurrentPlanResult) - } -} - -private extension DomainSettingsViewModel { - @MainActor - func loadDomains() async -> Result<[SiteDomain], Error> { - await withCheckedContinuation { continuation in - stores.dispatch(DomainAction.loadDomains(siteID: siteID) { result in - continuation.resume(returning: result) - }) - } - } - - @MainActor - func loadSiteCurrentPlan() async -> Result { - await withCheckedContinuation { continuation in - stores.dispatch(PaymentAction.loadSiteCurrentPlan(siteID: siteID) { result in - continuation.resume(returning: result) - }) - } - } -} - -private extension DomainSettingsViewModel { - @MainActor - func handleDomainsResult(_ result: Result<[SiteDomain], Error>) { - switch result { - case .success(let domains): - let stagingOrWPCOMDomain = domains.first(where: { $0.isWPCOMStagingDomain || $0.type == .wpcom }) - freeStagingDomain = stagingOrWPCOMDomain - .map { FreeStagingDomain(isPrimary: $0.isPrimary, name: $0.name) } - self.domains = domains.filter { $0 != stagingOrWPCOMDomain && $0.type != .wpcom } - .map { Domain(isPrimary: $0.isPrimary, name: $0.name, autoRenewalDate: $0.renewalDate) } - case .failure(let error): - DDLogError("⛔️ Error retrieving domains for siteID \(siteID): \(error)") - } - } - - @MainActor - func handleSiteCurrentPlanResult(_ result: Result) { - switch result { - case .success(let sitePlan): - hasDomainCredit = sitePlan.hasDomainCredit - case .failure(let error): - DDLogError("⛔️ Error retrieving site plan for siteID \(siteID): \(error)") - } - } -} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/FreeStagingDomainView.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/FreeStagingDomainView.swift deleted file mode 100644 index 0e127ee4aa3..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/FreeStagingDomainView.swift +++ /dev/null @@ -1,50 +0,0 @@ -import SwiftUI - -/// Shows a site's free staging domain, with an optional badge if the domain is the primary domain. -struct FreeStagingDomainView: View { - let domain: DomainSettingsViewModel.FreeStagingDomain - - var body: some View { - VStack(alignment: .leading, spacing: Layout.contentSpacing) { - VStack(alignment: .leading, spacing: 0) { - Text(Localization.freeDomainTitle) - Text(domain.name) - .bold() - } - if domain.isPrimary { - BadgeView(text: Localization.primaryDomainNotice) - } - } - } -} - -private extension FreeStagingDomainView { - enum Localization { - static let freeDomainTitle = NSLocalizedString( - "Your free store address", - comment: "Title of the free domain view." - ) - static let primaryDomainNotice = NSLocalizedString( - "Primary site address", - comment: "Title for a free domain if the domain is the primary site address." - ) - } -} - -private extension FreeStagingDomainView { - enum Layout { - static let horizontalPadding: CGFloat = 6 - static let verticalPadding: CGFloat = 4 - static let cornerRadius: CGFloat = 8 - static let contentSpacing: CGFloat = 8 - } -} - -struct FreeStagingDomainView_Previews: PreviewProvider { - static var previews: some View { - VStack { - FreeStagingDomainView(domain: .init(isPrimary: true, name: "go.trees")) - FreeStagingDomainView(domain: .init(isPrimary: false, name: "go.trees")) - } - } -} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/SupportButton.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/SupportButton.swift deleted file mode 100644 index bfb5b5f4c7a..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/SupportButton.swift +++ /dev/null @@ -1,33 +0,0 @@ -import SwiftUI - -/// A support button with a help icon that is currently shown in the navigation bar. -struct SupportButton: View { - let onTapped: () -> Void - - var body: some View { - Button { - onTapped() - } label: { - Image(uiImage: .helpOutlineImage) - .renderingMode(.template) - .linkStyle() - } - .accessibilityLabel(Localization.accessibilityLabel) - } -} - -private extension SupportButton { - enum Localization { - static let accessibilityLabel = NSLocalizedString( - "supportButton.accessibilityLabel", - value: "Help & Support", - comment: "Accessibility label for the Help & Support image navigation bar button in the store creation flow." - ) - } -} - -struct SupportButton_Previews: PreviewProvider { - static var previews: some View { - SupportButton(onTapped: {}) - } -} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/WebCheckoutViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/WebCheckoutViewModel.swift deleted file mode 100644 index 9cc38d6bd84..00000000000 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/WebCheckoutViewModel.swift +++ /dev/null @@ -1,73 +0,0 @@ -import WebKit - -/// View model used for the web view controller to check out. -final class WebCheckoutViewModel: AuthenticatedWebViewModel { - // `AuthenticatedWebViewModel` protocol conformance. - let title = Localization.title - let initialURL: URL? - - /// Keeps track of whether the completion has been triggered so that it's only invoked once. - /// There are usually multiple redirects with the same success URL prefix. - private var isComplete: Bool = false - - private let completion: () -> Void - - /// - Parameters: - /// - siteSlug: The slug of the site URL that the web checkout is for. - /// - completion: Invoked when the webview reaches the success URL. - init(siteSlug: String, completion: @escaping () -> Void) { - self.initialURL = URL(string: String(format: Constants.checkoutURLFormat, siteSlug)) - self.completion = completion - } - - func handleDismissal() { - // no-op: dismissal is handled in the close button in the navigation bar. - } - - func handleRedirect(for url: URL?) { - guard let path = url?.absoluteString else { - return - } - handleCompletionIfPossible(path) - } - - func decidePolicy(for navigationURL: URL) async -> WKNavigationActionPolicy { - handleCompletionIfPossible(navigationURL.absoluteString) - return .allow - } -} - -private extension WebCheckoutViewModel { - func handleCompletionIfPossible(_ url: String) { - guard url.starts(with: Constants.completionURLPrefix) else { - return - } - // Running on the main thread is necessary if this method is triggered from `decidePolicy`. - DispatchQueue.main.async { [weak self] in - self?.handleSuccess() - } - } - - func handleSuccess() { - guard isComplete == false else { - return - } - completion() - isComplete = true - } -} - -private extension WebCheckoutViewModel { - enum Constants { - static let checkoutURLFormat = "https://wordpress.com/checkout/%@" - static let completionURLPrefix = "https://wordpress.com/checkout/thank-you/" - } - - enum Localization { - static let title = NSLocalizedString( - "webCheckoutViewModel.title", - value: "Checkout", - comment: "Title of the WPCOM checkout web view." - ) - } -} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Settings/SettingsViewController.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Settings/SettingsViewController.swift index b831845af31..1ffdfa9169c 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Settings/SettingsViewController.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Settings/SettingsViewController.swift @@ -29,8 +29,6 @@ final class SettingsViewController: UIViewController { /// private var storePickerCoordinator: StorePickerCoordinator? - private var domainSettingsCoordinator: DomainSettingsCoordinator? - private lazy var closeAccountCoordinator: CloseAccountCoordinator = CloseAccountCoordinator(sourceViewController: self) { [weak self] in guard let self = self else { throw CloseAccountError.presenterDeallocated } diff --git a/WooCommerce/Resources/Images.xcassets/domain-credit.imageset/Contents.json b/WooCommerce/Resources/Images.xcassets/domain-credit.imageset/Contents.json deleted file mode 100644 index ed0b5cb0407..00000000000 --- a/WooCommerce/Resources/Images.xcassets/domain-credit.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "domain-credit.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true - } -} diff --git a/WooCommerce/Resources/Images.xcassets/domain-credit.imageset/domain-credit.pdf b/WooCommerce/Resources/Images.xcassets/domain-credit.imageset/domain-credit.pdf deleted file mode 100644 index b14d99ac96b..00000000000 Binary files a/WooCommerce/Resources/Images.xcassets/domain-credit.imageset/domain-credit.pdf and /dev/null differ diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 07969adb114..e6877bc85c3 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -102,14 +102,10 @@ 02055B142D5DAB6400E51059 /* POSCornerRadiusStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02055B132D5DAB6400E51059 /* POSCornerRadiusStyle.swift */; }; 020564982D5DC96600E51059 /* POSShadowStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020564972D5DC96600E51059 /* POSShadowStyle.swift */; }; 0206483A23FA4160008441BB /* OrdersRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0206483923FA4160008441BB /* OrdersRootViewController.swift */; }; - 0206E296299CD2C900C061C1 /* WooAnalyticsEvent+DomainSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0206E295299CD2C900C061C1 /* WooAnalyticsEvent+DomainSettings.swift */; }; - 020732042988AB7B000A53C2 /* DomainContactInfoForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020732032988AB7B000A53C2 /* DomainContactInfoForm.swift */; }; - 020732062988AC4D000A53C2 /* DomainContactInfoFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020732052988AC4D000A53C2 /* DomainContactInfoFormViewModel.swift */; }; 02077F72253816FF005A78EF /* ProductFormActionsFactory+ReadonlyProductTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02077F71253816FF005A78EF /* ProductFormActionsFactory+ReadonlyProductTests.swift */; }; 020886572499E643001D784E /* ProductExternalLinkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020886562499E642001D784E /* ProductExternalLinkViewController.swift */; }; 020A55F127F6C605007843F0 /* CardReaderConnectionAnalyticsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020A55F027F6C605007843F0 /* CardReaderConnectionAnalyticsTracker.swift */; }; 020ACF88299A809000B3638B /* LearnMoreAttributedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020ACF87299A809000B3638B /* LearnMoreAttributedText.swift */; }; - 020ACF8A299B746700B3638B /* DomainContactInfoFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020ACF89299B746700B3638B /* DomainContactInfoFormViewModelTests.swift */; }; 020B2F8F23BD9F1F00BD79AD /* IntegerInputFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020B2F8E23BD9F1F00BD79AD /* IntegerInputFormatter.swift */; }; 020B2F9123BDD71500BD79AD /* IntegerInputFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020B2F9023BDD71500BD79AD /* IntegerInputFormatterTests.swift */; }; 020B640F29C194CF001C5BD9 /* StoreOnboardingStoreLaunchedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020B640E29C194CF001C5BD9 /* StoreOnboardingStoreLaunchedView.swift */; }; @@ -201,8 +197,6 @@ 022BF7FD23B9D708000A1DFB /* InProgressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022BF7FB23B9D708000A1DFB /* InProgressViewController.swift */; }; 022BF7FE23B9D708000A1DFB /* InProgressViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 022BF7FC23B9D708000A1DFB /* InProgressViewController.xib */; }; 022C658C2863BBAC00EC35A9 /* ProductFormViewController+ProductImageUploaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022C658B2863BBAC00EC35A9 /* ProductFormViewController+ProductImageUploaderTests.swift */; }; - 022C7D7729793ABE0036568D /* PaidDomainSelectorDataProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022C7D7629793ABE0036568D /* PaidDomainSelectorDataProviderTests.swift */; }; - 022C7D792979778E0036568D /* DomainSettingsDomainCreditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022C7D782979778E0036568D /* DomainSettingsDomainCreditView.swift */; }; 022CE91A29BB143000F210E0 /* ProductSelectorNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022CE91929BB143000F210E0 /* ProductSelectorNavigationView.swift */; }; 022F7A0324A05F6400012601 /* LinkedProductsListSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022F7A0124A05F6400012601 /* LinkedProductsListSelectorViewController.swift */; }; 022F7A0424A05F6400012601 /* LinkedProductsListSelectorViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 022F7A0224A05F6400012601 /* LinkedProductsListSelectorViewController.xib */; }; @@ -220,7 +214,6 @@ 02335E492D13BA42000B6ECE /* AsyncPaginationTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02335E482D13BA42000B6ECE /* AsyncPaginationTracker.swift */; }; 023453F22579DA1A00A6BB20 /* ShippingLabelPrintingInstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023453F12579DA1A00A6BB20 /* ShippingLabelPrintingInstructionsViewController.swift */; }; 0234680A282CEA5F00CFC503 /* LegacyReceiptViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02346809282CEA5F00CFC503 /* LegacyReceiptViewModelTests.swift */; }; - 0235354E2999D17A00BF77D3 /* DomainSettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0235354D2999D17A00BF77D3 /* DomainSettingsViewModelTests.swift */; }; 0235595024496853004BE2B8 /* BottomSheetListSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0235594E24496853004BE2B8 /* BottomSheetListSelectorViewController.swift */; }; 0235595124496853004BE2B8 /* BottomSheetListSelectorViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0235594F24496853004BE2B8 /* BottomSheetListSelectorViewController.xib */; }; 0235595324496A93004BE2B8 /* BottomSheetListSelectorViewProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0235595224496A93004BE2B8 /* BottomSheetListSelectorViewProperties.swift */; }; @@ -230,8 +223,6 @@ 0235BFD9246E959500778909 /* ProductFormActionsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0235BFD8246E959500778909 /* ProductFormActionsFactory.swift */; }; 0235BFDB246E99A700778909 /* ProductFormActionsFactory+NonEmptyBottomSheetActionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0235BFDA246E99A700778909 /* ProductFormActionsFactory+NonEmptyBottomSheetActionsTests.swift */; }; 0236BCA425087B660043EB43 /* ProductFormRemoteActionUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0236BCA325087B660043EB43 /* ProductFormRemoteActionUseCaseTests.swift */; }; - 023930612918F36400B2632F /* DomainSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023930602918F36400B2632F /* DomainSelectorView.swift */; }; - 02393069291A065000B2632F /* DomainRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02393068291A065000B2632F /* DomainRowView.swift */; }; 023A059A24135F2600E3FC99 /* ReviewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023A059824135F2600E3FC99 /* ReviewsViewController.swift */; }; 023A059B24135F2600E3FC99 /* ReviewsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 023A059924135F2600E3FC99 /* ReviewsViewController.xib */; }; 023BD5842BFDCBF800A10D7B /* BetaFeaturesConfigurationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023BD5832BFDCBF800A10D7B /* BetaFeaturesConfigurationViewModel.swift */; }; @@ -292,7 +283,6 @@ 02524A5D252ED5C60033E7BD /* ProductVariationLoadUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02524A5C252ED5C60033E7BD /* ProductVariationLoadUseCaseTests.swift */; }; 02535CBB25823F7A00E137BB /* ShippingLabelPaperSize+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02535CBA25823F7A00E137BB /* ShippingLabelPaperSize+UI.swift */; }; 02562AD0296D1FD100980404 /* View+DividerStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02562ACF296D1FD100980404 /* View+DividerStyle.swift */; }; - 02562AD2296D293D00980404 /* DomainSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02562AD1296D293D00980404 /* DomainSettingsCoordinator.swift */; }; 02564A88246C047C00D6DB2A /* Optional+StringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02564A87246C047C00D6DB2A /* Optional+StringTests.swift */; }; 02564A8A246CDF6100D6DB2A /* ProductsTopBannerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02564A89246CDF6100D6DB2A /* ProductsTopBannerFactory.swift */; }; 02564A8C246CE38E00D6DB2A /* SwappableSubviewContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02564A8B246CE38E00D6DB2A /* SwappableSubviewContainerView.swift */; }; @@ -421,7 +411,6 @@ 027F83ED29B046D2002688C6 /* TopPerformersPeriodViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027F83EC29B046D2002688C6 /* TopPerformersPeriodViewModel.swift */; }; 027F83EF29B048E2002688C6 /* TopPerformersPeriodViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027F83EE29B048E2002688C6 /* TopPerformersPeriodViewModelTests.swift */; }; 02817B39242B34560050AD8B /* ToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02817B38242B34560050AD8B /* ToolbarView.swift */; }; - 028203CF297662A200217369 /* DomainSelectorDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028203CE297662A200217369 /* DomainSelectorDataProvider.swift */; }; 02820F3422C257B700DE0D37 /* UITableView+HeaderFooterHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02820F3322C257B700DE0D37 /* UITableView+HeaderFooterHelpers.swift */; }; 028296EC237D28B600E84012 /* TextViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028296EA237D28B600E84012 /* TextViewViewController.swift */; }; 028296ED237D28B600E84012 /* TextViewViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 028296EB237D28B600E84012 /* TextViewViewController.xib */; }; @@ -517,7 +506,6 @@ 02B2AD8D2CD0A89800929CE8 /* POSModalSizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B2AD8C2CD0A87B00929CE8 /* POSModalSizing.swift */; }; 02B2C831249C4C8D0040C83C /* TextFieldTextAlignmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B2C830249C4C8D0040C83C /* TextFieldTextAlignmentTests.swift */; }; 02B334A12BEB712600A46774 /* CollapsibleCustomerCardAddressViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B334A02BEB712600A46774 /* CollapsibleCustomerCardAddressViewModel.swift */; }; - 02B41A96296D09D100FE3311 /* DomainSettingsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B41A95296D09D100FE3311 /* DomainSettingsListView.swift */; }; 02B60DFB2A58809F004C47FF /* View+MediaSourceActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B60DFA2A58809F004C47FF /* View+MediaSourceActionSheet.swift */; }; 02B653AC2429F7BF00A9C839 /* MockTaxClassStoresManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B653AB2429F7BF00A9C839 /* MockTaxClassStoresManager.swift */; }; 02B7C4F62BE375D800F8E93A /* CollapsibleCustomerCardHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B7C4F52BE375D800F8E93A /* CollapsibleCustomerCardHeaderView.swift */; }; @@ -555,8 +543,6 @@ 02C2756F24F5F5EE00286C04 /* ProductShippingSettingsViewModel+ProductVariationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C2756E24F5F5EE00286C04 /* ProductShippingSettingsViewModel+ProductVariationTests.swift */; }; 02C27BCE282CB52F0065471A /* CardPresentPaymentReceiptEmailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C27BCD282CB52F0065471A /* CardPresentPaymentReceiptEmailCoordinator.swift */; }; 02C27BD0282CDF9E0065471A /* CardPresentPaymentReceiptEmailCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C27BCF282CDF9E0065471A /* CardPresentPaymentReceiptEmailCoordinatorTests.swift */; }; - 02C37B7B2967096800F0CF9E /* DomainSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C37B7A2967096800F0CF9E /* DomainSettingsView.swift */; }; - 02C37B7D2967B72A00F0CF9E /* FreeStagingDomainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C37B7C2967B72A00F0CF9E /* FreeStagingDomainView.swift */; }; 02C3FACE282A93020095440A /* WooAnalyticsEvent+Dashboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C3FACD282A93020095440A /* WooAnalyticsEvent+Dashboard.swift */; }; 02C3FDEA251091CE009569EE /* ProductFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C3FDE9251091CE009569EE /* ProductFactoryTests.swift */; }; 02C7EE8A2B21B951008B7DF8 /* ProductWithQuantityStepperViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C7EE892B21B951008B7DF8 /* ProductWithQuantityStepperViewModel.swift */; }; @@ -589,14 +575,11 @@ 02D681A929C358BA00348510 /* StoreOnboardingPaymentsSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D681A829C358BA00348510 /* StoreOnboardingPaymentsSetupView.swift */; }; 02D681AB29C3F8AC00348510 /* StoreOnboardingPaymentsSetupCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D681AA29C3F8AC00348510 /* StoreOnboardingPaymentsSetupCoordinator.swift */; }; 02D9EFCB2B69F91B00AE8968 /* ProductsSplitViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D9EFCA2B69F91B00AE8968 /* ProductsSplitViewCoordinator.swift */; }; - 02DAE7FC291B7B8B009342B7 /* DomainSelectorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DAE7FB291B7B8B009342B7 /* DomainSelectorViewModel.swift */; }; - 02DAE7FF291B8C8A009342B7 /* FreeDomainSelectorViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DAE7FE291B8C8A009342B7 /* FreeDomainSelectorViewModelTests.swift */; }; 02DC2ED2242061BF002F9676 /* ProductPriceSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DC2ED1242061BE002F9676 /* ProductPriceSettingsViewModel.swift */; }; 02DD81F9242CAA400060E50B /* WordPressMediaLibraryPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DD81F5242CAA3F0060E50B /* WordPressMediaLibraryPickerViewController.swift */; }; 02DD81FA242CAA400060E50B /* Media+WPMediaAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DD81F6242CAA3F0060E50B /* Media+WPMediaAsset.swift */; }; 02DD81FB242CAA400060E50B /* WordPressMediaLibraryPickerDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DD81F7242CAA3F0060E50B /* WordPressMediaLibraryPickerDataSource.swift */; }; 02DD81FC242CAA400060E50B /* WordPressMediaLibraryImagePickerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 02DD81F8242CAA400060E50B /* WordPressMediaLibraryImagePickerViewController.xib */; }; - 02DE39D92968647100BB31D4 /* DomainSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE39D82968647100BB31D4 /* DomainSettingsViewModel.swift */; }; 02DEA23328810B7A0057FC40 /* LoginOnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DEA23228810B7A0057FC40 /* LoginOnboardingScreen.swift */; }; 02DF174B2A4A134B008FD33B /* ProductFormActionsFactory+ProductCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DF174A2A4A134B008FD33B /* ProductFormActionsFactory+ProductCreationTests.swift */; }; 02DFD5042B20486C0048CD70 /* ProductStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DFD5032B20486C0048CD70 /* ProductStepper.swift */; }; @@ -640,7 +623,6 @@ 02EEB5C52424AFAA00B8A701 /* TextFieldTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 02EEB5C32424AFAA00B8A701 /* TextFieldTableViewCell.xib */; }; 02EFF81A2ABC28BA0015ABB2 /* GiftCardInputViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EFF8192ABC28BA0015ABB2 /* GiftCardInputViewModelTests.swift */; }; 02F1E6BD2A39805C00C3E4C7 /* ProductDescriptionAICoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F1E6BC2A39805C00C3E4C7 /* ProductDescriptionAICoordinatorTests.swift */; }; - 02F36F472978349500D97EA0 /* DomainPurchaseSuccessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F36F462978349500D97EA0 /* DomainPurchaseSuccessView.swift */; }; 02F3884C2D6C38BB00619396 /* POSErrorAndAlertIconSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F3884B2D6C38BB00619396 /* POSErrorAndAlertIconSize.swift */; }; 02F3A6842A618CD7004CD2E8 /* WordPressMediaLibraryPickerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F3A6832A618CD7004CD2E8 /* WordPressMediaLibraryPickerCoordinator.swift */; }; 02F3A6862A619270004CD2E8 /* WordPressMediaLibraryImagePickerCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F3A6852A619270004CD2E8 /* WordPressMediaLibraryImagePickerCoordinatorTests.swift */; }; @@ -1213,8 +1195,8 @@ 26FE09E124DB8FA000B9BDF5 /* SurveyCoordinatorControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FE09E024DB8FA000B9BDF5 /* SurveyCoordinatorControllerTests.swift */; }; 26FFC50C2BED7C5A0067B3A4 /* WatchDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B249702BEC801400730730 /* WatchDependencies.swift */; }; 26FFC50D2BED7C5B0067B3A4 /* WatchDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B249702BEC801400730730 /* WatchDependencies.swift */; }; - 2D88C1112DF883C300A6FB2C /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D88C1102DF883BD00A6FB2C /* AttributedString+Helpers.swift */; }; 2D880B492DFB2F3F00A6FB2C /* OptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D880B482DFB2F3D00A6FB2C /* OptionalBinding.swift */; }; + 2D88C1112DF883C300A6FB2C /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D88C1102DF883BD00A6FB2C /* AttributedString+Helpers.swift */; }; 310D1B482734919E001D55B4 /* InPersonPaymentsLiveSiteInTestModeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310D1B472734919E001D55B4 /* InPersonPaymentsLiveSiteInTestModeView.swift */; }; 311237EE2714DA240033C44E /* CardPresentModalDisplayMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311237ED2714DA240033C44E /* CardPresentModalDisplayMessage.swift */; }; 311D21E8264AEDB900102316 /* CardPresentModalScanningForReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311D21E7264AEDB900102316 /* CardPresentModalScanningForReader.swift */; }; @@ -2723,8 +2705,6 @@ DE63115B2AF1E13200587641 /* WPComLoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE63115A2AF1E13200587641 /* WPComLoginCoordinator.swift */; }; DE653EAD2A70D09F00937C78 /* CreateTestOrderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE653EAC2A70D09F00937C78 /* CreateTestOrderView.swift */; }; DE653EAF2A72577500937C78 /* WooAnalyticsEvent+TestOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE653EAE2A72577500937C78 /* WooAnalyticsEvent+TestOrder.swift */; }; - DE65C1F72C48E7DC003EF8D1 /* SupportButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE65C1F62C48E7DC003EF8D1 /* SupportButton.swift */; }; - DE65C1F92C48E89C003EF8D1 /* WebCheckoutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE65C1F82C48E89C003EF8D1 /* WebCheckoutViewModel.swift */; }; DE6627E42DCC503E0068E12E /* WooShippingRefundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6627E32DCC503E0068E12E /* WooShippingRefundView.swift */; }; DE6627E62DCC52230068E12E /* WooShippingRefundViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6627E52DCC52180068E12E /* WooShippingRefundViewModel.swift */; }; DE6627E92DCCBD2D0068E12E /* ShippingLabel+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6627E82DCCBD280068E12E /* ShippingLabel+Helpers.swift */; }; @@ -3363,14 +3343,10 @@ 02055B132D5DAB6400E51059 /* POSCornerRadiusStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSCornerRadiusStyle.swift; sourceTree = ""; }; 020564972D5DC96600E51059 /* POSShadowStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSShadowStyle.swift; sourceTree = ""; }; 0206483923FA4160008441BB /* OrdersRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrdersRootViewController.swift; sourceTree = ""; }; - 0206E295299CD2C900C061C1 /* WooAnalyticsEvent+DomainSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooAnalyticsEvent+DomainSettings.swift"; sourceTree = ""; }; - 020732032988AB7B000A53C2 /* DomainContactInfoForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainContactInfoForm.swift; sourceTree = ""; }; - 020732052988AC4D000A53C2 /* DomainContactInfoFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainContactInfoFormViewModel.swift; sourceTree = ""; }; 02077F71253816FF005A78EF /* ProductFormActionsFactory+ReadonlyProductTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductFormActionsFactory+ReadonlyProductTests.swift"; sourceTree = ""; }; 020886562499E642001D784E /* ProductExternalLinkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductExternalLinkViewController.swift; sourceTree = ""; }; 020A55F027F6C605007843F0 /* CardReaderConnectionAnalyticsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderConnectionAnalyticsTracker.swift; sourceTree = ""; }; 020ACF87299A809000B3638B /* LearnMoreAttributedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearnMoreAttributedText.swift; sourceTree = ""; }; - 020ACF89299B746700B3638B /* DomainContactInfoFormViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainContactInfoFormViewModelTests.swift; sourceTree = ""; }; 020B2F8E23BD9F1F00BD79AD /* IntegerInputFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerInputFormatter.swift; sourceTree = ""; }; 020B2F9023BDD71500BD79AD /* IntegerInputFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerInputFormatterTests.swift; sourceTree = ""; }; 020B640E29C194CF001C5BD9 /* StoreOnboardingStoreLaunchedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreOnboardingStoreLaunchedView.swift; sourceTree = ""; }; @@ -3462,8 +3438,6 @@ 022BF7FB23B9D708000A1DFB /* InProgressViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InProgressViewController.swift; sourceTree = ""; }; 022BF7FC23B9D708000A1DFB /* InProgressViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InProgressViewController.xib; sourceTree = ""; }; 022C658B2863BBAC00EC35A9 /* ProductFormViewController+ProductImageUploaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductFormViewController+ProductImageUploaderTests.swift"; sourceTree = ""; }; - 022C7D7629793ABE0036568D /* PaidDomainSelectorDataProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaidDomainSelectorDataProviderTests.swift; sourceTree = ""; }; - 022C7D782979778E0036568D /* DomainSettingsDomainCreditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainSettingsDomainCreditView.swift; sourceTree = ""; }; 022CE91929BB143000F210E0 /* ProductSelectorNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductSelectorNavigationView.swift; sourceTree = ""; }; 022F7A0124A05F6400012601 /* LinkedProductsListSelectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkedProductsListSelectorViewController.swift; sourceTree = ""; }; 022F7A0224A05F6400012601 /* LinkedProductsListSelectorViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LinkedProductsListSelectorViewController.xib; sourceTree = ""; }; @@ -3481,7 +3455,6 @@ 02335E482D13BA42000B6ECE /* AsyncPaginationTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncPaginationTracker.swift; sourceTree = ""; }; 023453F12579DA1A00A6BB20 /* ShippingLabelPrintingInstructionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelPrintingInstructionsViewController.swift; sourceTree = ""; }; 02346809282CEA5F00CFC503 /* LegacyReceiptViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyReceiptViewModelTests.swift; sourceTree = ""; }; - 0235354D2999D17A00BF77D3 /* DomainSettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainSettingsViewModelTests.swift; sourceTree = ""; }; 0235594E24496853004BE2B8 /* BottomSheetListSelectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetListSelectorViewController.swift; sourceTree = ""; }; 0235594F24496853004BE2B8 /* BottomSheetListSelectorViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BottomSheetListSelectorViewController.xib; sourceTree = ""; }; 0235595224496A93004BE2B8 /* BottomSheetListSelectorViewProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetListSelectorViewProperties.swift; sourceTree = ""; }; @@ -3491,8 +3464,6 @@ 0235BFD8246E959500778909 /* ProductFormActionsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFormActionsFactory.swift; sourceTree = ""; }; 0235BFDA246E99A700778909 /* ProductFormActionsFactory+NonEmptyBottomSheetActionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductFormActionsFactory+NonEmptyBottomSheetActionsTests.swift"; sourceTree = ""; }; 0236BCA325087B660043EB43 /* ProductFormRemoteActionUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFormRemoteActionUseCaseTests.swift; sourceTree = ""; }; - 023930602918F36400B2632F /* DomainSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainSelectorView.swift; sourceTree = ""; }; - 02393068291A065000B2632F /* DomainRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainRowView.swift; sourceTree = ""; }; 023A059824135F2600E3FC99 /* ReviewsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewsViewController.swift; sourceTree = ""; }; 023A059924135F2600E3FC99 /* ReviewsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReviewsViewController.xib; sourceTree = ""; }; 023BD5832BFDCBF800A10D7B /* BetaFeaturesConfigurationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BetaFeaturesConfigurationViewModel.swift; sourceTree = ""; }; @@ -3553,7 +3524,6 @@ 02524A5C252ED5C60033E7BD /* ProductVariationLoadUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductVariationLoadUseCaseTests.swift; sourceTree = ""; }; 02535CBA25823F7A00E137BB /* ShippingLabelPaperSize+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShippingLabelPaperSize+UI.swift"; sourceTree = ""; }; 02562ACF296D1FD100980404 /* View+DividerStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+DividerStyle.swift"; sourceTree = ""; }; - 02562AD1296D293D00980404 /* DomainSettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainSettingsCoordinator.swift; sourceTree = ""; }; 02564A87246C047C00D6DB2A /* Optional+StringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+StringTests.swift"; sourceTree = ""; }; 02564A89246CDF6100D6DB2A /* ProductsTopBannerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsTopBannerFactory.swift; sourceTree = ""; }; 02564A8B246CE38E00D6DB2A /* SwappableSubviewContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwappableSubviewContainerView.swift; sourceTree = ""; }; @@ -3683,7 +3653,6 @@ 027F83EC29B046D2002688C6 /* TopPerformersPeriodViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopPerformersPeriodViewModel.swift; sourceTree = ""; }; 027F83EE29B048E2002688C6 /* TopPerformersPeriodViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopPerformersPeriodViewModelTests.swift; sourceTree = ""; }; 02817B38242B34560050AD8B /* ToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarView.swift; sourceTree = ""; }; - 028203CE297662A200217369 /* DomainSelectorDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainSelectorDataProvider.swift; sourceTree = ""; }; 02820F3322C257B700DE0D37 /* UITableView+HeaderFooterHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+HeaderFooterHelpers.swift"; sourceTree = ""; }; 028296EA237D28B600E84012 /* TextViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewViewController.swift; sourceTree = ""; }; 028296EB237D28B600E84012 /* TextViewViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TextViewViewController.xib; sourceTree = ""; }; @@ -3781,7 +3750,6 @@ 02B2AD8C2CD0A87B00929CE8 /* POSModalSizing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSModalSizing.swift; sourceTree = ""; }; 02B2C830249C4C8D0040C83C /* TextFieldTextAlignmentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldTextAlignmentTests.swift; sourceTree = ""; }; 02B334A02BEB712600A46774 /* CollapsibleCustomerCardAddressViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleCustomerCardAddressViewModel.swift; sourceTree = ""; }; - 02B41A95296D09D100FE3311 /* DomainSettingsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainSettingsListView.swift; sourceTree = ""; }; 02B60DFA2A58809F004C47FF /* View+MediaSourceActionSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+MediaSourceActionSheet.swift"; sourceTree = ""; }; 02B653AB2429F7BF00A9C839 /* MockTaxClassStoresManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTaxClassStoresManager.swift; sourceTree = ""; }; 02B7C4F52BE375D800F8E93A /* CollapsibleCustomerCardHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleCustomerCardHeaderView.swift; sourceTree = ""; }; @@ -3819,8 +3787,6 @@ 02C2756E24F5F5EE00286C04 /* ProductShippingSettingsViewModel+ProductVariationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductShippingSettingsViewModel+ProductVariationTests.swift"; sourceTree = ""; }; 02C27BCD282CB52F0065471A /* CardPresentPaymentReceiptEmailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentPaymentReceiptEmailCoordinator.swift; sourceTree = ""; }; 02C27BCF282CDF9E0065471A /* CardPresentPaymentReceiptEmailCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentPaymentReceiptEmailCoordinatorTests.swift; sourceTree = ""; }; - 02C37B7A2967096800F0CF9E /* DomainSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainSettingsView.swift; sourceTree = ""; }; - 02C37B7C2967B72A00F0CF9E /* FreeStagingDomainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreeStagingDomainView.swift; sourceTree = ""; }; 02C3FACD282A93020095440A /* WooAnalyticsEvent+Dashboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooAnalyticsEvent+Dashboard.swift"; sourceTree = ""; }; 02C3FDE9251091CE009569EE /* ProductFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFactoryTests.swift; sourceTree = ""; }; 02C7EE892B21B951008B7DF8 /* ProductWithQuantityStepperViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductWithQuantityStepperViewModel.swift; sourceTree = ""; }; @@ -3853,14 +3819,11 @@ 02D681A829C358BA00348510 /* StoreOnboardingPaymentsSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreOnboardingPaymentsSetupView.swift; sourceTree = ""; }; 02D681AA29C3F8AC00348510 /* StoreOnboardingPaymentsSetupCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreOnboardingPaymentsSetupCoordinator.swift; sourceTree = ""; }; 02D9EFCA2B69F91B00AE8968 /* ProductsSplitViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsSplitViewCoordinator.swift; sourceTree = ""; }; - 02DAE7FB291B7B8B009342B7 /* DomainSelectorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainSelectorViewModel.swift; sourceTree = ""; }; - 02DAE7FE291B8C8A009342B7 /* FreeDomainSelectorViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreeDomainSelectorViewModelTests.swift; sourceTree = ""; }; 02DC2ED1242061BE002F9676 /* ProductPriceSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductPriceSettingsViewModel.swift; sourceTree = ""; }; 02DD81F5242CAA3F0060E50B /* WordPressMediaLibraryPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordPressMediaLibraryPickerViewController.swift; sourceTree = ""; }; 02DD81F6242CAA3F0060E50B /* Media+WPMediaAsset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Media+WPMediaAsset.swift"; sourceTree = ""; }; 02DD81F7242CAA3F0060E50B /* WordPressMediaLibraryPickerDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordPressMediaLibraryPickerDataSource.swift; sourceTree = ""; }; 02DD81F8242CAA400060E50B /* WordPressMediaLibraryImagePickerViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WordPressMediaLibraryImagePickerViewController.xib; sourceTree = ""; }; - 02DE39D82968647100BB31D4 /* DomainSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainSettingsViewModel.swift; sourceTree = ""; }; 02DEA23228810B7A0057FC40 /* LoginOnboardingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginOnboardingScreen.swift; sourceTree = ""; }; 02DF174A2A4A134B008FD33B /* ProductFormActionsFactory+ProductCreationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductFormActionsFactory+ProductCreationTests.swift"; sourceTree = ""; }; 02DFD5032B20486C0048CD70 /* ProductStepper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductStepper.swift; sourceTree = ""; }; @@ -3904,7 +3867,6 @@ 02EEB5C32424AFAA00B8A701 /* TextFieldTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TextFieldTableViewCell.xib; sourceTree = ""; }; 02EFF8192ABC28BA0015ABB2 /* GiftCardInputViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GiftCardInputViewModelTests.swift; sourceTree = ""; }; 02F1E6BC2A39805C00C3E4C7 /* ProductDescriptionAICoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDescriptionAICoordinatorTests.swift; sourceTree = ""; }; - 02F36F462978349500D97EA0 /* DomainPurchaseSuccessView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainPurchaseSuccessView.swift; sourceTree = ""; }; 02F3884B2D6C38BB00619396 /* POSErrorAndAlertIconSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSErrorAndAlertIconSize.swift; sourceTree = ""; }; 02F3A6832A618CD7004CD2E8 /* WordPressMediaLibraryPickerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressMediaLibraryPickerCoordinator.swift; sourceTree = ""; }; 02F3A6852A619270004CD2E8 /* WordPressMediaLibraryImagePickerCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressMediaLibraryImagePickerCoordinatorTests.swift; sourceTree = ""; }; @@ -4447,8 +4409,8 @@ 26FE09E024DB8FA000B9BDF5 /* SurveyCoordinatorControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyCoordinatorControllerTests.swift; sourceTree = ""; }; 26FFD32628C6A0A4002E5E5E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 26FFD32928C6A0F4002E5E5E /* UIImage+Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Widgets.swift"; sourceTree = ""; }; - 2D88C1102DF883BD00A6FB2C /* AttributedString+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Helpers.swift"; sourceTree = ""; }; 2D880B482DFB2F3D00A6FB2C /* OptionalBinding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalBinding.swift; sourceTree = ""; }; + 2D88C1102DF883BD00A6FB2C /* AttributedString+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Helpers.swift"; sourceTree = ""; }; 310D1B472734919E001D55B4 /* InPersonPaymentsLiveSiteInTestModeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsLiveSiteInTestModeView.swift; sourceTree = ""; }; 311237ED2714DA240033C44E /* CardPresentModalDisplayMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalDisplayMessage.swift; sourceTree = ""; }; 311D21E7264AEDB900102316 /* CardPresentModalScanningForReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalScanningForReader.swift; sourceTree = ""; }; @@ -5970,8 +5932,6 @@ DE63115A2AF1E13200587641 /* WPComLoginCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WPComLoginCoordinator.swift; sourceTree = ""; }; DE653EAC2A70D09F00937C78 /* CreateTestOrderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateTestOrderView.swift; sourceTree = ""; }; DE653EAE2A72577500937C78 /* WooAnalyticsEvent+TestOrder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooAnalyticsEvent+TestOrder.swift"; sourceTree = ""; }; - DE65C1F62C48E7DC003EF8D1 /* SupportButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportButton.swift; sourceTree = ""; }; - DE65C1F82C48E89C003EF8D1 /* WebCheckoutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebCheckoutViewModel.swift; sourceTree = ""; }; DE6627E32DCC503E0068E12E /* WooShippingRefundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooShippingRefundView.swift; sourceTree = ""; }; DE6627E52DCC52180068E12E /* WooShippingRefundViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooShippingRefundViewModel.swift; sourceTree = ""; }; DE6627E82DCCBD280068E12E /* ShippingLabel+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShippingLabel+Helpers.swift"; sourceTree = ""; }; @@ -6960,28 +6920,6 @@ path = BottomSheetListSelector; sourceTree = ""; }; - 0239305F2918F35600B2632F /* Domains */ = { - isa = PBXGroup; - children = ( - 023930602918F36400B2632F /* DomainSelectorView.swift */, - 02393068291A065000B2632F /* DomainRowView.swift */, - 02DAE7FB291B7B8B009342B7 /* DomainSelectorViewModel.swift */, - 02C37B7A2967096800F0CF9E /* DomainSettingsView.swift */, - 02C37B7C2967B72A00F0CF9E /* FreeStagingDomainView.swift */, - 02DE39D82968647100BB31D4 /* DomainSettingsViewModel.swift */, - 02B41A95296D09D100FE3311 /* DomainSettingsListView.swift */, - 02562AD1296D293D00980404 /* DomainSettingsCoordinator.swift */, - 028203CE297662A200217369 /* DomainSelectorDataProvider.swift */, - 02F36F462978349500D97EA0 /* DomainPurchaseSuccessView.swift */, - 022C7D782979778E0036568D /* DomainSettingsDomainCreditView.swift */, - 020732032988AB7B000A53C2 /* DomainContactInfoForm.swift */, - 020732052988AC4D000A53C2 /* DomainContactInfoFormViewModel.swift */, - DE65C1F62C48E7DC003EF8D1 /* SupportButton.swift */, - DE65C1F82C48E89C003EF8D1 /* WebCheckoutViewModel.swift */, - ); - path = Domains; - sourceTree = ""; - }; 023BD5892BFDCF9500A10D7B /* POS */ = { isa = PBXGroup; children = ( @@ -7896,17 +7834,6 @@ path = "Beta features"; sourceTree = ""; }; - 02DAE7FD291B8C7C009342B7 /* Domains */ = { - isa = PBXGroup; - children = ( - 02DAE7FE291B8C8A009342B7 /* FreeDomainSelectorViewModelTests.swift */, - 022C7D7629793ABE0036568D /* PaidDomainSelectorDataProviderTests.swift */, - 0235354D2999D17A00BF77D3 /* DomainSettingsViewModelTests.swift */, - 020ACF89299B746700B3638B /* DomainContactInfoFormViewModelTests.swift */, - ); - path = Domains; - sourceTree = ""; - }; 02DFECE525EE33430070F212 /* Create Shipping Label Info */ = { isa = PBXGroup; children = ( @@ -9832,7 +9759,6 @@ B993051B2B7CC27F00456E35 /* Order Details */, 6850C5F22B6A11AE0026A93B /* Receipts */, 027111402913B9D400F5269A /* Authentication */, - 02DAE7FD291B8C7C009342B7 /* Domains */, D41C9F2F26D9A41F00993558 /* WhatsNew */, D8025469265517F9001B2CC1 /* CardPresentPayments */, 0371C36F2876ED3A00277E2C /* Feature Announcement Cards */, @@ -10215,7 +10141,6 @@ CEC8188B2A3B7C8B00459843 /* AppStartupWaitingTimeTracker.swift */, 0263E3BA290BB21800E5F88F /* WooAnalyticsEvent+StoreCreation.swift */, EE57C11E297E742200BC31E7 /* WooAnalyticsEvent+ApplicationPassword.swift */, - 0206E295299CD2C900C061C1 /* WooAnalyticsEvent+DomainSettings.swift */, B946881929BA2AC6000646B0 /* WooAnalyticsEvent+Spotlight.swift */, B9F3DAB029BB83E600DDD545 /* WooAnalyticsEvent+AppIntents.swift */, 02B21C5629C9EEF900C5623B /* WooAnalyticsEvent+StoreOnboarding.swift */, @@ -12581,7 +12506,6 @@ DEDA8DBA2B19833E0076BF0F /* Themes */, DE68979D2A8F7C8C00154588 /* AccountSettings */, 02ACD2592852E11700EC928E /* CloseAccountCoordinator.swift */, - 0239305F2918F35600B2632F /* Domains */, BAF1B3B32736595A00BA11DC /* Settings */, DE8C9464264698E800C94823 /* Plugins */, 74C6FEA321C2F189009286B6 /* About */, @@ -15446,7 +15370,6 @@ 01F42C182CE34AD2003D0A5A /* CardPresentModalSuccessEmailSent.swift in Sources */, 025FA38B2522CB4D0054CA57 /* AppCoordinator.swift in Sources */, 02CEBB8024C9869E002EDF35 /* ProductFormActionsFactoryProtocol.swift in Sources */, - 022C7D792979778E0036568D /* DomainSettingsDomainCreditView.swift in Sources */, DE0CDC942B1ECFF000C98800 /* LocalFileUploader.swift in Sources */, CE21B3E020FFC59700A259D5 /* ProductDetailsTableViewCell.swift in Sources */, B6F3796A29378B3900718561 /* AnalyticsHubMonthToDateRangeData.swift in Sources */, @@ -15591,7 +15514,6 @@ 0230B4D62C33454900F2F660 /* PointOfSaleCardPresentPaymentCaptureErrorMessageView.swift in Sources */, E1BAAEA026BBECEF00F2C037 /* ButtonStyles.swift in Sources */, CC13C0CB278E021300C0B5B5 /* ProductVariationSelectorViewModel.swift in Sources */, - 020732042988AB7B000A53C2 /* DomainContactInfoForm.swift in Sources */, 026826AD2BF59DF70036F959 /* PointOfSaleDashboardView.swift in Sources */, DEE215302D113F89004A11F3 /* EditStoreListViewModel.swift in Sources */, DE2FE595292737330018040A /* JetpackSetupView.swift in Sources */, @@ -15668,7 +15590,6 @@ 205E79422C1CA6E3001BA266 /* PointOfSaleCardPresentPaymentEventPresentationStyle.swift in Sources */, EE4C45842C381BAA001A3D94 /* PackagePhotoView.swift in Sources */, B57C743D20F5493300EEFC87 /* AccountHeaderView.swift in Sources */, - 02562AD2296D293D00980404 /* DomainSettingsCoordinator.swift in Sources */, 026826C42BF59E410036F959 /* PointOfSaleCardPresentPaymentFoundReaderView.swift in Sources */, EE8B42142BFB4A8A0077C4E7 /* LastOrderDashboardRow.swift in Sources */, 03E471D2293FA8B2001A58AD /* BluetoothCardReaderPaymentAlertsProvider.swift in Sources */, @@ -15852,7 +15773,6 @@ 68D5094E2AD39BC900B6FFD5 /* DiscountLineDetailsView.swift in Sources */, B98DA0AE2B275F45008A3607 /* ProductLoaderView.swift in Sources */, 02B1AFEC24BC5AE5005DB1E3 /* LinkedProductListSelectorDataSource.swift in Sources */, - 0206E296299CD2C900C061C1 /* WooAnalyticsEvent+DomainSettings.swift in Sources */, 026826AC2BF59DF70036F959 /* SimpleProductCardView.swift in Sources */, DE02C65C2D5A0B9F0089850D /* FailedProductImageCollectionViewCell.swift in Sources */, 26F94E1C267A3E4500DB6CCF /* ProductAddOnsListViewController.swift in Sources */, @@ -15910,7 +15830,6 @@ 205E79512C207FAE001BA266 /* PointOfSaleCardPresentPaymentDisplayReaderMessageMessageView.swift in Sources */, 209E96BD2DB681B50089F3D2 /* KeyboardObserver.swift in Sources */, 03F5CB012A0BA3D40026877A /* ModalOverlay.swift in Sources */, - 02C37B7D2967B72A00F0CF9E /* FreeStagingDomainView.swift in Sources */, 01C9C59F2DA3D98400CD81D8 /* CartRowRemoveButton.swift in Sources */, 457509E4267B9E91005FA2EA /* AggregatedProductListViewController.swift in Sources */, D8815B0D263861A400EDAD62 /* CardPresentModalSuccess.swift in Sources */, @@ -16024,7 +15943,6 @@ 02482A8E237BEAE9007E73ED /* AztecLinkFormatBarCommand.swift in Sources */, 02162729237965E8000208D2 /* ProductFormTableViewModel.swift in Sources */, 68E6749F2A4DA01C0034BA1E /* WooWPComPlan.swift in Sources */, - DE65C1F92C48E89C003EF8D1 /* WebCheckoutViewModel.swift in Sources */, DEF3300C270444070073AE29 /* ShippingLabelSelectedRate.swift in Sources */, CE2A9FBF23BFB1BE002BEC1C /* LedgerTableViewCell.swift in Sources */, 035DBA47292D0995003E5125 /* CardPresentPaymentPreflightController.swift in Sources */, @@ -16045,7 +15963,6 @@ 314DC4BF268D183600444C9E /* CardReaderSettingsKnownReaderStorage.swift in Sources */, CEAB739C2C81E3F600A7EB39 /* WooShippingCreateLabelsView.swift in Sources */, 2662D90826E15D6E00E25611 /* AreaSelectorCommand.swift in Sources */, - 02393069291A065000B2632F /* DomainRowView.swift in Sources */, 20D3D42B2C64D7CC004CE6E3 /* SimpleProductsOnlyInformation.swift in Sources */, 024A543422BA6F8F00F4F38E /* DeveloperEmailChecker.swift in Sources */, 027B8BB823FE0CB30040944E /* DefaultProductUIImageLoader.swift in Sources */, @@ -16323,7 +16240,6 @@ 028E1F702833DD0A001F8829 /* DashboardViewModel.swift in Sources */, 2602A63D27BD3C8C00B347F1 /* RemoteOrderSynchronizer.swift in Sources */, CCFBBCFA29C4C85F0081B595 /* ComponentSettingsViewModel.swift in Sources */, - 02C37B7B2967096800F0CF9E /* DomainSettingsView.swift in Sources */, CC4B252B27CFCEE2008D2E6E /* OrderTotalsCalculator.swift in Sources */, 8625C5112BF20990007F1901 /* ReviewsDashboardCard.swift in Sources */, CE2DF92E2CC16C95001AA394 /* WooShippingServiceCardView.swift in Sources */, @@ -16401,7 +16317,6 @@ D8C2A291231BD0FD00F503E9 /* ReviewsDataSource.swift in Sources */, CE855366209BA6A700938BDC /* CustomerInfoTableViewCell.swift in Sources */, 205E79442C204368001BA266 /* PointOfSaleCardPresentPaymentNonRetryableErrorMessageViewModel.swift in Sources */, - 02DAE7FC291B7B8B009342B7 /* DomainSelectorViewModel.swift in Sources */, 26838356296F702B00CCF60A /* GenerateAllVariationsPresenter.swift in Sources */, 4521396E27FEE55200964ED3 /* FullScreenTextView.swift in Sources */, 453F52A32C3C3474006CBA2F /* ProductCreationAIPromptProgressBar.swift in Sources */, @@ -16489,7 +16404,6 @@ CEDBDA472B6BEF2E002047D4 /* AnalyticsWebReport.swift in Sources */, 314DC4BD268D158F00444C9E /* CardReaderSettingsKnownReadersProvider.swift in Sources */, 264957A329C520860095AA4C /* SubscriptionsView.swift in Sources */, - 02DE39D92968647100BB31D4 /* DomainSettingsViewModel.swift in Sources */, 576EA39225264C7400AFC0B3 /* RefundConfirmationViewController.swift in Sources */, DE36E0982A8634FF00B98496 /* StoreNameSetupView.swift in Sources */, EEBB9B3B2D8E5071008D6CE5 /* SelectableShipmentItemRow.swift in Sources */, @@ -16761,7 +16675,6 @@ 3188533C2639FE5800F66A9C /* PaymentSettingsFlowPresentedViewModel.swift in Sources */, CE55F2D42B238C04005D53D7 /* CollapsibleProductCardPriceSummaryViewModel.swift in Sources */, DE792E1B26EF37ED0071200C /* DefaultConnectivityObserver.swift in Sources */, - 02B41A96296D09D100FE3311 /* DomainSettingsListView.swift in Sources */, 029F29FE24DA5B2D004751CA /* ProductInventorySettingsViewModel.swift in Sources */, 57CFCD28248845B4003F51EC /* PrimarySectionHeaderView.swift in Sources */, 023A059A24135F2600E3FC99 /* ReviewsViewController.swift in Sources */, @@ -16798,7 +16711,6 @@ 26AE31B0251E602D004B1BCE /* RefundShippingDetailsTableViewCell.swift in Sources */, 26F115AD2C4997EA0019CD73 /* PerformanceCardDataSyncUseCase.swift in Sources */, 207823E92C5D3A1700025A59 /* POSErrorExclamationMark.swift in Sources */, - 020732062988AC4D000A53C2 /* DomainContactInfoFormViewModel.swift in Sources */, 68E4E8B52C0EF39D00CFA0C3 /* PreviewHelpers.swift in Sources */, EE8B42092BF668540077C4E7 /* MostActiveCouponRow.swift in Sources */, 022266BA2AE76E0E00614F34 /* ProductBundleItem+SwiftUIPreviewHelpers.swift in Sources */, @@ -16827,7 +16739,6 @@ DEC1508227F450AC00F4487C /* CouponAllowedEmails.swift in Sources */, EE505DE12B3D321A006E3323 /* BlazeLearnHowView.swift in Sources */, 03582BE2299A9CC8007B7AA3 /* CollectOrderPaymentAnalytics.swift in Sources */, - 02F36F472978349500D97EA0 /* DomainPurchaseSuccessView.swift in Sources */, B9DA153C280EC7D700FC67DD /* OrderRefundsOptionsDeterminer.swift in Sources */, 02FE734B2B21613D00CD486B /* ProductWithQuantityStepperView.swift in Sources */, 86EC6EB72CD0A53E00D7D2FE /* CustomFieldEditorViewModel.swift in Sources */, @@ -16864,7 +16775,6 @@ 02B9243F2C2200D600DC75F2 /* PointOfSaleCardPresentPaymentReaderUpdateFailedLowBatteryAlertViewModel.swift in Sources */, 2667BFEB2535FF09008099D4 /* RefundShippingCalculationUseCase.swift in Sources */, B6C838DE28793B3A003AB786 /* CustomFieldViewModel.swift in Sources */, - DE65C1F72C48E7DC003EF8D1 /* SupportButton.swift in Sources */, 95B6C60E2D9DA99200E1A661 /* WPComMagicLinkRequestViewModel.swift in Sources */, 20D210BE2B14C9B90099E517 /* WooPaymentsPayoutStatusDisplayDetails.swift in Sources */, 026225212C21F01F00700977 /* PointOfSaleCardPresentPaymentReaderUpdateFailedNonRetryableAlertViewModel.swift in Sources */, @@ -16952,7 +16862,6 @@ DEC75CC62BC4ED2100763801 /* DashboardCard+UI.swift in Sources */, 09EA565527C8ACEE00407D40 /* BulkUpdateViewController.swift in Sources */, 02BBD6E929A3024400243BE2 /* StoreOnboardingTaskView.swift in Sources */, - 028203CF297662A200217369 /* DomainSelectorDataProvider.swift in Sources */, 209EE8132DBA95BA0089F3D2 /* POSSearchView.swift in Sources */, DE74F2A327E41D650002FE59 /* EnableAnalyticsViewModel.swift in Sources */, AE77EA5027A47C99006A21BD /* View+AddingDividers.swift in Sources */, @@ -17257,7 +17166,6 @@ B6E7DB64293A7C390049B001 /* AnalyticsHubYesterdayRangeData.swift in Sources */, 456396B625C82691001F1A26 /* ShippingLabelFormStepTableViewCell.swift in Sources */, 03FBDA9D263AD49200ACE257 /* CouponListViewController.swift in Sources */, - 023930612918F36400B2632F /* DomainSelectorView.swift in Sources */, EE9D030C2B86078A0077CED1 /* ProductSelector+Order.swift in Sources */, EE5B5BC42AB83749009BCBD6 /* AddProductWithAIContainerView.swift in Sources */, 02C8876D24501FAC00E4470F /* FilterListViewController.swift in Sources */, @@ -17394,7 +17302,6 @@ 265D909D2446688C00D66F0F /* ProductCategoryViewModelBuilderTests.swift in Sources */, 03FBDAFD263EE4E800ACE257 /* CouponListViewModelTests.swift in Sources */, 20DB185B2CF5D9220018D3E1 /* MockPointOfSaleOrderController.swift in Sources */, - 02DAE7FF291B8C8A009342B7 /* FreeDomainSelectorViewModelTests.swift in Sources */, 036F6EA6281847D5006D84F8 /* PaymentCaptureOrchestratorTests.swift in Sources */, B555531321B57E8800449E71 /* MockUserNotificationsCenterAdapter.swift in Sources */, 682210ED2909666600814E14 /* CustomerSearchUICommandTests.swift in Sources */, @@ -17575,7 +17482,6 @@ 86E40AED2B597DEC00990365 /* BlazeCampaignCreationCoordinatorTests.swift in Sources */, 20A3AFE12B0F750B0033AF2D /* MockInPersonPaymentsCashOnDeliveryToggleRowViewModel.swift in Sources */, 2683835A296F9C1A00CCF60A /* GenerateAllVariationsUseCaseTests.swift in Sources */, - 020ACF8A299B746700B3638B /* DomainContactInfoFormViewModelTests.swift in Sources */, 0215320D2423309B003F2BBD /* UIStackView+SubviewsTests.swift in Sources */, DE2004532BF4A37B00660A72 /* InboxDashboardCardViewModelTests.swift in Sources */, 027B8BBD23FE0DE10040944E /* ProductImageActionHandlerTests.swift in Sources */, @@ -17631,7 +17537,6 @@ 2602A64227BD89CE00B347F1 /* NewOrderInitialStatusResolverTests.swift in Sources */, 68503C362DA53E0A00C07909 /* MockPointOfSaleCouponsController.swift in Sources */, DE6D84A52C3B8C9C0014FBFF /* GoogleAdsDashboardCardViewModelTests.swift in Sources */, - 0235354E2999D17A00BF77D3 /* DomainSettingsViewModelTests.swift in Sources */, 4535EE80281BE4E0004212B4 /* CouponAmountInputFormatterTests.swift in Sources */, 570AAB052472FACB00516C0C /* OrderDetailsDataSourceTests.swift in Sources */, CC77488E2719A07D0043CDD7 /* ShippingLabelAddressTopBannerFactoryTests.swift in Sources */, @@ -17702,7 +17607,6 @@ D83F5939225B424B00626E75 /* AddManualTrackingViewModelTests.swift in Sources */, FEED57FA2686544D00E47FD9 /* RoleErrorViewModelTests.swift in Sources */, 0388E1AA29E04715007DF84D /* MockDeepLinkNavigator.swift in Sources */, - 022C7D7729793ABE0036568D /* PaidDomainSelectorDataProviderTests.swift in Sources */, 01A3093C2DAE768600B672F6 /* MockPointOfSaleCouponService.swift in Sources */, 03CF78D127C3DBC000523706 /* WCPayCardBrand+IconsTests.swift in Sources */, CEEF742C2B9A052300B03948 /* OrdersReportCardViewModelTests.swift in Sources */, diff --git a/WooCommerce/WooCommerceTests/Extensions/IconsTests.swift b/WooCommerce/WooCommerceTests/Extensions/IconsTests.swift index 404036e1d38..8d686bf5bd0 100644 --- a/WooCommerce/WooCommerceTests/Extensions/IconsTests.swift +++ b/WooCommerce/WooCommerceTests/Extensions/IconsTests.swift @@ -160,10 +160,6 @@ final class IconsTests: XCTestCase { XCTAssertNotNil(UIImage.documentImage) } - func test_domainCreditImage_is_not_nil() { - XCTAssertNotNil(UIImage.domainCreditImage) - } - func test_domainPurchaseSuccessImage_is_not_nil() { XCTAssertNotNil(UIImage.domainPurchaseSuccessImage) } diff --git a/WooCommerce/WooCommerceTests/ViewModels/Domains/DomainContactInfoFormViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewModels/Domains/DomainContactInfoFormViewModelTests.swift deleted file mode 100644 index dbb2d5ce1a1..00000000000 --- a/WooCommerce/WooCommerceTests/ViewModels/Domains/DomainContactInfoFormViewModelTests.swift +++ /dev/null @@ -1,122 +0,0 @@ -import Networking -import XCTest -import Yosemite -@testable import WooCommerce - -final class DomainContactInfoFormViewModelTests: XCTestCase { - private var stores: MockStoresManager! - - override func setUp() { - super.setUp() - stores = MockStoresManager(sessionManager: SessionManager.makeForTesting()) - } - - override func tearDown() { - stores = nil - super.tearDown() - } - - func test_init_with_contact_info_pre_fills_all_fields() throws { - // Given - let viewModel = DomainContactInfoFormViewModel(siteID: 134, - contactInfoToEdit: Fixtures.contactInfo, - domain: "woocommerce.com", - source: .settings, - storageManager: ServiceLocator.storageManager, - stores: ServiceLocator.stores, - analytics: ServiceLocator.analytics) - - // Then - XCTAssertEqual(viewModel.fields.firstName, "Woo") - XCTAssertEqual(viewModel.fields.lastName, "Testing") - XCTAssertEqual(viewModel.fields.company, "WooCommerce org") - XCTAssertEqual(viewModel.fields.address1, "335 2nd St") - XCTAssertEqual(viewModel.fields.address2, "Apt 222") - XCTAssertEqual(viewModel.fields.postcode, "94111") - XCTAssertEqual(viewModel.fields.city, "San Francisco") - XCTAssertEqual(viewModel.fields.state, "CA") - XCTAssertEqual(viewModel.fields.country, "US") - XCTAssertEqual(viewModel.fields.phoneCountryCode, "886") - XCTAssertEqual(viewModel.fields.phone, "911123456") - XCTAssertEqual(viewModel.fields.email, "woo@store.com") - } - - func test_validating_contact_info_after_editing_all_fields_returns_updated_fields() async throws { - // Given - let viewModel = DomainContactInfoFormViewModel(siteID: 134, - contactInfoToEdit: Fixtures.contactInfo, - domain: "woocommerce.com", - source: .settings, - storageManager: ServiceLocator.storageManager, - stores: stores, - analytics: ServiceLocator.analytics) - mockRemoteValidation(result: .success(())) - mockCountriesData(result: .success(Fixtures.countries)) - - // When - viewModel.fields.firstName = "Oow" - viewModel.fields.lastName = "Woo" - viewModel.fields.company = "Woo" - viewModel.fields.address1 = "333 1st St" - viewModel.fields.address2 = "#228" - viewModel.fields.postcode = "94303" - viewModel.fields.city = "Palo Alto" - viewModel.fields.state = "" - viewModel.fields.selectedCountry = .init(code: "CA", name: "Canada", states: []) - viewModel.fields.phoneCountryCode = "+1" - viewModel.fields.phone = "650-123-4567" - viewModel.fields.email = "woo+test@store.com" - let validatedContactInfo = try await viewModel.validateContactInfo() - - // Then - XCTAssertEqual(validatedContactInfo.firstName, "Oow") - XCTAssertEqual(validatedContactInfo.lastName, "Woo") - XCTAssertEqual(validatedContactInfo.organization, "Woo") - XCTAssertEqual(validatedContactInfo.address1, "333 1st St") - XCTAssertEqual(validatedContactInfo.address2, "#228") - XCTAssertEqual(validatedContactInfo.postcode, "94303") - XCTAssertEqual(validatedContactInfo.city, "Palo Alto") - XCTAssertEqual(validatedContactInfo.state, "") - XCTAssertEqual(validatedContactInfo.countryCode, "CA") - XCTAssertEqual(validatedContactInfo.phone, "+1.6501234567") - XCTAssertEqual(validatedContactInfo.email, "woo+test@store.com") - } -} - -private extension DomainContactInfoFormViewModelTests { - func mockRemoteValidation(result: Result) { - stores.whenReceivingAction(ofType: DomainAction.self) { action in - if case let .validate(_, _, completion) = action { - completion(result) - } - } - } - - func mockCountriesData(result: Result<[Country], Error>) { - stores.whenReceivingAction(ofType: DataAction.self) { action in - if case let .synchronizeCountries(_, completion) = action { - completion(result) - } - } - } -} - -private extension DomainContactInfoFormViewModelTests { - enum Fixtures { - static let contactInfo: DomainContactInfo = .init(firstName: "Woo", - lastName: "Testing", - organization: "WooCommerce org", - address1: "335 2nd St", - address2: "Apt 222", - postcode: "94111", - city: "San Francisco", - state: "CA", - countryCode: "US", - phone: "+886.911123456", - email: "woo@store.com") - static let countries: [Country] = [ - .init(code: "US", name: "United States", states: [.init(code: "CA", name: "California")]), - .init(code: "CA", name: "Canada", states: []) - ] - } -} diff --git a/WooCommerce/WooCommerceTests/ViewModels/Domains/DomainSettingsViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewModels/Domains/DomainSettingsViewModelTests.swift deleted file mode 100644 index a038c57caf1..00000000000 --- a/WooCommerce/WooCommerceTests/ViewModels/Domains/DomainSettingsViewModelTests.swift +++ /dev/null @@ -1,107 +0,0 @@ -import XCTest -import Yosemite -@testable import WooCommerce - -final class DomainSettingsViewModelTests: XCTestCase { - private var stores: MockStoresManager! - private var viewModel: DomainSettingsViewModel! - - override func setUp() { - super.setUp() - stores = MockStoresManager(sessionManager: SessionManager.makeForTesting()) - viewModel = DomainSettingsViewModel(siteID: 134, stores: stores) - } - - override func tearDown() { - viewModel = nil - stores = nil - super.tearDown() - } - - // MARK: - `freeStagingDomain` - - func test_onAppear_sets_freeStagingDomain_from_first_domain_matching_isWPCOMStagingDomain() async throws { - // Given - mockLoadDomains(result: .success([ - .init(name: "woocommerce.dm", isPrimary: false, isWPCOMStagingDomain: false, type: .mapping), - .init(name: "woocommerce.wpcomstaging.com", isPrimary: true, isWPCOMStagingDomain: true, type: .mapping), - .init(name: "another.woocommerce.wpcomstaging.com", isPrimary: true, isWPCOMStagingDomain: false, type: .wpcom) - ])) - mockLoadSiteCurrentPlan(result: .success(.init(hasDomainCredit: false))) - - // When - await viewModel.onAppear() - - // Then - XCTAssertEqual(viewModel.freeStagingDomain, .init(isPrimary: true, name: "woocommerce.wpcomstaging.com")) - } - - func test_onAppear_sets_freeStagingDomain_from_first_domain_matching_type() async throws { - // Given - mockLoadDomains(result: .success([ - .init(name: "woocommerce.dm", isPrimary: false, isWPCOMStagingDomain: false, type: .mapping), - .init(name: "woocommerce.wordpress.com", isPrimary: true, isWPCOMStagingDomain: false, type: .wpcom), - .init(name: "woocommerce.wpcomstaging.com", isPrimary: true, isWPCOMStagingDomain: true, type: .mapping) - ])) - mockLoadSiteCurrentPlan(result: .success(.init(hasDomainCredit: false))) - - // When - await viewModel.onAppear() - - // Then - XCTAssertEqual(viewModel.freeStagingDomain, .init(isPrimary: true, name: "woocommerce.wordpress.com")) - } - - func test_onAppear_sets_freeStagingDomain_to_nil_when_no_domain_matches_isWPCOMStagingDomain_and_type() async throws { - // Given - mockLoadDomains(result: .success([ - .init(name: "woocommerce.com", isPrimary: true, isWPCOMStagingDomain: false, type: .mapping) - ])) - mockLoadSiteCurrentPlan(result: .success(.init(hasDomainCredit: false))) - - // When - await viewModel.onAppear() - - // Then - XCTAssertNil(viewModel.freeStagingDomain) - } - - // MARK: - `domains` - - func test_onAppear_sets_domains_to_non_wpcom_domains() async throws { - // Given - mockLoadDomains(result: .success([ - .init(name: "woocommerce.wpcomstaging.com", isPrimary: true, isWPCOMStagingDomain: true, type: .wpcom), - .init(name: "woocommerce.com", isPrimary: true, isWPCOMStagingDomain: true, type: .mapping), - .init(name: "woocommerce.wordpress.com", isPrimary: true, isWPCOMStagingDomain: false, type: .wpcom), - ])) - mockLoadSiteCurrentPlan(result: .success(.init(hasDomainCredit: false))) - XCTAssertEqual(viewModel.domains, []) - - // When - await viewModel.onAppear() - - // Then - XCTAssertEqual(viewModel.domains, [.init(isPrimary: true, name: "woocommerce.com", autoRenewalDate: nil)]) - } -} - -private extension DomainSettingsViewModelTests { - func mockLoadDomains(result: Result<[SiteDomain], Error>) { - stores.whenReceivingAction(ofType: DomainAction.self) { action in - guard case let .loadDomains(_, completion) = action else { - return XCTFail() - } - completion(result) - } - } - - func mockLoadSiteCurrentPlan(result: Result) { - stores.whenReceivingAction(ofType: PaymentAction.self) { action in - guard case let .loadSiteCurrentPlan(_, completion) = action else { - return XCTFail() - } - completion(result) - } - } -} diff --git a/WooCommerce/WooCommerceTests/ViewModels/Domains/FreeDomainSelectorViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewModels/Domains/FreeDomainSelectorViewModelTests.swift deleted file mode 100644 index 1eafca673dd..00000000000 --- a/WooCommerce/WooCommerceTests/ViewModels/Domains/FreeDomainSelectorViewModelTests.swift +++ /dev/null @@ -1,161 +0,0 @@ -import Combine -import XCTest -import Yosemite -import enum Networking.DotcomError -@testable import WooCommerce - -final class FreeDomainSelectorViewModelTests: XCTestCase { - typealias ViewModel = DomainSelectorViewModel - private var stores: MockStoresManager! - private var viewModel: ViewModel! - private var subscriptions: Set = [] - - override func setUp() { - super.setUp() - stores = MockStoresManager(sessionManager: SessionManager.makeForTesting()) - viewModel = .init(title: "", subtitle: "", dataProvider: FreeDomainSelectorDataProvider(stores: stores), debounceDuration: 0) - } - - override func tearDown() { - viewModel = nil - stores = nil - super.tearDown() - } - - func test_title_and_subtitle_are_set_by_init_parameter() { - // When - let viewModel = DomainSelectorViewModel( - title: "title", subtitle: "Subtitle", dataProvider: FreeDomainSelectorDataProvider(stores: stores), debounceDuration: 0 - ) - - // Then - XCTAssertEqual(viewModel.title, "title") - XCTAssertEqual(viewModel.subtitle, "Subtitle") - } - - func test_DomainAction_is_not_dispatched_when_searchTerm_is_empty() { - // Given - stores.whenReceivingAction(ofType: DomainAction.self) { action in - // Then - XCTFail("Unexpected action: \(action)") - } - - // When - viewModel.searchTerm = "" - viewModel.searchTerm = "" - } - - // MARK: - `isLoadingDomainSuggestions` - - func test_isLoadingDomainSuggestions_is_toggled_when_loading_suggestions() { - var loadingValues: [Bool] = [] - viewModel.$isLoadingDomainSuggestions.sink { value in - loadingValues.append(value) - }.store(in: &subscriptions) - - mockDomainSuggestionsFailure(error: SampleError.first) - - // When - viewModel.searchTerm = "Woo" - - // Then - waitUntil { - loadingValues == [false, true, false] - } - } - - // MARK: - `state` - - func test_state_is_placeholder_when_searchTerm_is_empty() { - // When - viewModel.searchTerm = "" - - // Then - XCTAssertEqual(viewModel.state, .placeholder) - } - - func test_state_is_results_with_free_domain_only_on_domain_suggestions_success() { - // Given - mockDomainSuggestionsSuccess(suggestions: [ - .init(name: "free.com", isFree: true), - .init(name: "paid.com", isFree: false) - ]) - - // When - viewModel.searchTerm = "woo" - - // Then - waitUntil { - self.viewModel.state == .results(domains: [.init(domainSuggestion: .init(name: "free.com", isFree: true))]) - } - } - - func test_state_is_errorMessage_with_default_error_message_when_failure_is_not_DotcomError() { - // Given - mockDomainSuggestionsFailure(error: SampleError.first) - - // When - viewModel.searchTerm = "woo" - - // Then - waitUntil { - self.viewModel.state == .error(message: ViewModel.Localization.defaultErrorMessage) - } - } - - func test_state_is_errorMessage_with_DotcomError_message_when_failure_is_DotcomError() { - // Given - mockDomainSuggestionsFailure(error: DotcomError.unknown(code: "", message: "error message")) - - // When - viewModel.searchTerm = "woo" - - // Then - waitUntil { - self.viewModel.state == .error(message: "error message") - } - } - - func test_state_is_updated_from_errorMessage_to_results_when_changing_search_term_after_failure() { - // Given - mockDomainSuggestionsFailure(error: SampleError.first) - - // When - viewModel.searchTerm = "woo" - waitUntil { - self.viewModel.state == .error(message: ViewModel.Localization.defaultErrorMessage) - } - - mockDomainSuggestionsSuccess(suggestions: []) - viewModel.searchTerm = "wooo" - - // Then - waitUntil { - self.viewModel.state == .results(domains: []) - } - } -} - -private extension FreeDomainSelectorViewModelTests { - func mockDomainSuggestionsSuccess(suggestions: [FreeDomainSuggestion]) { - stores.whenReceivingAction(ofType: DomainAction.self) { action in - switch action { - case let .loadFreeDomainSuggestions(_, completion): - completion(.success(suggestions)) - default: - return - } - } - } - - func mockDomainSuggestionsFailure(error: Error) { - stores.whenReceivingAction(ofType: DomainAction.self) { action in - switch action { - case let .loadFreeDomainSuggestions(_, completion): - completion(.failure(error)) - default: - return - } - } - } -} diff --git a/WooCommerce/WooCommerceTests/ViewModels/Domains/PaidDomainSelectorDataProviderTests.swift b/WooCommerce/WooCommerceTests/ViewModels/Domains/PaidDomainSelectorDataProviderTests.swift deleted file mode 100644 index acfae899cc3..00000000000 --- a/WooCommerce/WooCommerceTests/ViewModels/Domains/PaidDomainSelectorDataProviderTests.swift +++ /dev/null @@ -1,151 +0,0 @@ -import XCTest -import Yosemite -@testable import WooCommerce - -final class PaidDomainSelectorDataProviderTests: XCTestCase { - private var stores: MockStoresManager! - private var dataProvider: PaidDomainSelectorDataProvider! - - override func setUp() { - super.setUp() - stores = MockStoresManager(sessionManager: SessionManager.makeForTesting()) - dataProvider = PaidDomainSelectorDataProvider(stores: stores, hasDomainCredit: false) - } - - override func tearDown() { - dataProvider = nil - stores = nil - super.tearDown() - } - - func test_loadDomainSuggestions_returns_detail_without_sale_cost() async throws { - // Given - let domainWithoutSale = PaidDomainSuggestion(productID: 134, - supportsPrivacy: true, - name: "domain.nosale", - term: "year", - cost: "US$47.00", - isPremium: false) - stores.whenReceivingAction(ofType: DomainAction.self) { action in - switch action { - case let .loadPaidDomainSuggestions(_, _, completion): - completion(.success([domainWithoutSale])) - default: - XCTFail("Unexpected action: \(action)") - } - } - - // When - let viewModels = try await dataProvider.loadDomainSuggestions(query: "domain") - - // Then - XCTAssertEqual(viewModels.count, 1) - - let viewModel = viewModels[0] - XCTAssertEqual(viewModel.name, "domain.nosale") - XCTAssertEqual(viewModel.productID, 134) - let viewModelPrice = String(format: PaidDomainSuggestionViewModel.Localization.priceFormat, "US$47.00", "year") - let viewModelDetailText = try XCTUnwrap(viewModel.attributedDetail) - XCTAssertEqual(String(viewModelDetailText.characters), viewModelPrice) - } - - func test_loadDomainSuggestions_returns_detail_with_sale_cost() async throws { - // Given - let domainWithSale = PaidDomainSuggestion(productID: 18, - supportsPrivacy: true, - name: "domain.onsale", - term: "year", - cost: "US$25.00", - saleCost: "US$3.90", - isPremium: false) - stores.whenReceivingAction(ofType: DomainAction.self) { action in - switch action { - case let .loadPaidDomainSuggestions(_, _, completion): - completion(.success([domainWithSale])) - default: - XCTFail("Unexpected action: \(action)") - } - } - - // When - let viewModels = try await dataProvider.loadDomainSuggestions(query: "domain") - - // Then - XCTAssertEqual(viewModels.count, 1) - - let viewModel = viewModels[0] - XCTAssertEqual(viewModel.name, "domain.onsale") - XCTAssertEqual(viewModel.productID, 18) - let viewModelPrice = String(format: PaidDomainSuggestionViewModel.Localization.priceFormat, "US$25.00", "year") - let viewModelDetailText = try XCTUnwrap(viewModel.attributedDetail) - XCTAssertEqual(String(viewModelDetailText.characters), "US$3.90 \(viewModelPrice)") - } - - func test_loadDomainSuggestions_with_domain_credit_returns_first_year_free_text_for_nonpremium_domain() async throws { - // Given - let domainWithSale = PaidDomainSuggestion(productID: 18, - supportsPrivacy: true, - name: "domain.credit", - term: "year", - cost: "US$25.00", - saleCost: "US$3.90", - isPremium: false) - stores.whenReceivingAction(ofType: DomainAction.self) { action in - switch action { - case let .loadPaidDomainSuggestions(_, _, completion): - completion(.success([domainWithSale])) - default: - XCTFail("Unexpected action: \(action)") - } - } - let dataProvider = PaidDomainSelectorDataProvider(stores: stores, hasDomainCredit: true) - - // When - let viewModels = try await dataProvider.loadDomainSuggestions(query: "domain") - - // Then - XCTAssertEqual(viewModels.count, 1) - - let viewModel = viewModels[0] - XCTAssertEqual(viewModel.name, "domain.credit") - XCTAssertEqual(viewModel.productID, 18) - XCTAssertFalse(viewModel.isPremium) - let viewModelPrice = String(format: PaidDomainSuggestionViewModel.Localization.priceFormat, "US$25.00", "year") - let viewModelDetailText = try XCTUnwrap(viewModel.attributedDetail) - XCTAssertEqual(String(viewModelDetailText.characters), "\(viewModelPrice) \(PaidDomainSuggestionViewModel.Localization.domainCreditPricing)") - } - - func test_loadDomainSuggestions_with_domain_credit_returns_cost_detail_for_premium_domain() async throws { - // Given - let premiumDomain = PaidDomainSuggestion(productID: 18, - supportsPrivacy: true, - name: "premium.domain", - term: "year", - cost: "US$25.00", - saleCost: "US$3.90", - isPremium: true) - stores.whenReceivingAction(ofType: DomainAction.self) { action in - switch action { - case let .loadPaidDomainSuggestions(_, _, completion): - completion(.success([premiumDomain])) - default: - XCTFail("Unexpected action: \(action)") - } - } - let dataProvider = PaidDomainSelectorDataProvider(stores: stores, hasDomainCredit: true) - - // When - let viewModels = try await dataProvider.loadDomainSuggestions(query: "domain") - - // Then - XCTAssertEqual(viewModels.count, 1) - - let viewModel = viewModels[0] - XCTAssertEqual(viewModel.name, "premium.domain") - XCTAssertEqual(viewModel.productID, 18) - XCTAssertTrue(viewModel.isPremium) - let viewModelPrice = String(format: PaidDomainSuggestionViewModel.Localization.priceFormat, "US$25.00", "year") - let viewModelDetailText = try XCTUnwrap(viewModel.attributedDetail) - XCTAssertEqual(String(viewModelDetailText.characters), "US$3.90 \(viewModelPrice)") - } -}