Skip to content

Commit ae0c504

Browse files
Merge branch 'trunk' into issue/14419-jetpack-setup-account-creation-1
2 parents 99ad008 + 6448eef commit ae0c504

File tree

60 files changed

+280
-230
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+280
-230
lines changed

.buildkite/commands/checkout-release-branch.sh

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
11
#!/bin/bash -eu
22

3-
echo "--- :git: Checkout Release Branch"
4-
5-
# Note: `BUILDKITE_RELEASE_VERSION` is the legacy environment variable passed to Buildkite by ReleaseV2.
6-
# It used the `BUILDKITE_` prefix so it was not filtered out when passed to the MacOS VMs, due to how `hostmgr` works.
7-
# This is considered legacy: we should eventually remove all use of custom `BUILDKITE_` variables, and instead
8-
# resolve the value of those sooner (i.e. in the YML pipeline) then pass it as parameter to the `.sh` calls instead.
3+
RELEASE_VERSION="${1:?RELEASE_VERSION parameter missing}"
94

10-
# Use the provided argument if there's one, otherwise fall back to the legacy BUILDKITE_RELEASE_VERSION
11-
RELEASE_VERSION=${1:-$BUILDKITE_RELEASE_VERSION}
12-
13-
if [[ -z "${RELEASE_VERSION}" ]]; then
14-
echo "RELEASE_VERSION is not set and BUILDKITE_RELEASE_VERSION is not available."
15-
exit 1
16-
fi
5+
echo "--- :git: Checkout Release Branch"
176

