Skip to content
Open
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
35 changes: 35 additions & 0 deletions BrowserKit/Sources/ComponentLibrary/SwiftUI/Backport.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import SwiftUI

public struct Backport<Content> {
let content: Content
}

public extension Backport where Content: View {
@MainActor
@ViewBuilder
func glassButtonStyle() -> some View {
if #available(iOS 26, *) {
content.buttonStyle(.glass)
} else {
content.buttonStyle(.plain)
}
}

@MainActor
@ViewBuilder
func cardBackground(_ color: Color, cornerRadius: CGFloat) -> some View {
if #available(iOS 26, *) {
content.glassEffect(.regular.tint(color), in: .rect(cornerRadius: cornerRadius))
} else {
content.background(
RoundedRectangle(cornerRadius: cornerRadius)
.fill(color)
.accessibilityHidden(true)
)
}
}
}
112 changes: 112 additions & 0 deletions BrowserKit/Sources/ComponentLibrary/SwiftUI/OnboardingButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import SwiftUI
import Common

public struct OnboardingButton: View {
@State private var backgroundColor: Color = .clear
@State private var foregroundColor: Color = .clear
let title: String
let action: () -> Void
let accessibilityIdentifier: String
let buttonType: ButtonType
let width: CGFloat?
let windowUUID: WindowUUID
let themeManager: ThemeManager

public enum ButtonType {
case primary
case secondary
}

init(
title: String,
action: @escaping () -> Void,
accessibilityIdentifier: String,
buttonType: ButtonType,
width: CGFloat? = nil,
windowUUID: WindowUUID,
themeManager: ThemeManager
) {
self.title = title
self.action = action
self.accessibilityIdentifier = accessibilityIdentifier
self.buttonType = buttonType
self.width = width
self.windowUUID = windowUUID
self.themeManager = themeManager
}

public var body: some View {
Button(action: action) {
Text(title)
.font(.headline)
.frame(maxWidth: width ?? .infinity)
.foregroundColor(foregroundColor)
}
.accessibility(identifier: accessibilityIdentifier)
.buttonStyle(.borderedProminent)
.controlSize(.large)
.tint(backgroundColor)
.onAppear {
applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID))
}
.onReceive(NotificationCenter.default.publisher(for: .ThemeDidChange)) { notification in
guard let uuid = notification.windowUUID, uuid == windowUUID else { return }
applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID))
}
}

private func applyTheme(theme: Theme) {
let colors = theme.colors

switch buttonType {
case .primary:
backgroundColor = Color(colors.actionPrimary)
foregroundColor = Color(colors.textInverted)
case .secondary:
backgroundColor = Color(colors.actionSecondary)
foregroundColor = Color(colors.textSecondary)
}
}

public static func primary(
_ title: String,
action: @escaping () -> Void,
accessibilityIdentifier: String,
width: CGFloat? = nil,
windowUUID: WindowUUID,
themeManager: ThemeManager
) -> OnboardingButton {
OnboardingButton(
title: title,
action: action,
accessibilityIdentifier: accessibilityIdentifier,
buttonType: .primary,
width: width,
windowUUID: windowUUID,
themeManager: themeManager
)
}

