Skip to content

Commit 77e49ae

Browse files
committed
Merge branch 'trunk' into feat/WOOMOB-1609-catalog-api-behind-feature-flag
2 parents 1166f87 + 89d3065 commit 77e49ae

26 files changed

+1003
-378
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<!--
22
Contains editorialized release notes. Raw release notes should go into `RELEASE-NOTES.txt`.
33
-->
4+
## 23.6
5+
This update improves app stability and usability. We’ve enhanced compatibility for stores using HTTP site addresses, optimized how tabs load based on saved states, and fixed an issue that prevented dismissing the keyboard when editing product titles.
6+
47
## 23.5
58
This update brings smoother store management and better control. You can now filter orders by source, and manage all POS orders directly within the POS interface. Plus, we fixed a scrolling issue on the Create Coupon screen for a more seamless experience.
69

Modules/Sources/PointOfSale/Presentation/Settings/POSSettingsHardwareDetailView.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ struct POSSettingsHardwareDetailView: View {
66
@Environment(\.posFeatureFlags) private var featureFlags
77
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
88
@Environment(\.posAnalytics) private var analytics
9+
@Environment(\.posExternalViews) private var externalViews
910

1011
let settingsController: POSSettingsControllerProtocol
1112

1213
@State private var navigationPath: [NavigationDestination] = []
1314
@State private var showBarcodeScanningSetupModal: Bool = false
1415
@State private var showBarcodeScanningDocumentationModal: Bool = false
1516
@State private var showCardReaderDocumentationModal: Bool = false
17+
@State private var showSupport: Bool = false
1618

1719
private var cardReaderName: String {
1820
if let cardReaderName = settingsController.connectedCardReader?.name {
@@ -88,6 +90,15 @@ struct POSSettingsHardwareDetailView: View {
8890
PointOfSaleCardPresentPaymentAlert(alertType: alertType)
8991
.posInteractiveDismissDisabled(alertType.isDismissDisabled)
9092
})
93+
.posModal(item: $posModel.cardPresentPaymentOnboardingViewContainer, onDismiss: {
94+
posModel.cancelCardPaymentsOnboarding()
95+
}, content: { viewContainer in
96+
paymentsOnboardingView(from: viewContainer)
97+
})
98+
.posSheet(isPresented: $showSupport) {
99+
supportForm
100+
.interactiveDismissDisabled(true)
101+
}
91102
.posModal(isPresented: $showBarcodeScanningSetupModal) {
92103
POSBarcodeScannerSetup(isPresented: $showBarcodeScanningSetupModal, analytics: analytics)
93104
}
@@ -100,6 +111,36 @@ struct POSSettingsHardwareDetailView: View {
100111

101112
// MARK: - Views
102113
private extension POSSettingsHardwareDetailView {
114+
func paymentsOnboardingView(from onboardingViewContainer: CardPresentPaymentOnboardingViewContainer) -> some View {
115+
onboardingViewContainer.configuration.showSupport = {
116+
posModel.cancelCardPaymentsOnboarding()
117+
showSupport = true
118+
}
119+
120+
return PointOfSaleCardPresentPaymentOnboardingView(
121+
viewModel: .init(onboardingViewContainer: onboardingViewContainer,
122+
onDismissTap: {
123+
posModel.cancelCardPaymentsOnboarding()
124+
}))
125+
.onAppear {
126+
posModel.trackCardPaymentsOnboardingShown()
127+
}
128+
}
129+
130+
var supportForm: some View {
131+
NavigationView {
132+
externalViews.createSupportFormView(isPresented: $showSupport, sourceTag: Constants.supportTag)
133+
.toolbar {
134+
ToolbarItem(placement: .cancellationAction) {
135+
Button(Localization.supportCancel) {
136+
showSupport = false
137+
}
138+
}
139+
}
140+
}
141+
.navigationViewStyle(.stack)
142+
}
143+
103144
var legacyCardReadersView: some View {
104145
VStack(spacing: POSSpacing.none) {
105146
POSPageHeaderView(
@@ -354,6 +395,10 @@ private extension POSSettingsHardwareDetailView {
354395

355396
// MARK: - Constants
356397
private extension POSSettingsHardwareDetailView {
398+
enum Constants {
399+
static let supportTag = "origin:point-of-sale"
400+
}
401+
357402
enum Localization {
358403
static let readerModelTitle = NSLocalizedString(
359404
"pointOfSaleSettingsHardwareDetailView.readerModelTitle",
@@ -466,6 +511,12 @@ private extension POSSettingsHardwareDetailView {
466511
value: "Connect your card reader and start accepting payments",
467512
comment: "Subtitle for card reader connect button when no reader is connected."
468513
)
514+
515+
static let supportCancel = NSLocalizedString(
516+
"pointOfSaleSettingsHardwareDetailView.help.support.cancel",
517+
value: "Cancel",
518+
comment: "Button to dismiss the support form from POS settings."
519+
)
469520
}
470521
}
471522

Modules/Sources/Yosemite/Model/Payments/CardPresentPaymentsConfiguration.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,12 @@ public struct CardPresentPaymentsConfiguration: Equatable {
9595
countryCode: country,
9696
paymentMethods: [.cardPresent],
9797
currencies: [.GBP],
98-
paymentGateways: [WCPayAccount.gatewayID],
98+
paymentGateways: [WCPayAccount.gatewayID, StripeAccount.gatewayID],
9999
supportedReaders: [.wisepad3, .tapToPay],
100-
supportedPluginVersions: [.init(plugin: .wcPay, minimumVersion: "4.4.0")],
100+
supportedPluginVersions: [
101+
.init(plugin: .wcPay, minimumVersion: "4.4.0"),
102+
.init(plugin: .stripe, minimumVersion: "6.2.0")
103+
],
101104
minimumAllowedChargeAmount: NSDecimalNumber(string: "0.3"),
102105
stripeSmallestCurrencyUnitMultiplier: 100,
103106
contactlessLimitAmount: 10000,

Modules/Tests/YosemiteTests/Model/CardPresentConfigurationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class CardPresentConfigurationTests: XCTestCase {
4444
let configuration = CardPresentPaymentsConfiguration(country: .GB)
4545
XCTAssertTrue(configuration.isSupportedCountry)
4646
XCTAssertEqual(configuration.currencies, [.GBP])
47-
XCTAssertEqual(configuration.paymentGateways, [Constants.PaymentGateway.wcpay])
47+
XCTAssertEqual(configuration.paymentGateways, [Constants.PaymentGateway.wcpay, Constants.PaymentGateway.stripe])
4848
XCTAssertEqual(configuration.paymentMethods, [.cardPresent])
4949
XCTAssertEqual(configuration.purchaseCardReaderUrl(utmProvider: MockUTMParameterProvider()).absoluteString, Constants.PurchaseURL.gb)
5050
assertEqual([.wisepad3, .tapToPay], configuration.supportedReaders)

RELEASE-NOTES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
*** PLEASE FOLLOW THIS FORMAT: [<priority indicator, more stars = higher priority>] <description> [<PR URL>]
22
*** Use [*****] to indicate smoke tests of all critical flows should be run on the final IPA before release (e.g. major library or OS update).
33

4+
23.7
5+
-----
6+
7+
48
23.6
59
-----
610
- [*] Handle sites configured with `http` siteAddress. [https://github.com/woocommerce/woocommerce-ios/pull/16279]

WooCommerce/Classes/Bookings/BookingFilters/BookableProductListSyncable.swift

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ struct BookableProductListSyncable: ListSyncable {
1111

1212
let title = Localization.title
1313

14-
let emptyStateMessage = Localization.noMembersFound
14+
let emptyStateMessage = Localization.noServiceFound
1515
let emptyItemTitlePlaceholder: String? = nil
1616

17-
let searchConfiguration: ListSearchConfiguration? = nil
17+
let searchConfiguration: ListSearchConfiguration? = ListSearchConfiguration(
18+
searchPrompt: Localization.searchPrompt,
19+
emptySearchTitle: Localization.noServiceFound,
20+
emptySearchDescription: Localization.emptySearchDescription
21+
)
1822

1923
let selectionDisabledMessage: String? = nil
2024

@@ -53,7 +57,19 @@ struct BookableProductListSyncable: ListSyncable {
5357

5458
/// Creates the action to search items with keyword
5559
func createSearchAction(keyword: String, pageNumber: Int, pageSize: Int, completion: @escaping (Result<Bool, Error>) -> Void) -> Action {
56-
fatalError("Searching is not supported")
60+
ProductAction.searchProducts(
61+
siteID: siteID,
62+
keyword: keyword,
63+
pageNumber: pageNumber,
64+
pageSize: pageSize,
65+
productType: .booking,
66+
onCompletion: completion
67+
)
68+
}
69+
70+
/// Creates the predicate for filtering search results
71+
func createSearchPredicate(keyword: String) -> NSPredicate? {
72+
NSPredicate(format: "SUBQUERY(searchResults, $result, $result.keyword = %@).@count > 0", keyword)
5773
}
5874

5975
// MARK: - Display Configuration
@@ -79,10 +95,20 @@ private extension BookableProductListSyncable {
7995
value: "Service / Event",
8096
comment: "Title of the booking service/event selector view"
8197
)
82-
static let noMembersFound = NSLocalizedString(
98+
static let noServiceFound = NSLocalizedString(
8399
"bookingServiceEventSelectorView.noMembersFound",
84100
value: "No service or event found",
85101
comment: "Text on the empty view of the booking service/event selector view"
86102
)
103+
static let searchPrompt = NSLocalizedString(
104+
"bookingServiceEventSelectorView.searchPrompt",
105+
value: "Search service / event",
106+
comment: "Prompt in the search bar of the booking service/event selector view"
107+
)
108+
static let emptySearchDescription = NSLocalizedString(
109+
"bookingServiceEventSelectorView.emptySearchDescription",
110+
value: "Try adjusting your search term to see more results",
111+
comment: "Message on the empty search result view of the booking service/event selector view"
112+
)
87113
}
88114
}

WooCommerce/Classes/Bookings/BookingFilters/CustomerListSyncable.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ struct CustomerListSyncable: ListSyncable {
6666
)
6767
}
6868

69+
/// Creates the predicate for filtering search results
70+
/// - Returns: nil because customer search handles filtering directly
71+
func createSearchPredicate(keyword: String) -> NSPredicate? {
72+
nil
73+
}
74+
6975
// MARK: - Display Configuration
7076

7177
func displayName(for item: Customer) -> String {

WooCommerce/Classes/Bookings/BookingFilters/ListSyncable.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ protocol ListSyncable {
3030
/// Creates the action to search items with keyword
3131
func createSearchAction(keyword: String, pageNumber: Int, pageSize: Int, completion: @escaping (Result<Bool, Error>) -> Void) -> Action
3232

33+
/// Creates the predicate for filtering search results
34+
/// - Parameter keyword: The search keyword
35+
/// - Returns: A predicate to filter storage objects by search results, or nil if search predicate is not needed
36+
func createSearchPredicate(keyword: String) -> NSPredicate?
37+
3338
// MARK: - Display Configuration
3439

3540
/// Returns the display name for an item

WooCommerce/Classes/Bookings/BookingFilters/SyncableListSelectorView.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,16 @@ private extension SyncableListSelectorView {
7777
}
7878
)
7979
.renderedIf(viewModel.searchQuery.isEmpty)
80+
.listRowSeparator(.hidden, edges: .top)
8081

81-
ForEach(items, id: \.self) { item in
82+
ForEach(Array(items.enumerated()), id: \.element) { (index, item) in
8283
optionRow(text: syncable.displayName(for: item),
8384
description: syncable.description(for: item),
8485
isSelected: selectedItems.contains(where: { $0 == syncable.filterItem(for: item) }),
8586
onSelection: { toggleSelectionIfPossible(for: item) })
87+
.if(index == 0 && viewModel.searchQuery.isNotEmpty) {
88+
$0.listRowSeparator(.hidden, edges: .top)
89+
}
8690
}
8791

8892
InfiniteScrollIndicator(showContent: viewModel.shouldShowBottomActivityIndicator)

WooCommerce/Classes/Bookings/BookingFilters/SyncableListSelectorViewModel.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,15 @@ final class SyncableListSelectorViewModel<Syncable: ListSyncable>: ObservableObj
8181

8282
/// Handles search query changes by resetting pagination and triggering new search
8383
private func handleSearchQueryChange(_ query: String) {
84-
syncState = .syncingFirstPage
8584
currentSearchKeyword = query
85+
86+
// Update the predicate to filter by search results if needed
87+
var predicates = [syncable.createPredicate()]
88+
if !query.isEmpty, let searchPredicate = syncable.createSearchPredicate(keyword: query) {
89+
predicates.append(searchPredicate)
90+
}
91+
resultsController.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
92+
8693
paginationTracker.syncFirstPage()
8794
}
8895

0 commit comments

Comments
 (0)