Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,8 @@ final class CreateOrderAddressFormViewModel: AddressFormViewModel, AddressFormVi
siteID: siteID,
keyword: "hello") { result in
switch result {
case .success(let customers):
let storage = ServiceLocator.storageManager
guard let result = storage.viewStorage.loadCustomerSearchResult(siteID: self.siteID, keyword: "hello") else {
return
}
print("Site ID: \(result.siteID), keyword: \(result.keyword), Customers: \(result.customers?.count as Any)")
for eachCustomer in customers {
let output = """
Customer: \(eachCustomer.customerID),
Name: \(String(describing: eachCustomer.firstName)) \(String(describing: eachCustomer.lastName))
"""
print(output)
}
case .success(_):
print("Success")
case .failure(let error):
print(error)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Foundation
import Yosemite

/// Implementation of `SearchUICommand` for Customer search.
///
final class CustomerSearchUICommand: SearchUICommand {

typealias Model = Customer
typealias CellViewModel = TitleAndSubtitleAndStatusTableViewCell.ViewModel
Copy link
Contributor Author

@iamgabrielma iamgabrielma Oct 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to get a different recommendation for the CellView we want to use here, at the moment I'm using TitleAndSubtitleAndStatusTableViewCell just for convenience, as must conform to SearchResultCell there are several options to choose from without having to create a new one (or I could create a new one as well!)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that you're going to have the avatar in there too, you might want to try adding SearchResultCell conformance to LeftImageTitleSubtitleTableViewCell?

The only thing it won't do is any attributed text in the subtitle for an underlined email as on Android... but I don't personally think that should be a blocker.

typealias ResultsControllerModel = StorageCustomer

var searchBarPlaceholder: String = Localization.searchBarPlaceHolder

var searchBarAccessibilityIdentifier: String = "customer-search-screen-search-field"

var cancelButtonAccessibilityIdentifier: String = "customer-search-screen-cancel-button"
Comment on lines +14 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably only need to set accessibilityIdentifiers if you're actually using the search in a UI test. Identifiers are nothing to do with VoiceOver and other assistive tech, in practice, they are only used for UI tests.

AccessibilityLabels are for VoiceOver and important to have on custom controls or complex groups.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL!


var resynchronizeModels: (() -> Void) = {}

var onDidSelectSearchResult: ((Customer) -> Void)

private let siteID: Int64

init(siteID: Int64, onDidSelectSearchResult: @escaping ((Customer) -> Void)) {
self.siteID = siteID
self.onDidSelectSearchResult = onDidSelectSearchResult
}

func createResultsController() -> ResultsController<StorageCustomer> {
let storageManager = ServiceLocator.storageManager
let predicate = NSPredicate(format: "siteID == %lld", siteID)
let descriptor = NSSortDescriptor(keyPath: \StorageCustomer.customerID, ascending: false)
return ResultsController<StorageCustomer>(storageManager: storageManager, matching: predicate, sortedBy: [descriptor])
}

func createStarterViewController() -> UIViewController? {
nil
}

func createCellViewModel(model: Customer) -> TitleAndSubtitleAndStatusTableViewCell.ViewModel {
return CellViewModel(
id: "\(model.customerID)",
title: "\(model.firstName ?? "") \(model.lastName ?? "")",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This... might be the right thing to do given our data source, but names are complicated, and simply concatenating them isn't correct in all locales.

See PersonNameComponentsFormatter docs for more info. There's not necessarily anything to do here (as I don't know whether we have enough in our data source to correctly make a formatter) but I thought it would be useful for you to know about if you don't already.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the link, I definitely didn't know about PersonNameComponentsFormatter. I agree with you, concatenating strings doesn't look good to me either and I'll look for cleaner alternatives (if possible). I'll dig into this document and make any necessary improvements on another PR.

subtitle: model.email,
accessibilityLabel: "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessibility Label is required here... see what it does in VoiceOver without it: it just announces:

Button

Without any detail about the name of the user or anything else about the search result.

If you're moving to another cell, you may not have to do anything, usually we get VoiceOver in cells for free, but in this case we are specific about what the label should be in the view model.

status: "",
statusBackgroundColor: .clear
)
}

func synchronizeModels(siteID: Int64, keyword: String, pageNumber: Int, pageSize: Int, onCompletion: ((Bool) -> Void)?) {
let action = CustomerAction.searchCustomers(siteID: siteID, keyword: keyword) { result in
switch result {
case .success(_):
onCompletion?(result.isSuccess)
case .failure(let error):
DDLogError("Customer Search Failure \(error)")
}
}
ServiceLocator.stores.dispatch(action)
}

func didSelectSearchResult(model: Customer, from viewController: UIViewController, reloadData: () -> Void, updateActionButton: () -> Void) {
// Not implemented yet
print("1 - Customer tapped")
print("2 - Customer ID: \(model.customerID) - Name: \(model.firstName ?? ""))")
// Customer data will go up to EditOrderAddressForm, via OrderCustomerListView completion handler
onDidSelectSearchResult(model)
}

func searchResultsPredicate(keyword: String) -> NSPredicate? {
return NSPredicate(format: "siteID == %lld AND ANY searchResults.keyword = %@", siteID, keyword)
}
}

private extension CustomerSearchUICommand {
enum Localization {
static let searchBarPlaceHolder = NSLocalizedString("Search all customers",
comment: "Customer Search Placeholder")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Foundation
import Yosemite
import SwiftUI

/// `SwiftUI` wrapper for `SearchViewController` using `CustomerSearchUICommand`
/// TODO: Make it generic
struct OrderCustomerListView: UIViewControllerRepresentable {

let siteID: Int64

let onCustomerTapped: ((Customer) -> Void)

func makeUIViewController(context: Context) -> WooNavigationController {

let viewController = SearchViewController(
storeID: siteID,
command: CustomerSearchUICommand(siteID: siteID, onDidSelectSearchResult: onCustomerTapped),
cellType: TitleAndSubtitleAndStatusTableViewCell.self,
// Must conform to SearchResultCell.
// TODO: Proper cell for this cellType.
cellSeparator: .none
)
let navigationController = WooNavigationController(rootViewController: viewController)
return navigationController
}

func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// nope
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import protocol Storage.StorageManagerType
///
protocol AddressFormViewModelProtocol: ObservableObject {

/// Site ID
///
var siteID: Int64 { get }

/// Address form fields
///
var fields: AddressFormFields { get set }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ struct EditOrderAddressForm<ViewModel: AddressFormViewModelProtocol>: View {
@ObservedObject private(set) var viewModel: ViewModel

@Environment(\.safeAreaInsets) var safeAreaInsets: EdgeInsets
@State private var showingCustomerSearch: Bool = false

let isSearchCustomersEnabled = DefaultFeatureFlagService().isFeatureFlagEnabled(.orderCreationSearchCustomers)

Expand Down Expand Up @@ -152,7 +153,7 @@ struct EditOrderAddressForm<ViewModel: AddressFormViewModelProtocol>: View {
ToolbarItem(placement: .automatic) {
if isSearchCustomersEnabled {
Button(action: {
viewModel.customerSearchTapped()
showingCustomerSearch = true
}, label: {
Image(systemName: "magnifyingglass")
})
Expand All @@ -169,6 +170,13 @@ struct EditOrderAddressForm<ViewModel: AddressFormViewModelProtocol>: View {
viewModel.onLoadTrigger.send()
}
.notice($viewModel.notice)
.sheet(isPresented: $showingCustomerSearch, content: {
OrderCustomerListView(siteID: viewModel.siteID, onCustomerTapped: { customer in
// Not implemented yet.
print("3 - Customer Callback. Fill Order data with Customer details")
print("4 - Customer ID: \(customer.customerID) - Name: \(customer.firstName ?? ""))")
})
})
}

/// Decides if the navigation trailing item should be a done button or a loading indicator.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ final class EditOrderAddressFormViewModel: AddressFormViewModel, AddressFormView
case billing
}

var siteID: Int64 {
order.siteID
}
/// Order to be edited.
///
private let order: Yosemite.Order
Expand Down
8 changes: 8 additions & 0 deletions WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,8 @@
6856DE479EC3B2265AC1F775 /* Calendar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6856D66A1963092C34D20674 /* Calendar+Extensions.swift */; };
6856DF20E1BDCC391635F707 /* AgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6856D1A5F72A36AB3704D19D /* AgeTests.swift */; };
6879B8DB287AFFA100A0F9A8 /* CardReaderManualsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6879B8DA287AFFA100A0F9A8 /* CardReaderManualsViewModelTests.swift */; };
68D1BEDB28FFEDC20074A29E /* OrderCustomerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D1BEDA28FFEDC20074A29E /* OrderCustomerListView.swift */; };
68D1BEDD2900E4180074A29E /* CustomerSearchUICommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D1BEDC2900E4180074A29E /* CustomerSearchUICommand.swift */; };
68E952CC287536010095A23D /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E952CB287536010095A23D /* SafariView.swift */; };
68E952D0287587BF0095A23D /* CardReaderManualRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E952CF287587BF0095A23D /* CardReaderManualRowView.swift */; };
68E952D22875A44B0095A23D /* CardReaderType+Manual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E952D12875A44B0095A23D /* CardReaderType+Manual.swift */; };
Expand Down Expand Up @@ -2892,6 +2894,8 @@
6856D7981E11F85D5E4EFED7 /* NSMutableAttributedStringHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSMutableAttributedStringHelperTests.swift; sourceTree = "<group>"; };
6856DCE1638958DA296D690F /* KeyboardStateProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardStateProviderTests.swift; sourceTree = "<group>"; };
6879B8DA287AFFA100A0F9A8 /* CardReaderManualsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderManualsViewModelTests.swift; sourceTree = "<group>"; };
68D1BEDA28FFEDC20074A29E /* OrderCustomerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCustomerListView.swift; sourceTree = "<group>"; };
68D1BEDC2900E4180074A29E /* CustomerSearchUICommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerSearchUICommand.swift; sourceTree = "<group>"; };
68E952CB287536010095A23D /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
68E952CF287587BF0095A23D /* CardReaderManualRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderManualRowView.swift; sourceTree = "<group>"; };
68E952D12875A44B0095A23D /* CardReaderType+Manual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardReaderType+Manual.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6467,6 +6471,8 @@
children = (
AE9E04742776213E003FA09E /* OrderCustomerSection.swift */,
AEC95D422774D07B001571F5 /* CreateOrderAddressFormViewModel.swift */,
68D1BEDA28FFEDC20074A29E /* OrderCustomerListView.swift */,
68D1BEDC2900E4180074A29E /* CustomerSearchUICommand.swift */,
);
path = CustomerSection;
sourceTree = "<group>";
Expand Down Expand Up @@ -9661,6 +9667,7 @@
0313651328ABCB2D00EEE571 /* InPersonPaymentsOnboardingErrorMainContentView.swift in Sources */,
260C315E2523CC4000157BC2 /* RefundProductsTotalViewModel.swift in Sources */,
0286B27D23C7051F003D784B /* ProductImagesViewController.swift in Sources */,
68D1BEDB28FFEDC20074A29E /* OrderCustomerListView.swift in Sources */,
4569317F2653E82B009ED69D /* ShippingLabelCarriers.swift in Sources */,
024DF30B23742297006658FE /* AztecFormatBarCommand.swift in Sources */,
262AF387271114CC00E39AFF /* SimplePaymentsAmountViewModel.swift in Sources */,
Expand Down Expand Up @@ -9704,6 +9711,7 @@
D881A31B256B5CC500FE5605 /* ULErrorViewController.swift in Sources */,
CE22E3F72170E23C005A6BEF /* PrivacySettingsViewController.swift in Sources */,
021125482577CC650075AD2A /* ShippingLabelDetailsViewModel.swift in Sources */,
68D1BEDD2900E4180074A29E /* CustomerSearchUICommand.swift in Sources */,
316837DA25CCA90C00E36B2F /* OrderStatusListDataSource.swift in Sources */,
FE28F6F4268477C1004465C7 /* RoleEligibilityUseCase.swift in Sources */,
57C5FF7A25091A350074EC26 /* OrderListSyncActionUseCase.swift in Sources */,
Expand Down