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
61 changes: 61 additions & 0 deletions BrowserKit/Sources/Common/Utilities/DynamicFont.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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 DynamicFont {
let textStyle: Font.TextStyle
let size: CGFloat
let sizeCap: CGFloat?
let weight: Font.Weight?
let design: Font.Design

init(textStyle: Font.TextStyle,
size: CGFloat,
sizeCap: CGFloat? = nil,
weight: Font.Weight? = nil,
design: Font.Design = .default) {
self.textStyle = textStyle
self.size = size
self.sizeCap = sizeCap
self.weight = weight
self.design = design
}
}

extension View {
public func font(_ dynamicFont: DynamicFont) -> some View {
modifier(DynamicFontModifier(dynamicFont: dynamicFont))
}
}

private struct DynamicFontModifier: ViewModifier {
@Environment(\.sizeCategory) var sizeCategory
let dynamicFont: DynamicFont

func body(content: Content) -> some View {
content.font(calculateFont())
}

private func calculateFont() -> Font {
let uiTextStyle = TextStyling.toUIFontTextStyle(dynamicFont.textStyle)
let uiWeight = dynamicFont.weight.map { TextStyling.toUIFontWeight($0) }

// UIFontMetrics.scaledFont automatically uses the current content size category
// from UIApplication.shared.preferredContentSizeCategory
let scaledFont = DefaultDynamicFontHelper.preferredFont(
withTextStyle: uiTextStyle,
size: dynamicFont.size,
sizeCap: dynamicFont.sizeCap,
weight: uiWeight
)

var font = Font.system(size: scaledFont.pointSize, design: dynamicFont.design)
if let weight = dynamicFont.weight {
font = font.weight(weight)
}

return font
}
}
34 changes: 14 additions & 20 deletions BrowserKit/Sources/Common/Utilities/DynamicFontHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,22 @@ public protocol DynamicFontHelper {
size: CGFloat
) -> UIFont

/// Returns a SwiftUI `Font` that scales with Dynamic Type.
/// Returns a SwiftUI `DynamicFont` that scales with Dynamic Type.
///
/// - Parameters:
/// - textStyle: The text style to base scaling on.
/// - size: The base font size (points).
/// - sizeCap: Optional maximum size (points) after scaling.
/// - weight: Optional font weight (uses styles default if `nil`).
/// - weight: Optional font weight (uses style's default if `nil`).
/// - design: The font design to apply.
static func preferredSwiftUIFont(withTextStyle textStyle: Font.TextStyle,
size: CGFloat,
sizeCap: CGFloat?,
weight: Font.Weight?,
design: Font.Design
) -> Font
) -> DynamicFont

/// Returns a bold SwiftUI `Font` that scales with Dynamic Type.
/// Returns a bold SwiftUI `DynamicFont` that scales with Dynamic Type.
///
/// - Parameters:
/// - textStyle: The text style to base scaling on.
Expand All @@ -51,7 +51,7 @@ public protocol DynamicFontHelper {
static func preferredBoldSwiftUIFont(withTextStyle textStyle: Font.TextStyle,
size: CGFloat,
design: Font.Design
) -> Font
) -> DynamicFont
}

