Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ private extension AddOrderComponentsSection {
.renderedIf(couponLineViewModel.couponLineRows.isEmpty)
.padding()
.accessibilityIdentifier("add-coupon-button")
// Usage #2
.tooltip(isPresented: $shouldShowCouponsInfoTooltip,
toolTipTitle: Localization.couponsTooltipTitle,
toolTipDescription: Localization.couponsTooltipDescription,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import Yosemite
import SwiftUI
import TipKit

/// TipKit tip for explaining when product discounts are unavailable due to coupons
@available(iOS 17.0, *)
struct ProductDiscountTip: Tip {
// Forcing a new ID recreates invalidates the existing tip marked as `seen` by the system,
// since we're creating a new instance each time is toggled
var id: String = UUID().uuidString

var title: Text {
Text("Tipkit: Discounts unavailable")
.font(.body)
.foregroundColor(.white)
.fontWeight(.bold)
}

var message: Text? {
Text("To add a Product Discount, please remove all Coupons from your order")
.font(.body)
.foregroundColor(.gray)
}
}
/// Displays a single collapsible product row or grouped parent and child product rows
struct CollapsibleProductCard: View {
private let viewModel: CollapsibleProductCardViewModel
Expand Down Expand Up @@ -128,6 +149,17 @@ private struct CollapsibleProductRowCard: View {
ServiceLocator.featureFlagService.isFeatureFlagEnabled(.subscriptionsInOrderCreationUI)
}

@available(iOS 17.0, *)
private func configureTips() {
do {
// Passing a configuration is necessary, despite not enforced on compile-time
// If no config is passed, the tip will not render, and show no feedback of why
try Tips.configure([.displayFrequency(.immediate)])
} catch {
debugPrint("🍍 Configure TipKit failed")
}
}

init(viewModel: CollapsibleProductRowCardViewModel,
flow: WooAnalyticsEvent.Orders.Flow,
isLoading: Bool,
Expand Down Expand Up @@ -292,7 +324,7 @@ private extension CollapsibleProductRowCard {
}
}

private extension CollapsibleProductRowCard {
extension CollapsibleProductRowCard {
// Subscription details section. Renders all elements for a Subscription-type product
@ViewBuilder var subscriptionDetailsSection: some View {
VStack(spacing: Layout.subscriptionDetailsPadding) {
Expand Down Expand Up @@ -336,53 +368,147 @@ private extension CollapsibleProductRowCard {
}

@ViewBuilder var discountRow: some View {
HStack {
if !viewModel.hasDiscount || shouldDisallowDiscounts {
Button(Localization.addDiscountLabel) {
trackAddDiscountTapped()
onAddDiscount(viewModel.id)
}
.buttonStyle(PlusButtonStyle())
.disabled(shouldDisallowDiscounts || isLoading)
} else {
// Always render the row, overlay the tip if needed (iOS 17+), else fallback to .tooltip (iOS 16-)
if #available(iOS 17.0, *) {
ZStack {
// The row content
HStack {
Button(action: {
trackEditDiscountTapped()
onAddDiscount(viewModel.id)
}, label: {
if !viewModel.hasDiscount || shouldDisallowDiscounts {
Button(Localization.addDiscountLabel) {
trackAddDiscountTapped()
onAddDiscount(viewModel.id)
}
.buttonStyle(PlusButtonStyle())
.disabled(shouldDisallowDiscounts || isLoading)
} else {
HStack {
Text(Localization.discountLabel)
Image(uiImage: .pencilImage)
.resizable()
.frame(width: Layout.iconSize, height: Layout.iconSize)
Button(action: {
trackEditDiscountTapped()
onAddDiscount(viewModel.id)
}, label: {
HStack {
Text(Localization.discountLabel)
Image(uiImage: .pencilImage)
.resizable()
.frame(width: Layout.iconSize, height: Layout.iconSize)
}
})
.disabled(isLoading)
Spacer()
if let discountLabel = viewModel.discountLabel {
Text(minusSign + discountLabel)
.foregroundColor(.green)
.shimmering(active: isLoading)
}
}
.redacted(reason: shouldDisableDiscountEditing ? .placeholder : [] )
}
Group {
Spacer()
Button {
configureTips()
shouldShowInfoTooltip.toggle()
debugPrint("🍍 toggle tapped - shouldShowInfoTooltip: \(shouldShowInfoTooltip)")
} label: {
Image(systemName: "questionmark.circle")
.foregroundColor(Color(.wooCommercePurple(.shade60)))
}
})
.disabled(isLoading)
Spacer()
if let discountLabel = viewModel.discountLabel {
Text(minusSign + discountLabel)
.foregroundColor(.green)
.shimmering(active: isLoading)
}
.renderedIf(shouldDisallowDiscounts)
}
// Overlay the TipView and a mostly opaque black background if tooltip is visible
if shouldShowInfoTooltip {
Color(.systemBackground).opacity(0.01)
.ignoresSafeArea()
.contentShape(Rectangle())
.transition(.opacity)
.onTapGesture {
shouldShowInfoTooltip = false
}
TipView(ProductDiscountTip(), arrowEdge: .trailing)
.tipBackground(Color.black)
.padding()
.onTapGesture {
shouldShowInfoTooltip = false
}
}
// Redacts the discount editing row while product data is reloaded during remote sync.
// This avoids showing an out-of-date discount while hasn't synched
.redacted(reason: shouldDisableDiscountEditing ? .placeholder : [] )
}
Group {
Spacer()
Button {
shouldShowInfoTooltip.toggle()
} label: {
Image(systemName: "questionmark.circle")
.foregroundColor(Color(.wooCommercePurple(.shade60)))
} else {
// iOS 16 and below: use .tooltip as before
HStack {
if !viewModel.hasDiscount || shouldDisallowDiscounts {
Button(Localization.addDiscountLabel) {
trackAddDiscountTapped()
onAddDiscount(viewModel.id)
}
.buttonStyle(PlusButtonStyle())
.disabled(shouldDisallowDiscounts || isLoading)
} else {
HStack {
Button(action: {
trackEditDiscountTapped()
onAddDiscount(viewModel.id)
}, label: {
HStack {
Text(Localization.discountLabel)
Image(uiImage: .pencilImage)
.resizable()
.frame(width: Layout.iconSize, height: Layout.iconSize)
}
})
.disabled(isLoading)
Spacer()
if let discountLabel = viewModel.discountLabel {
Text(minusSign + discountLabel)
.foregroundColor(.green)
.shimmering(active: isLoading)
}
}
.redacted(reason: shouldDisableDiscountEditing ? .placeholder : [] )
}
Group {
Spacer()
Button {
shouldShowInfoTooltip.toggle()
debugPrint("🍍 toggle tapped - shouldShowInfoTooltip: \(shouldShowInfoTooltip)")
} label: {
Image(systemName: "questionmark.circle")
.foregroundColor(Color(.wooCommercePurple(.shade60)))
}
}
.renderedIf(shouldDisallowDiscounts)
}
.renderedIf(shouldDisallowDiscounts)
.tooltip(isPresented: $shouldShowInfoTooltip,
toolTipTitle: Localization.discountTooltipTitle,
toolTipDescription: Localization.discountTooltipDescription)
}
}
}

/// ViewModifier that handles both TipKit (iOS 17+) and custom tooltip (iOS 16-)
private struct CustomTooltipModifier: ViewModifier {
@Binding var shouldShowInfoTooltip: Bool

func body(content: Content) -> some View {
if #available(iOS 17.0, *) {
// TipKit
content
.overlay(alignment: .topTrailing) {
if shouldShowInfoTooltip {
TipView(ProductDiscountTip(), arrowEdge: .top)
.tipBackground(Color.black)
.onTapGesture {
shouldShowInfoTooltip = false
}
}
}
} else {
// iOS 16 and below continues using the custom tooltip
content
.tooltip(isPresented: $shouldShowInfoTooltip,
toolTipTitle: "Custom: Discounts unavailable",
toolTipDescription: "To add a Product Discount, please remove all Coupons from your order.")
}
.tooltip(isPresented: $shouldShowInfoTooltip,
toolTipTitle: Localization.discountTooltipTitle,
toolTipDescription: Localization.discountTooltipDescription)
}
}

Expand Down