public static func secondary(
_ title: String,
action: @escaping () -> Void,
accessibilityIdentifier: String,
width: CGFloat? = nil,
windowUUID: WindowUUID,
themeManager: ThemeManager
) -> OnboardingButton {
OnboardingButton(
title: title,
action: action,
accessibilityIdentifier: accessibilityIdentifier,
buttonType: .secondary,
width: width,
windowUUID: windowUUID,
themeManager: themeManager
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import SwiftUI

/// Extensions to SwiftUI's View protocol providing conditional view modifiers.
public extension View {
extension View {
/// Conditionally applies a transformation when the condition is true.
///
/// - Parameters:
Expand All @@ -18,11 +18,21 @@ public extension View {
/// .if(isHighlighted) { $0.foregroundColor(.red) }
/// ```
@ViewBuilder
func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
public func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
if condition {
transform(self)
} else {
self
}
}

public var isIOS26OrLater: Bool {
if #available(iOS 26.0, *) {
return true
} else {
return false
}
}

public var backport: Backport<Self> { Backport(content: self) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import ComponentLibrary
struct OnboardingBasicCardViewCompact<ViewModel: OnboardingCardInfoModelProtocol>: View {
@State private var textColor: Color = .clear
@State private var secondaryTextColor: Color = .clear
@State private var primaryBackgroundColor: Color = .clear
@State private var primaryForegroundColor: Color = .clear
@State private var cardBackgroundColor: Color = .clear
@Environment(\.sizeCategory)
private var sizeCategory
Expand Down Expand Up @@ -68,11 +66,7 @@ struct OnboardingBasicCardViewCompact<ViewModel: OnboardingCardInfoModelProtocol
}
.padding(UX.CardView.verticalPadding)
}
.background(
RoundedRectangle(cornerRadius: UX.CardView.cornerRadius)
.fill(cardBackgroundColor)
.accessibilityHidden(true)
)
.backport.cardBackground(cardBackgroundColor, cornerRadius: UX.CardView.cornerRadius)
}

var titleView: some View {
Expand Down Expand Up @@ -110,18 +104,18 @@ struct OnboardingBasicCardViewCompact<ViewModel: OnboardingCardInfoModelProtocol
var primaryButton: some View {
Group {
if #available(iOS 17.0, *) {
Button(
OnboardingButton.primary(
viewModel.buttons.primary.title,
action: {
onBottomButtonAction(
viewModel.buttons.primary.action,
viewModel.name
)
}
},
accessibilityIdentifier: "\(viewModel.a11yIdRoot)PrimaryButton",
windowUUID: windowUUID,
themeManager: themeManager
)
.font(UX.CardView.primaryActionFont)
.accessibility(identifier: "\(viewModel.a11yIdRoot)PrimaryButton")
.buttonStyle(PrimaryButtonStyle(theme: themeManager.getCurrentTheme(for: windowUUID)))
} else {
DragCancellablePrimaryButton(
title: viewModel.buttons.primary.title,
Expand All @@ -143,17 +137,18 @@ struct OnboardingBasicCardViewCompact<ViewModel: OnboardingCardInfoModelProtocol
if let secondary = viewModel.buttons.secondary {
Group {
if #available(iOS 17.0, *) {
Button(
OnboardingButton.secondary(
secondary.title,
action: {
onBottomButtonAction(
secondary.action,
viewModel.name
)
})
.font(UX.CardView.secondaryActionFont)
.accessibility(identifier: "\(viewModel.a11yIdRoot)SecondaryButton")
.buttonStyle(SecondaryButtonStyle(theme: themeManager.getCurrentTheme(for: windowUUID)))
},
accessibilityIdentifier: "\(viewModel.a11yIdRoot)SecondaryButton",
windowUUID: windowUUID,
themeManager: themeManager
)
} else {
DragCancellableSecondaryButton(
title: secondary.title,
Expand All @@ -176,7 +171,5 @@ struct OnboardingBasicCardViewCompact<ViewModel: OnboardingCardInfoModelProtocol
textColor = Color(color.textPrimary)
secondaryTextColor = Color(color.textSecondary)
cardBackgroundColor = Color(color.layer2)
primaryBackgroundColor = Color(color.actionPrimary)
primaryForegroundColor = Color(color.textInverted)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ struct OnboardingMultipleChoiceCardViewCompact<ViewModel: OnboardingCardInfoMode
.padding(.top, UX.CardView.titleTopPadding(for: geometry.size.height))
OnboardingSegmentedControl<ViewModel.OnboardingMultipleChoiceActionType>(
selection: $selectedAction,
items: viewModel.multipleChoiceButtons,
windowUUID: windowUUID,
themeManager: themeManager
items: viewModel.multipleChoiceButtons
)
.onChange(of: selectedAction) { newAction in
onMultipleChoiceAction(newAction, viewModel.name)
Expand All @@ -74,18 +72,15 @@ struct OnboardingMultipleChoiceCardViewCompact<ViewModel: OnboardingCardInfoMode
primaryButton
Button(" ", action: {})
.font(UX.CardView.secondaryActionFont)
.buttonStyle(SecondaryButtonStyle(theme: themeManager.getCurrentTheme(for: windowUUID)))
.buttonStyle(.borderedProminent)
.controlSize(.large)
.opacity(0)
.accessibilityHidden(true)
.disabled(true)
}
.padding(UX.CardView.verticalPadding)
}
.background(
RoundedRectangle(cornerRadius: UX.CardView.cornerRadius)
.fill(cardBackgroundColor)
.accessibilityHidden(true)
)
.backport.cardBackground(cardBackgroundColor, cornerRadius: UX.CardView.cornerRadius)
}

var titleView: some View {
Expand All @@ -103,18 +98,18 @@ struct OnboardingMultipleChoiceCardViewCompact<ViewModel: OnboardingCardInfoMode
var primaryButton: some View {
Group {
if #available(iOS 17.0, *) {
Button(
OnboardingButton.primary(
viewModel.buttons.primary.title,
action: {
onBottomButtonAction(
viewModel.buttons.primary.action,
viewModel.name
)
}
},
accessibilityIdentifier: "\(viewModel.a11yIdRoot)PrimaryButton",
windowUUID: windowUUID,
themeManager: themeManager
)
.font(UX.CardView.primaryActionFont)
.accessibility(identifier: "\(viewModel.a11yIdRoot)PrimaryButton")
.buttonStyle(PrimaryButtonStyle(theme: themeManager.getCurrentTheme(for: windowUUID)))
} else {
DragCancellablePrimaryButton(
title: viewModel.buttons.primary.title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ struct OnboardingViewCompact<ViewModel: OnboardingCardInfoModelProtocol>: View {
.bold()
.foregroundColor(skipTextColor)
}
.buttonStyle(.plain)
.padding(.trailing, UX.Onboarding.Spacing.standard)
.backport.glassButtonStyle()
}
.onAppear {
applyTheme()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,15 @@ import SwiftUI
import Common

struct OnboardingSegmentedControl<Action: Equatable & Hashable & Sendable>: View {
@State private var actionPrimary: Color = .clear
@Binding var selection: Action
let items: [OnboardingMultipleChoiceButtonModel<Action>]
let windowUUID: WindowUUID
var themeManager: ThemeManager

init(
selection: Binding<Action>,
items: [OnboardingMultipleChoiceButtonModel<Action>],
windowUUID: WindowUUID,
themeManager: ThemeManager
items: [OnboardingMultipleChoiceButtonModel<Action>]
) {
self._selection = selection
self.items = items
self.windowUUID = windowUUID
self.themeManager = themeManager
}

var body: some View {
Expand All @@ -33,13 +26,6 @@ struct OnboardingSegmentedControl<Action: Equatable & Hashable & Sendable>: View
}
}
.accessibilityElement(children: .contain)
.onAppear {
applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID))
}
.onReceive(NotificationCenter.default.publisher(for: .ThemeDidChange)) {
guard let uuid = $0.windowUUID, uuid == windowUUID else { return }
applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID))
}
}

@ViewBuilder
Expand Down Expand Up @@ -136,9 +122,4 @@ struct OnboardingSegmentedControl<Action: Equatable & Hashable & Sendable>: View
.accessibilityHidden(true)
}
}

private func applyTheme(theme: Theme) {
actionPrimary = Color(theme.colors.actionPrimary)
.opacity(UX.SegmentedControl.selectedColorOpacity)
}
}
Loading