public extension DynamicFontHelper {
Expand Down Expand Up @@ -116,25 +116,19 @@ public struct DefaultDynamicFontHelper: DynamicFontHelper {
size: CGFloat,
sizeCap: CGFloat? = nil,
weight: Font.Weight? = nil,
design: Font.Design = .default) -> Font {
var font = Font.system(textStyle, design: design)

if let weight = weight {
font = font.weight(weight)
}

// Apply size cap if specified
if let sizeCap = sizeCap {
// Create a custom font that respects the size cap
return Font.custom("", size: min(size, sizeCap), relativeTo: textStyle)
}

return font
design: Font.Design = .default) -> DynamicFont {
return DynamicFont(
textStyle: textStyle,
size: size,
sizeCap: sizeCap,
weight: weight,
design: design
)
}

public static func preferredBoldSwiftUIFont(withTextStyle textStyle: Font.TextStyle,
size: CGFloat,
design: Font.Design = .default) -> Font {
design: Font.Design = .default) -> DynamicFont {
return preferredSwiftUIFont(withTextStyle: textStyle,
size: size,
weight: .bold,
Expand Down
36 changes: 34 additions & 2 deletions BrowserKit/Sources/Common/Utilities/TextStyling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public struct TextStyling: Sendable {

// MARK: - SwiftUI Font Methods

public func scaledSwiftUIFont(sizeCap: CGFloat? = nil) -> Font {
public func scaledSwiftUIFont(sizeCap: CGFloat? = nil) -> DynamicFont {
return DefaultDynamicFontHelper.preferredSwiftUIFont(withTextStyle: toSwiftUITextStyle(textStyle),
size: size,
sizeCap: sizeCap,
Expand All @@ -48,7 +48,7 @@ public struct TextStyling: Sendable {
return Font.system(size: size, weight: toSwiftUIWeight(weight))
}

public func monospacedSwiftUIFont() -> Font {
public func monospacedSwiftUIFont() -> DynamicFont {
return DefaultDynamicFontHelper.preferredSwiftUIFont(withTextStyle: toSwiftUITextStyle(textStyle),
size: size,
weight: toSwiftUIWeight(weight),
Expand Down Expand Up @@ -86,4 +86,36 @@ public struct TextStyling: Sendable {
default: return .regular
}
}

public static func toUIFontTextStyle(_ style: Font.TextStyle) -> UIFont.TextStyle {
switch style {
case .largeTitle: return .largeTitle
case .title: return .title1
case .title2: return .title2
case .title3: return .title3
case .headline: return .headline
case .subheadline: return .subheadline
case .body: return .body
case .callout: return .callout
case .footnote: return .footnote
case .caption: return .caption1
case .caption2: return .caption2
@unknown default: return .body
}
}

public static func toUIFontWeight(_ style: Font.Weight) -> UIFont.Weight {
switch style {
case .ultraLight: return .ultraLight
case .thin: return .thin
case .light: return .light
case .regular: return .regular
case .medium: return .medium
case .semibold: return .semibold
case .bold: return .bold
case .heavy: return .heavy
case .black: return .black
default: return .regular
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,14 @@ public final class OnboardingFlowViewModel<ViewModel: OnboardingCardInfoModelPro
multipleChoiceSelections[cardName] = action
onMultipleChoiceActionTap(action, cardName)
}

public func skipOnboarding() {
guard !onboardingCards.isEmpty else {
return
}

let currentIndex = min(max(pageCount, 0), onboardingCards.count - 1)
let currentCardName = onboardingCards[currentIndex].name
onComplete(currentCardName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ struct OnboardingMultipleChoiceCardViewCompact<ViewModel: OnboardingCardInfoMode
.scrollBounceBehavior(basedOnSize: true)
VStack {
primaryButton
// Hidden spacer button to maintain consistent layout spacing
// when secondary button is not present
Button(" ", action: {})
.font(UX.CardView.secondaryActionFont)
.buttonStyle(.borderedProminent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ struct OnboardingViewCompact<ViewModel: OnboardingCardInfoModelProtocol>: View {
.padding(.bottom)
}

Button(action: skipOnboarding) {
Button(action: viewModel.skipOnboarding) {
Text(viewModel.skipText)
.bold()
.font(FXFontStyles.Bold.body.scaledSwiftUIFont(sizeCap: UX.Onboarding.Font.skipButtonSizeCap))
.foregroundColor(skipTextColor)
}
.padding(.trailing, UX.Onboarding.Spacing.standard)
Expand Down Expand Up @@ -99,14 +99,8 @@ struct OnboardingViewCompact<ViewModel: OnboardingCardInfoModelProtocol>: View {
}
}

private func skipOnboarding() {
let currentIndex = min(max(viewModel.pageCount, 0), viewModel.onboardingCards.count - 1)
let currentCardName = viewModel.onboardingCards[currentIndex].name
viewModel.onComplete(currentCardName)
}

private func applyTheme() {
let theme = themeManager.getCurrentTheme(for: windowUUID)
skipTextColor = Color(theme.colors.textInverted)
skipTextColor = Color(theme.colors.textOnDark)
}
}
5 changes: 4 additions & 1 deletion BrowserKit/Sources/OnboardingKit/Views/Helper/UX.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ enum UX {
static let imageHeight: CGFloat = 150
static let tosImageHeight: CGFloat = 70
static let cornerRadius: CGFloat = 20
static let secondaryButtonTopPadding: CGFloat = 8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice clean up 🧹

static let secondaryButtonBottomPadding: CGFloat = 24
static let primaryButtonWidthiPad: CGFloat = 313

Expand Down Expand Up @@ -84,6 +83,10 @@ enum UX {
static let vertical: CGFloat = 16
}

struct Font {
static let skipButtonSizeCap: CGFloat = 23
}

struct Layout {
static let logoSize = CGSize(width: 150, height: 150)
static let buttonCornerRadius: CGFloat = 12
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ struct OnboardingBasicCardViewRegular<ViewModel: OnboardingCardInfoModelProtocol
primaryButton
secondaryButton
}
.padding(.bottom, UX.CardView.secondaryButtonBottomPadding)
}
.onAppear {
applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID))
Expand Down Expand Up @@ -111,11 +112,10 @@ struct OnboardingBasicCardViewRegular<ViewModel: OnboardingCardInfoModelProtocol
)
},
accessibilityIdentifier: "\(viewModel.a11yIdRoot)SecondaryButton",
width: UX.CardView.primaryButtonWidthiPad,
windowUUID: windowUUID,
themeManager: themeManager
)
.padding(.top, UX.CardView.secondaryButtonTopPadding)
.padding(.bottom, UX.CardView.secondaryButtonBottomPadding)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,20 @@ struct OnboardingMultipleChoiceCardViewRegular<ViewModel: OnboardingCardInfoMode
.padding(UX.CardView.verticalPadding)
}
.scrollBounceBehavior(basedOnSize: true)
primaryButton
.padding(.bottom, UX.CardView.verticalPadding)

VStack {
primaryButton
// Hidden spacer button to maintain consistent layout spacing
// when secondary button is not present
Button(" ", action: {})
.font(UX.CardView.secondaryActionFont)
.buttonStyle(.borderedProminent)
.controlSize(.large)
.opacity(0)
.accessibilityHidden(true)
.disabled(true)
Comment on lines +60 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a comment on what this empty button with no action is for? 🤔

}
.padding(.bottom, UX.CardView.secondaryButtonBottomPadding)
}
.onAppear {
applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Common
struct OnboardingViewRegular<ViewModel: OnboardingCardInfoModelProtocol>: View {
@State private var cardBackgroundColor: Color = .clear
@StateObject private var viewModel: OnboardingFlowViewModel<ViewModel>
@State private var skipTextColor: Color = .clear

let windowUUID: WindowUUID
var themeManager: ThemeManager
Expand All @@ -26,7 +27,7 @@ struct OnboardingViewRegular<ViewModel: OnboardingCardInfoModelProtocol>: View {
}

var body: some View {
ZStack {
ZStack(alignment: .topTrailing) {
AnimatedGradientMetalView(windowUUID: windowUUID, themeManager: themeManager)
.edgesIgnoringSafeArea(.all)
SheetSizedCard {
Expand All @@ -37,6 +38,13 @@ struct OnboardingViewRegular<ViewModel: OnboardingCardInfoModelProtocol>: View {
.bridge
.cardBackground(cardBackgroundColor, cornerRadius: UX.CardView.cornerRadius)
}
Button(action: viewModel.skipOnboarding) {
Text(viewModel.skipText)
.bold()
.foregroundColor(skipTextColor)
}
.padding(.trailing, UX.Onboarding.Spacing.standard)
.bridge.glassButtonStyle()
}
.onAppear {
applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID))
Expand Down Expand Up @@ -76,5 +84,6 @@ struct OnboardingViewRegular<ViewModel: OnboardingCardInfoModelProtocol>: View {
private func applyTheme(theme: Theme) {
let color = theme.colors
cardBackgroundColor = Color(color.layer2)
skipTextColor = Color(theme.colors.textOnDark)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public final class DefaultSummarizerService: SummarizerService {
}

public func closeCurrentStreamedSession() {
streamContinuation?.finish()
streamContinuation?.finish(throwing: CancellationError())
streamContinuation = nil
}

Expand Down
10 changes: 6 additions & 4 deletions BrowserKit/Sources/SummarizeKit/UI/InfoView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import UIKit
import Common

struct InfoViewModel {
let title: NSAttributedString?
let titleA11yId: String
let content: NSAttributedString?
let contentA11yId: String
let actionButtonLabel: String
let actionButtonA11yId: String
let actionButtonA11yLabel: String
let actionButtonCallback: () -> Void
let linkCallback: (URL) -> Void
}
Expand Down Expand Up @@ -82,10 +83,11 @@ class InfoView: UIView,

func configure(viewModel: InfoViewModel) {
self.viewModel = viewModel
contentView.attributedText = viewModel.title
contentView.accessibilityIdentifier = viewModel.titleA11yId
contentView.attributedText = viewModel.content
contentView.accessibilityIdentifier = viewModel.contentA11yId
actionButton.configuration?.title = viewModel.actionButtonLabel
actionButton.accessibilityIdentifier = viewModel.actionButtonA11yId
actionButton.accessibilityLabel = viewModel.actionButtonA11yLabel
actionButton.addAction(UIAction(handler: { _ in
viewModel.actionButtonCallback()
}), for: .touchUpInside)
Expand Down
Loading
Loading