Skip to content

Commit f7b22f1

Browse files
committed
upstream/develop changes merged into feature/4847-text-change-on-creating-variation
2 parents abaff29 + 7221929 commit f7b22f1

File tree

42 files changed

+970
-210
lines changed

Some content is hidden

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

42 files changed

+970
-210
lines changed

Experiments/Experiments/DefaultFeatureFlagService.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
2525
return buildConfig == .localDeveloper || buildConfig == .alpha
2626
case .orderListFilters:
2727
return buildConfig == .localDeveloper || buildConfig == .alpha
28-
case .filterProductsByCategory:
29-
return buildConfig == .localDeveloper || buildConfig == .alpha
3028
case .jetpackConnectionPackageSupport:
3129
return buildConfig == .localDeveloper || buildConfig == .alpha
3230
default:

Experiments/Experiments/FeatureFlag.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ public enum FeatureFlag: Int {
5050
///
5151
case orderListFilters
5252

53-
/// Allows to filter products by a product category, persisting it so the filter can remain after restarting the app
54-
///
55-
case filterProductsByCategory
56-
5753
/// Allows sites with plugins that include Jetpack Connection Package and without Jetpack-the-plugin to connect to the app
5854
///
5955
case jetpackConnectionPackageSupport

Hardware/Hardware/CardReader/StripeCardReader/StripeCardReaderService.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,12 @@ extension StripeCardReaderService: BluetoothReaderDelegate {
503503
error: CardReaderServiceError.softwareUpdate(underlyingError: UnderlyingError(with: error),
504504
batteryLevel: reader.batteryLevel?.doubleValue))
505505
)
506-
softwareUpdateSubject.send(.available)
506+
if let requiredDate = update?.requiredAt,
507+
requiredDate > Date() {
508+
softwareUpdateSubject.send(.available)
509+
} else {
510+
softwareUpdateSubject.send(.none)
511+
}
507512
} else {
508513
softwareUpdateSubject.send(.completed)
509514
softwareUpdateSubject.send(.none)

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
8.0
44
-----
5+
- [*] Product List: Add support for product filtering by category. [https://github.com/woocommerce/woocommerce-ios/pull/5388]
56
- [***] Push notifications are now supported for all connected stores. [https://github.com/woocommerce/woocommerce-ios/pull/5299]
67
- [*] Fix: in Settings > Switch Store, tapping "Dismiss" after selecting a different store does not switch stores anymore. [https://github.com/woocommerce/woocommerce-ios/pull/5359]
78

WooCommerce/Classes/Analytics/WooAnalytics.swift

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,7 @@ public extension WooAnalytics {
8080
/// - properties: a collection of properties related to the event
8181
///
8282
func track(_ stat: WooAnalyticsStat, withProperties properties: [AnyHashable: Any]?) {
83-
guard userHasOptedIn == true else {
84-
return
85-
}
86-
87-
if let updatedProperties = updatePropertiesIfNeeded(for: stat, properties: properties) {
88-
analyticsProvider.track(stat.rawValue, withProperties: updatedProperties)
89-
} else {
90-
analyticsProvider.track(stat.rawValue)
91-
}
83+
track(stat, properties: properties, error: nil)
9284
}
9385

9486
/// Track a specific event with an associated error (that is translated to properties)
@@ -98,16 +90,52 @@ public extension WooAnalytics {
9890
/// - error: the error to track
9991
///
10092
func track(_ stat: WooAnalyticsStat, withError error: Error) {
93+
track(stat, properties: nil, error: error)
94+
}
95+
96+
/// Track a specific event with associated properties and an associated error (that is translated to properties)
97+
///
98+
/// - Parameters:
99+
/// - stat: the event name
100+
/// - properties: a collection of properties related to the event
101+
/// - error: the error to track
102+
///
103+
func track(_ stat: WooAnalyticsStat, properties passedProperties: [AnyHashable: Any]?, error: Error?) {
101104
guard userHasOptedIn == true else {
102105
return
103106
}
104107

108+
let properties = combinedProperties(from: error, with: passedProperties)
109+
110+
if let updatedProperties = updatePropertiesIfNeeded(for: stat, properties: properties) {
111+
analyticsProvider.track(stat.rawValue, withProperties: updatedProperties)
112+
} else {
113+
analyticsProvider.track(stat.rawValue)
114+
}
115+
}
116+
117+
private func combinedProperties(from error: Error?, with passedProperties: [AnyHashable: Any]?) -> [AnyHashable: Any]? {
118+
let properties: [AnyHashable: Any]?
119+
let errorProperties = errorProperties(from: error)
120+
121+
if let passedProperties = passedProperties {
122+
properties = passedProperties.merging(errorProperties ?? [:], uniquingKeysWith: { current, _ in
123+
current
124+
})
125+
} else {
126+
properties = errorProperties
127+
}
128+
return properties
129+
}
130+
131+
private func errorProperties(from error: Error?) -> [AnyHashable: Any]? {
132+
guard let error = error else {
133+
return nil
134+
}
105135
let err = error as NSError
106-
let errorDictionary = [Constants.errorKeyCode: "\(err.code)",
107-
Constants.errorKeyDomain: err.domain,
108-
Constants.errorKeyDescription: err.description]
109-
let updatedProperties = updatePropertiesIfNeeded(for: stat, properties: errorDictionary)
110-
analyticsProvider.track(stat.rawValue, withProperties: updatedProperties)
136+
return [Constants.errorKeyCode: "\(err.code)",
137+
Constants.errorKeyDomain: err.domain,
138+
Constants.errorKeyDescription: err.description]
111139
}
112140
}
113141

WooCommerce/Classes/Extensions/UIImage+Woo.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ extension UIImage {
1313
return UIImage.gridicon(.addOutline)
1414
}
1515

16+
/// Alarm Bell Ring Image
17+
///
18+
static var alarmBellRingImage: UIImage {
19+
return UIImage(named: "icon-alarm-bell-ring")!
20+
}
21+
1622
/// Arrow Up Icon
1723
///
1824
static var arrowUp: UIImage {
@@ -25,6 +31,12 @@ extension UIImage {
2531
return UIImage.gridicon(.alignJustify)
2632
}
2733

34+
/// Analytics Image
35+
///
36+
static var analyticsImage: UIImage {
37+
return UIImage(named: "icon-analytics")!
38+
}
39+
2840
/// Notice Icon
2941
///
3042
static var noticeImage: UIImage {
@@ -732,6 +744,12 @@ extension UIImage {
732744
return UIImage(imageLiteralResourceName: "megaphone").imageFlippedForRightToLeftLayoutDirection()
733745
}
734746

747+
/// Multiple Users Image
748+
///
749+
static var multipleUsersImage: UIImage {
750+
return UIImage(named: "icon-multiple-users")!
751+
}
752+
735753
/// Error image
736754
///
737755
static var errorImage: UIImage {

WooCommerce/Classes/ServiceLocator/Analytics.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ protocol Analytics {
2929
///
3030
func track(_ stat: WooAnalyticsStat, withError error: Error)
3131

32+
/// Track a specific event with associated properties and an associated error (that is translated to properties)
33+
///
34+
/// - Parameters:
35+
/// - stat: the event name
36+
/// - properties: a collection of properties related to the event
37+
/// - error: the error to track
38+
///
39+
func track(_ stat: WooAnalyticsStat, properties: [AnyHashable: Any]?, error: Error?)
40+
3241
/// Refresh the tracking metadata for the currently logged-in or anonymous user.
3342
/// It's good to call this function after a user logs in or out of the app.
3443
///

WooCommerce/Classes/ViewRelated/CardPresentPayments/CardReaderConnectionController.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,9 +407,13 @@ private extension CardReaderConnectionController {
407407
let cancel = softwareUpdateCancelable.map { cancelable in
408408
return { [weak self] in
409409
self?.state = .cancel
410+
let analyticsProperties = [SoftwareUpdateTypeProperty.name: SoftwareUpdateTypeProperty.required.rawValue]
411+
ServiceLocator.analytics.track(.cardReaderSoftwareUpdateCancelTapped, withProperties: analyticsProperties)
410412
cancelable.cancel { result in
411413
if case .failure(let error) = result {
412414
print("=== error canceling software update: \(error)")
415+
} else {
416+
ServiceLocator.analytics.track(.cardReaderSoftwareUpdateCanceled, withProperties: analyticsProperties)
413417
}
414418
}
415419
}

WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ final class DashboardViewController: UIViewController {
7171
})
7272
}()
7373

74+
/// Bottom Jetpack benefits banner, shown when the site is connected to Jetpack without Jetpack-the-plugin.
75+
private lazy var bottomJetpackBenefitsBannerController = JetpackBenefitsBannerHostingController()
76+
private var contentBottomToJetpackBenefitsBannerConstraint: NSLayoutConstraint?
77+
private var contentBottomToContainerConstraint: NSLayoutConstraint?
78+
private var isJetpackBenefitsBannerShown: Bool {
79+
bottomJetpackBenefitsBannerController.view?.superview != nil
80+
}
81+
7482
/// A spacer view to add a margin below the top banner (between the banner and dashboard UI)
7583
///
7684
private lazy var spacerView: UIView = {
@@ -100,6 +108,7 @@ final class DashboardViewController: UIViewController {
100108
configureNavigation()
101109
configureView()
102110
configureDashboardUIContainer()
111+
configureBottomJetpackBenefitsBanner()
103112
observeSiteForUIUpdates()
104113
}
105114

@@ -174,8 +183,8 @@ private extension DashboardViewController {
174183
contentView.topAnchor.constraint(equalTo: headerStackView.bottomAnchor),
175184
contentView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
176185
contentView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
177-
contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
178186
])
187+
contentBottomToContainerConstraint = contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
179188
}
180189

181190
private func configureNavigationItem() {
@@ -210,6 +219,22 @@ private extension DashboardViewController {
210219
view.pinSubviewToSafeArea(containerView)
211220
}
212221

222+
func configureBottomJetpackBenefitsBanner() {
223+
bottomJetpackBenefitsBannerController.setActions { [weak self] in
224+
guard let self = self else { return }
225+
let benefitsController = JetpackBenefitsHostingController()
226+
benefitsController.setActions {
227+
// TODO: 5370 - Navigate to install Jetpack
228+
} dismissAction: { [weak self] in
229+
self?.dismiss(animated: true, completion: nil)
230+
}
231+
self.present(benefitsController, animated: true, completion: nil)
232+
} dismissAction: { [weak self] in
233+
// TODO: 5362 - Persist dismiss state per site
234+
self?.hideJetpackBenefitsBanner()
235+
}
236+
}
237+
213238
func reloadDashboardUIStatsVersion(forced: Bool) {
214239
dashboardUIFactory.reloadDashboardUI(onUIUpdate: { [weak self] dashboardUI in
215240
if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.largeTitles) {
@@ -303,6 +328,50 @@ private extension DashboardViewController {
303328
updatedDashboardUI.displaySyncingError = { [weak self] in
304329
self?.showTopBannerView()
305330
}
331+
332+
// Bottom banner
333+
// TODO: 5362 & 5368 - Display banner for JCP sites and if the banner has not been dismissed before.
334+
let shouldShowJetpackBenefitsBanner = ServiceLocator.featureFlagService.isFeatureFlagEnabled(.jetpackConnectionPackageSupport)
335+
if shouldShowJetpackBenefitsBanner {
336+
showJetpackBenefitsBanner(contentView: contentView)
337+
} else {
338+
hideJetpackBenefitsBanner()
339+
}
340+
}
341+
342+
func showJetpackBenefitsBanner(contentView: UIView) {
343+
hideJetpackBenefitsBanner()
344+
guard let banner = bottomJetpackBenefitsBannerController.view else {
345+
return
346+
}
347+
contentBottomToContainerConstraint?.isActive = false
348+
349+
addChild(bottomJetpackBenefitsBannerController)
350+
containerView.addSubview(banner)
351+
bottomJetpackBenefitsBannerController.didMove(toParent: self)
352+
353+
banner.translatesAutoresizingMaskIntoConstraints = false
354+
355+
// The banner height is calculated in `viewDidLayoutSubviews` to support rotation.
356+
let contentBottomToJetpackBenefitsBannerConstraint = banner.topAnchor.constraint(equalTo: contentView.bottomAnchor)
357+
self.contentBottomToJetpackBenefitsBannerConstraint = contentBottomToJetpackBenefitsBannerConstraint
358+
359+
NSLayoutConstraint.activate([
360+
contentBottomToJetpackBenefitsBannerConstraint,
361+
banner.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
362+
banner.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
363+
// Pins from the safe area layout bottom to accommodate offline banner.
364+
banner.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
365+
])
366+
}
367+
368+
func hideJetpackBenefitsBanner() {
369+
contentBottomToJetpackBenefitsBannerConstraint?.isActive = false
370+
contentBottomToContainerConstraint?.isActive = true
371+
if isJetpackBenefitsBannerShown {
372+
bottomJetpackBenefitsBannerController.view?.removeFromSuperview()
373+
remove(bottomJetpackBenefitsBannerController)
374+
}
306375
}
307376
}
308377

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import SwiftUI
2+
3+
/// Displays a Jetpack benefit with icon, title, and subtitle.
4+
struct JetpackBenefitItem: View {
5+
let title: String
6+
let subtitle: String
7+
let icon: UIImage
8+
9+
// Tracks the scale of the view due to accessibility changes
10+
@ScaledMetric private var scale: CGFloat = 1.0
11+
12+
var body: some View {
13+
HStack(spacing: Layout.horizontalSpacing) {
14+
Circle()
15+
.frame(width: Layout.circleDimension * scale, height: Layout.circleDimension * scale, alignment: .center)
16+
.foregroundColor(Color(.gray(.shade0)))
17+
.overlay(
18+
Image(uiImage: icon)
19+
.resizable()
20+
.frame(width: Layout.iconDimension, height: Layout.iconDimension, alignment: .center)
21+
)
22+
VStack(alignment: .leading, spacing: Layout.verticalTextSpacing) {
23+
Text(title).headlineStyle()
24+
Text(subtitle).subheadlineStyle()
25+
}
26+
Spacer()
27+
}
28+
}
29+
}
30+
31+
private extension JetpackBenefitItem {
32+
enum Layout {
33+
static let circleDimension = CGFloat(40)
34+
static let iconDimension = CGFloat(20)
35+
static let horizontalSpacing = CGFloat(16)
36+
static let verticalTextSpacing = CGFloat(2)
37+
}
38+
}
39+
40+
struct JetpackBenefitRow_Previews: PreviewProvider {
41+
static var previews: some View {
42+
JetpackBenefitItem(title: "Push Notifications with a longer title",
43+
subtitle: "Get push notifications for new orders, reviews, etc. delivered to your device.",
44+
icon: .cameraImage)
45+
.environment(\.sizeCategory, .extraExtraExtraLarge)
46+
.previewLayout(.sizeThatFits)
47+
JetpackBenefitItem(title: "Short",
48+
subtitle: "Short subtitle.",
49+
icon: .cameraImage)
50+
.previewLayout(.sizeThatFits)
51+
}
52+
}

0 commit comments

Comments
 (0)