diff --git a/apps/ios/Sources/Design/AgentProDreamingDestination.swift b/apps/ios/Sources/Design/AgentProDreamingDestination.swift index 8271ac0b0168..55cf655fc36c 100644 --- a/apps/ios/Sources/Design/AgentProDreamingDestination.swift +++ b/apps/ios/Sources/Design/AgentProDreamingDestination.swift @@ -65,8 +65,8 @@ struct AgentProDreamingDestination: View { OpenClawAdaptiveHeaderRow( title: "Dreaming", subtitle: self.dreamingDetail, - titleFont: .title3.weight(.semibold), - subtitleFont: .callout) + titleFont: OpenClawType.title3SemiBold, + subtitleFont: OpenClawType.subheadMedium) { OpenClawSidebarHeaderLeadingSlot(action: headerLeadingAction) } accessory: { diff --git a/apps/ios/Sources/Design/AgentProNodesDestination.swift b/apps/ios/Sources/Design/AgentProNodesDestination.swift index ab5c84a9c7b7..7e0acae6d4a8 100644 --- a/apps/ios/Sources/Design/AgentProNodesDestination.swift +++ b/apps/ios/Sources/Design/AgentProNodesDestination.swift @@ -39,8 +39,8 @@ struct AgentProNodesDestination: View { OpenClawAdaptiveHeaderRow( title: "Instances", subtitle: self.instancesDetail, - titleFont: .title3.weight(.semibold), - subtitleFont: .callout) + titleFont: OpenClawType.title3SemiBold, + subtitleFont: OpenClawType.subheadMedium) { OpenClawSidebarHeaderLeadingSlot(action: headerLeadingAction) } accessory: { diff --git a/apps/ios/Sources/Design/AgentProTab+Destinations.swift b/apps/ios/Sources/Design/AgentProTab+Destinations.swift index 3772a5f3fd15..8cda14d9a278 100644 --- a/apps/ios/Sources/Design/AgentProTab+Destinations.swift +++ b/apps/ios/Sources/Design/AgentProTab+Destinations.swift @@ -172,8 +172,8 @@ extension AgentProTab { OpenClawAdaptiveHeaderRow( title: title, subtitle: subtitle, - titleFont: .title3.weight(.semibold), - subtitleFont: .callout) + titleFont: OpenClawType.title3SemiBold, + subtitleFont: OpenClawType.subheadMedium) { OpenClawSidebarHeaderLeadingSlot(action: headerLeadingAction) } accessory: { diff --git a/apps/ios/Sources/Design/AgentProTab+Overview.swift b/apps/ios/Sources/Design/AgentProTab+Overview.swift index 7d6116816b3d..e2878a02e282 100644 --- a/apps/ios/Sources/Design/AgentProTab+Overview.swift +++ b/apps/ios/Sources/Design/AgentProTab+Overview.swift @@ -8,8 +8,8 @@ extension AgentProTab { OpenClawAdaptiveHeaderRow( title: self.headerTitle, subtitle: "\(self.sortedAgents.count) total", - titleFont: .system(size: 28, weight: .bold), - subtitleFont: .subheadline, + titleFont: OpenClawType.title2SemiBold, + subtitleFont: OpenClawType.subheadMedium, subtitleLineLimit: 1) { if let headerLeadingAction { @@ -64,7 +64,9 @@ extension AgentProTab { HStack(spacing: 10) { Picker("Agent status", selection: self.$agentRosterFilter) { ForEach(AgentRosterFilter.allCases) { filter in - Text(filter.title).tag(filter) + Text(filter.title) + .font(OpenClawType.captionSemiBold) + .tag(filter) } } .pickerStyle(.segmented) diff --git a/apps/ios/Sources/Design/CommandCenterTab.swift b/apps/ios/Sources/Design/CommandCenterTab.swift index 370a0ee64c09..ce007a55e870 100644 --- a/apps/ios/Sources/Design/CommandCenterTab.swift +++ b/apps/ios/Sources/Design/CommandCenterTab.swift @@ -120,8 +120,8 @@ struct CommandCenterTab: View { OpenClawAdaptiveHeaderRow( title: self.headerTitle, subtitle: self.gatewaySubtitle, - titleFont: .title3.weight(.semibold), - subtitleFont: .caption, + titleFont: OpenClawType.title3SemiBold, + subtitleFont: OpenClawType.caption, subtitleLineLimit: 1) { if let headerLeadingAction { diff --git a/apps/ios/Sources/Design/OpenClawDocsScreen.swift b/apps/ios/Sources/Design/OpenClawDocsScreen.swift index eb5f9eb83705..b136794d4d43 100644 --- a/apps/ios/Sources/Design/OpenClawDocsScreen.swift +++ b/apps/ios/Sources/Design/OpenClawDocsScreen.swift @@ -51,8 +51,8 @@ struct OpenClawDocsScreen: View { OpenClawAdaptiveHeaderRow( title: "Docs", subtitle: "Gateway setup, pairing, channels, and mobile node reference.", - titleFont: .headline, - subtitleFont: .caption) + titleFont: OpenClawType.headline, + subtitleFont: OpenClawType.caption) { HStack(alignment: .top, spacing: 12) { if let headerLeadingAction { diff --git a/apps/ios/Sources/Design/OpenClawTypography.swift b/apps/ios/Sources/Design/OpenClawTypography.swift index b4ecfc442574..565cebd12e0a 100644 --- a/apps/ios/Sources/Design/OpenClawTypography.swift +++ b/apps/ios/Sources/Design/OpenClawTypography.swift @@ -151,6 +151,39 @@ enum OpenClawType { Mono.semiBold, ] + @MainActor + static func installUIKitAppearance() { + let inlineNavigationTitleFont = self.scaledDisplayUIFont( + weight: Display.opticalSemiBold, + size: 17, + relativeTo: .headline) + let largeNavigationTitleFont = self.scaledDisplayUIFont( + weight: Display.heavyTitle, + size: 34, + relativeTo: .largeTitle) + let tabBarNormalFont = self.scaledBodyUIFont(weight: Body.medium, size: 11, relativeTo: .caption2) + let tabBarSelectedFont = self.scaledBodyUIFont(weight: Body.semiBold, size: 11, relativeTo: .caption2) + let segmentedNormalFont = self.scaledBodyUIFont(weight: Body.medium, size: 13, relativeTo: .footnote) + let segmentedSelectedFont = self.scaledBodyUIFont(weight: Body.semiBold, size: 13, relativeTo: .footnote) + + let navigationBar = UINavigationBar.appearance() + var titleAttributes = navigationBar.titleTextAttributes ?? [:] + titleAttributes[.font] = inlineNavigationTitleFont + navigationBar.titleTextAttributes = titleAttributes + + var largeTitleAttributes = navigationBar.largeTitleTextAttributes ?? [:] + largeTitleAttributes[.font] = largeNavigationTitleFont + navigationBar.largeTitleTextAttributes = largeTitleAttributes + + let tabBarItem = UITabBarItem.appearance() + tabBarItem.setTitleTextAttributes([.font: tabBarNormalFont], for: .normal) + tabBarItem.setTitleTextAttributes([.font: tabBarSelectedFont], for: .selected) + + let segmentedControl = UISegmentedControl.appearance() + segmentedControl.setTitleTextAttributes([.font: segmentedNormalFont], for: .normal) + segmentedControl.setTitleTextAttributes([.font: segmentedSelectedFont], for: .selected) + } + private enum Display { static let postScriptName = "RedHatDisplay-Regular" static let opticalSemiBold: CGFloat = 650 @@ -181,11 +214,11 @@ enum OpenClawType { size: CGFloat, relativeTo textStyle: UIFont.TextStyle) -> Font { - self.scaledVariableFont( - name: Display.postScriptName, - size: size, - relativeTo: textStyle, - variations: [self.fontWeightAxis: weight]) + Font( + self.scaledDisplayUIFont( + weight: weight, + size: size, + relativeTo: textStyle)) } private static func scaledBody( @@ -193,14 +226,11 @@ enum OpenClawType { size: CGFloat, relativeTo textStyle: UIFont.TextStyle) -> Font { - self.scaledVariableFont( - name: Body.postScriptName, - size: size, - relativeTo: textStyle, - variations: [ - self.fontWeightAxis: weight, - self.opticalSizeAxis: min(max(size, 14), 32), - ]) + Font( + self.scaledBodyUIFont( + weight: weight, + size: size, + relativeTo: textStyle)) } private static func scaledMono( @@ -211,16 +241,42 @@ enum OpenClawType { self.scaledFont(name: name, size: size, relativeTo: textStyle) } - private static func scaledVariableFont( + private static func scaledDisplayUIFont( + weight: CGFloat, + size: CGFloat, + relativeTo textStyle: UIFont.TextStyle) -> UIFont + { + self.scaledVariableUIFont( + name: Display.postScriptName, + size: size, + relativeTo: textStyle, + variations: [self.fontWeightAxis: weight]) + } + + private static func scaledBodyUIFont( + weight: CGFloat, + size: CGFloat, + relativeTo textStyle: UIFont.TextStyle) -> UIFont + { + self.scaledVariableUIFont( + name: Body.postScriptName, + size: size, + relativeTo: textStyle, + variations: [ + self.fontWeightAxis: weight, + self.opticalSizeAxis: min(max(size, 14), 32), + ]) + } + + private static func scaledVariableUIFont( name: String, size: CGFloat, relativeTo textStyle: UIFont.TextStyle, - variations: [NSNumber: CGFloat]) -> Font + variations: [NSNumber: CGFloat]) -> UIFont { guard UIFont(name: name, size: size) != nil else { let fallback = UIFont.systemFont(ofSize: size) - let scaledFallback = UIFontMetrics(forTextStyle: textStyle).scaledFont(for: fallback) - return Font(scaledFallback) + return UIFontMetrics(forTextStyle: textStyle).scaledFont(for: fallback) } let descriptor = UIFontDescriptor(fontAttributes: [ @@ -228,8 +284,7 @@ enum OpenClawType { kCTFontVariationAttribute as UIFontDescriptor.AttributeName: variations, ]) let base = UIFont(descriptor: descriptor, size: size) - let scaled = UIFontMetrics(forTextStyle: textStyle).scaledFont(for: base) - return Font(scaled) + return UIFontMetrics(forTextStyle: textStyle).scaledFont(for: base) } private static func scaledFont( diff --git a/apps/ios/Sources/Design/RootTabsPhoneControlHub.swift b/apps/ios/Sources/Design/RootTabsPhoneControlHub.swift index e97a983a9117..039329e8537e 100644 --- a/apps/ios/Sources/Design/RootTabsPhoneControlHub.swift +++ b/apps/ios/Sources/Design/RootTabsPhoneControlHub.swift @@ -33,6 +33,8 @@ struct RootTabsPhoneControlHub: View { } header: { if let title = self.sectionTitle(for: group) { Text(title) + .font(OpenClawType.captionSemiBold) + .foregroundStyle(.secondary) } } } diff --git a/apps/ios/Sources/Design/SettingsProTabSections.swift b/apps/ios/Sources/Design/SettingsProTabSections.swift index 736315acf6cc..c3514cbeffa5 100644 --- a/apps/ios/Sources/Design/SettingsProTabSections.swift +++ b/apps/ios/Sources/Design/SettingsProTabSections.swift @@ -145,7 +145,7 @@ extension SettingsProTab { route: .voice) } - Section("Device") { + Section { self.appearanceRow self.settingsListRow( icon: "stethoscope", @@ -166,6 +166,10 @@ extension SettingsProTab { icon: "info.circle", title: "About", route: .about) + } header: { + Text("Device") + .font(OpenClawType.captionSemiBold) + .foregroundStyle(.secondary) } Section { diff --git a/apps/ios/Sources/OpenClawApp.swift b/apps/ios/Sources/OpenClawApp.swift index 5ba4876ca8b8..ab4b8315b71f 100644 --- a/apps/ios/Sources/OpenClawApp.swift +++ b/apps/ios/Sources/OpenClawApp.swift @@ -628,6 +628,7 @@ struct OpenClawApp: App { init() { Self.installUncaughtExceptionLogger() GatewaySettingsStore.bootstrapPersistence() + OpenClawType.installUIKitAppearance() let appModel = NodeAppModel() #if DEBUG if ProcessInfo.processInfo.arguments.contains("--openclaw-reset-onboarding") { @@ -658,6 +659,7 @@ struct OpenClawApp: App { WindowGroup { RootTabs() .tint(OpenClawBrand.accent) + .font(OpenClawType.body) .preferredColorScheme(self.appearancePreference.colorScheme) .environment(self.appModel) .environment(self.appModel.voiceWake) diff --git a/apps/ios/Tests/RootTabsSourceGuardTests.swift b/apps/ios/Tests/RootTabsSourceGuardTests.swift index f691ea54f114..1606773abe8f 100644 --- a/apps/ios/Tests/RootTabsSourceGuardTests.swift +++ b/apps/ios/Tests/RootTabsSourceGuardTests.swift @@ -244,7 +244,7 @@ struct RootTabsSourceGuardTests { from: "var appearanceRow: some View", to: "var appearanceRowLabel: some View") - #expect(gatewayStatus.contains("HStack(spacing: 6)")) + #expect(gatewayStatus.contains("OpenClawStatusBadge(label: self.title, tone: self.tone)")) #expect(!gatewayStatus.contains("ProCapsule(")) #expect(!gatewayStatus.contains("Capsule()")) #expect(agentDestinationsSource.contains("List {")) @@ -259,7 +259,8 @@ struct RootTabsSourceGuardTests { #expect(!talkSource.contains("conversationCard")) #expect(!talkSource.contains("voiceModeCard")) #expect(!talkSource.contains("statusChip")) - #expect(settingsList.contains("Section(\"Device\")")) + #expect(settingsList.contains("Text(\"Device\")")) + #expect(settingsList.contains(".font(OpenClawType.captionSemiBold)")) #expect(!settingsList.contains("ProCard(")) #expect(settingsRow.contains("NavigationLink(value: route)")) #expect(!settingsRow.contains("chevron.right"))