187
# Buildkite, by default, checks out a specific commit. For many release actions, we need to be
198
# on a release branch instead.

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- [internal] Allow login for WPCOM suspended sites [https://github.com/woocommerce/woocommerce-ios/pull/14257]
1313
- [*] Menu tab > Payments: in the "deposits" summary view at the top, all mentions of "deposits/deposit/deposited" have been renamed to "payouts/payout/paid out" respectively to match the renaming in web. [https://github.com/woocommerce/woocommerce-ios/pull/14402]
1414
- [*] Receipts: Added message confirming receipt sent to customer after successful payment. [https://github.com/woocommerce/woocommerce-ios/pull/14390]
15+
- [internal] Simplifies App Icon by only using a single image for all supported platforms. [https://github.com/woocommerce/woocommerce-ios/pull/14429]
1516

1617
21.1
1718
-----

WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ import Foundation
33
import protocol Yosemite.POSItem
44
import protocol Yosemite.POSItemProvider
55
import protocol WooFoundation.Analytics
6+
import enum Yosemite.POSProductProviderError
67

78
protocol PointOfSaleAggregateModelProtocol {
89
var orderStage: PointOfSaleOrderStage { get }
910

11+
var cardReaderConnectionStatus: CardPresentPaymentReaderConnectionStatus { get }
12+
func connectCardReader()
13+
func disconnectCardReader()
14+
1015
@available(*, deprecated, message: "`allItems` is due for removal, use `itemListState` instead.")
1116
var allItems: [POSItem] { get }
1217
var itemListState: ItemListState { get }
13-
1418
func loadInitialItems() async
1519
func loadNextItems() async
1620
func reload() async
@@ -27,37 +31,47 @@ protocol PointOfSaleAggregateModelProtocol {
2731
class PointOfSaleAggregateModel: ObservableObject, PointOfSaleAggregateModelProtocol {
2832
@Published private(set) var orderStage: PointOfSaleOrderStage = .building
2933

34+
@Published private(set) var cardReaderConnectionStatus: CardPresentPaymentReaderConnectionStatus = .disconnected
35+
3036
@Published private(set) var allItems: [POSItem] = []
3137
@Published private(set) var itemListState: ItemListState = .initialLoading
3238

3339
@Published private(set) var cart: [CartItem] = []
3440

3541
private let itemProvider: POSItemProvider
42+
private let cardPresentPaymentService: CardPresentPaymentFacade
3643
private let analytics: Analytics
3744

3845
private var currentPage: Int = Constants.initialPage
46+
private var mightHaveMorePages: Bool = true
3947

4048
init(itemProvider: POSItemProvider,
49+
cardPresentPaymentService: CardPresentPaymentFacade,
4150
analytics: Analytics = ServiceLocator.analytics) {
4251
self.itemProvider = itemProvider
52+
self.cardPresentPaymentService = cardPresentPaymentService
4353
self.analytics = analytics
54+
publishCardReaderConnectionStatus()
4455
}
4556
}
4657

4758
// MARK: - ItemList
4859
extension PointOfSaleAggregateModel {
4960
@MainActor
5061
func loadInitialItems() async {
62+
mightHaveMorePages = true
5163
itemListState = .initialLoading
5264
try? await load(pageNumber: Constants.initialPage)
5365
}
5466

5567
@MainActor
5668
func loadNextItems() async {
5769
do {
70+
guard mightHaveMorePages else {
71+
return
72+
}
5873
itemListState = .loading(allItems)
59-
// TODO: Optimize API calls. gh-14186
60-
// If there are no more pages to fetch, we can avoid the next call.
74+
6175
let nextPage = currentPage + 1
6276
try await load(pageNumber: nextPage)
6377
currentPage = nextPage
@@ -70,6 +84,7 @@ extension PointOfSaleAggregateModel {
7084
func reload() async {
7185
allItems.removeAll()
7286
currentPage = Constants.initialPage
87+
mightHaveMorePages = true
7388
itemListState = .loading(allItems)
7489
try? await load(pageNumber: currentPage)
7590
}
@@ -78,6 +93,13 @@ extension PointOfSaleAggregateModel {
7893
private func load(pageNumber: Int) async throws {
7994
do {
8095
try await fetchItems(pageNumber: pageNumber)
96+
97+
mightHaveMorePages = true
98+
updateItemListStateAfterLoadAttempt()
99+
} catch POSProductProviderError.pageOutOfRange {
100+
mightHaveMorePages = false
101+
updateItemListStateAfterLoadAttempt()
102+
throw POSProductProviderError.pageOutOfRange
81103
} catch {
82104
itemListState = .error(PointOfSaleErrorState.errorOnLoadingProducts())
83105
throw error
@@ -90,9 +112,10 @@ extension PointOfSaleAggregateModel {
90112
let uniqueNewItems = newItems.filter { newItem in
91113
!allItems.contains(where: { $0.productID == newItem.productID })
92114
}
93-
94115
allItems.append(contentsOf: uniqueNewItems)
116+
}
95117

118+
private func updateItemListStateAfterLoadAttempt() {
96119
if allItems.count == 0 {
97120
itemListState = .empty
98121
} else {
@@ -133,6 +156,27 @@ extension PointOfSaleAggregateModel {
133156
}
134157
}
135158

159+
// MARK: - Card payments
160+
161+
extension PointOfSaleAggregateModel {
162+
private func publishCardReaderConnectionStatus() {
163+
// When adopting Observable, we can use `assign(to: on:)` here instead
164+
cardPresentPaymentService.readerConnectionStatusPublisher.assign(to: &$cardReaderConnectionStatus)
165+
}
166+
167+
func connectCardReader() {
168+
Task { @MainActor in
169+
_ = try await cardPresentPaymentService.connectReader(using: .bluetooth)
170+
}
171+
}
172+
173+
func disconnectCardReader() {
174+
Task { @MainActor in
175+
await cardPresentPaymentService.disconnectReader()
176+
}
177+
}
178+
}
179+
136180
private extension PointOfSaleAggregateModel {
137181
enum Constants {
138182
static let initialPage: Int = 1

WooCommerce/Classes/POS/Presentation/CardReaderConnection/CardReaderConnectionStatusView.swift

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,10 @@ import SwiftUI
22

33
struct CardReaderConnectionStatusView: View {
44
@Environment(\.posBackgroundAppearance) var backgroundAppearance
5-
@ObservedObject private var connectionViewModel: CardReaderConnectionViewModel
5+
@EnvironmentObject var posModel: PointOfSaleAggregateModel
66
@ScaledMetric private var scale: CGFloat = 1.0
77
@Environment(\.isEnabled) var isEnabled
88

9-
init(connectionViewModel: CardReaderConnectionViewModel) {
10-
self.connectionViewModel = connectionViewModel
11-
}
12-
139
@ViewBuilder
1410
private func circleIcon(with color: Color) -> some View {
1511
Image(systemName: "circle.fill")
@@ -21,11 +17,11 @@ struct CardReaderConnectionStatusView: View {
2117

2218
var body: some View {
2319
Group {
24-
switch connectionViewModel.connectionStatus {
20+
switch posModel.cardReaderConnectionStatus {
2521
case .connected:
2622
Menu {
2723
Button {
28-
connectionViewModel.disconnectReader()
24+
posModel.disconnectCardReader()
2925
} label: {
3026
Text(Localization.disconnectCardReader)
3127
}
@@ -44,7 +40,7 @@ struct CardReaderConnectionStatusView: View {
4440
progressIndicatingCardReaderStatus(title: Localization.pleaseWait)
4541
case .disconnected:
4642
Button {
47-
connectionViewModel.connectReader()
43+
posModel.connectCardReader()
4844
} label: {
4945
HStack(spacing: Constants.buttonImageAndTextSpacing) {
5046
circleIcon(with: Color(.wooCommerceAmber(.shade60)))
@@ -162,7 +158,7 @@ private extension CardReaderConnectionStatusView {
162158

163159
#Preview {
164160
VStack {
165-
CardReaderConnectionStatusView(connectionViewModel: .init(cardPresentPayment: CardPresentPaymentPreviewService()))
161+
CardReaderConnectionStatusView()
166162
}
167163
}
168164

WooCommerce/Classes/POS/Presentation/CardReaderConnection/CardReaderConnectionViewModel.swift

Lines changed: 0 additions & 40 deletions
This file was deleted.

WooCommerce/Classes/POS/Presentation/CartView.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,12 @@ import class WooFoundation.MockAnalyticsProviderPreview
256256
cardPresentPaymentService: CardPresentPaymentPreviewService(),
257257
currencyFormatter: .init(currencySettings: .init()),
258258
paymentState: .acceptingCard)
259-
let cartViewModel = CartViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview()))
260-
let itemsListViewModel = ItemListViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview()))
261-
let dashboardViewModel = PointOfSaleDashboardViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview()),
262-
cardPresentPaymentService: CardPresentPaymentPreviewService(),
259+
let cartViewModel = CartViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview(),
260+
cardPresentPaymentService: CardPresentPaymentPreviewService()))
261+
let itemsListViewModel = ItemListViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview(),
262+
cardPresentPaymentService: CardPresentPaymentPreviewService()))
263+
let dashboardViewModel = PointOfSaleDashboardViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview(),
264+
cardPresentPaymentService: CardPresentPaymentPreviewService()),
263265
totalsViewModel: totalsViewModel,
264266
cartViewModel: cartViewModel,
265267
itemListViewModel: itemsListViewModel,

WooCommerce/Classes/POS/Presentation/ItemListView.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ struct ItemListView: View {
88

99
@EnvironmentObject var posModel: PointOfSaleAggregateModel
1010

11+
@State private var lastScrollPosition: CGFloat = 0
12+
1113
init(viewModel: ItemListViewModel) {
1214
self.viewModel = viewModel
1315
}
@@ -144,12 +146,13 @@ private extension ItemListView {
144146
if posModel.itemListState.isLoadingAfterInitialLoad {
145147
return
146148
}
147-
let viewHeight = UIScreen.main.bounds.height
148-
if maxY < viewHeight {
149+
let threshold = Constants.viewHeight * Constants.scrollThresholdMultiplier
150+
if maxY < threshold && maxY < lastScrollPosition {
149151
Task {
150152
await viewModel.loadNextItems()
151153
}
152154
}
155+
lastScrollPosition = maxY
153156
}
154157
})
155158
}
@@ -210,6 +213,8 @@ private extension ItemListView {
210213
static let iconPadding: CGFloat = 26
211214
static let itemListPadding: CGFloat = 16
212215
static let bannerCardPadding: CGFloat = 16
216+
static let viewHeight: CGFloat = UIScreen.main.bounds.height
217+
static let scrollThresholdMultiplier: CGFloat = 1.7
213218
}
214219

215220
enum Localization {
@@ -248,6 +253,7 @@ private extension ItemListView {
248253

249254
#if DEBUG
250255
#Preview {
251-
ItemListView(viewModel: ItemListViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview())))
256+
ItemListView(viewModel: ItemListViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview(),
257+
cardPresentPaymentService: CardPresentPaymentPreviewService())))
252258
}
253259
#endif

WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ struct POSFloatingControlView: View {
4242
.cornerRadius(Constants.cornerRadius)
4343
.disabled(viewModel.isExitPOSDisabled)
4444

45-
CardReaderConnectionStatusView(connectionViewModel: viewModel.cardReaderConnectionViewModel)
45+
CardReaderConnectionStatusView()
4646
.foregroundStyle(fontColor)
4747
.background(backgroundColor)
4848
.cornerRadius(Constants.cornerRadius)

WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,12 @@ import class WooFoundation.MockAnalyticsProviderPreview
211211
cardPresentPaymentService: CardPresentPaymentPreviewService(),
212212
currencyFormatter: .init(currencySettings: .init()),
213213
paymentState: .acceptingCard)
214-
let cartVM = CartViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview()))
215-
let itemsListVM = ItemListViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview()))
216-
let posVM = PointOfSaleDashboardViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview()),
217-
cardPresentPaymentService: CardPresentPaymentPreviewService(),
214+
let cartVM = CartViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview(),
215+
cardPresentPaymentService: CardPresentPaymentPreviewService()))
216+
let itemsListVM = ItemListViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview(),
217+
cardPresentPaymentService: CardPresentPaymentPreviewService()))
218+
let posVM = PointOfSaleDashboardViewModel(posModel: PointOfSaleAggregateModel(itemProvider: POSItemProviderPreview(),
219+
cardPresentPaymentService: CardPresentPaymentPreviewService()),
218220
totalsViewModel: totalsVM,
219221
cartViewModel: cartVM,
220222
itemListViewModel: itemsListVM,

WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ struct PointOfSaleEntryPointView: View {
2222
analytics: Analytics) {
2323
self.onPointOfSaleModeActiveStateChange = onPointOfSaleModeActiveStateChange
2424

25-
let posModel = PointOfSaleAggregateModel(itemProvider: itemProvider)
25+
let posModel = PointOfSaleAggregateModel(itemProvider: itemProvider,
26+
cardPresentPaymentService: cardPresentPaymentService)
2627
let totalsViewModel = TotalsViewModel(orderService: orderService,
2728
cardPresentPaymentService: cardPresentPaymentService,
2829
currencyFormatter: currencyFormatter,
@@ -34,7 +35,6 @@ struct PointOfSaleEntryPointView: View {
3435
self._posModel = StateObject(wrappedValue: posModel)
3536
self._viewModel = StateObject(wrappedValue: PointOfSaleDashboardViewModel(
3637
posModel: posModel,
37-
cardPresentPaymentService: cardPresentPaymentService,
3838
totalsViewModel: totalsViewModel,
3939
cartViewModel: cartViewModel,
4040
itemListViewModel: itemListViewModel,

0 commit comments

Comments
 (0)