mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
Build the app and notification extension for Mac Catalyst with the Mac idiom, so Macs get a 1:1 native window instead of the scaled iPad app. The Mac build keeps the run.hapi.app bundle ID and adds Mac entitlements (sandbox, network, microphone, user-selected files, Mac APNs). - Always use the split on Mac with a sidebar list style, denser rows, 14pt reading text, a hidden duplicate titlebar title, a minimum window size and page-sized sheets; open web links in the default browser and hide the unavailable live QR scanner. - Add menu bar / hardware-keyboard commands: New Session, Settings, Toggle Sidebar, a Session menu (settings, files, rename, export, stop, previous/next session) and a guide link. - Composer: Return sends on Mac (Shift/Option-Return for a newline, IME untouched, first suggestion picked); dropped files and pasted images or copied files become attachments on all platforms. - CI builds Mac Catalyst and runs the navigation, split and transcript geometry tests there; README documents the Mac build.
103 lines
3.9 KiB
Swift
103 lines
3.9 KiB
Swift
import SwiftUI
|
|
|
|
/// Resolved once at the presentation root; never scale these values again.
|
|
/// Separate from color palettes so light, dark and OLED have identical metrics.
|
|
public struct HapiTypography: Equatable, Sendable {
|
|
public var bodyScale: CGFloat
|
|
public var codeScale: CGFloat
|
|
public var captionScale: CGFloat
|
|
public var boldText: Bool
|
|
|
|
public init(bodyScale: CGFloat = 1, codeScale: CGFloat = 1,
|
|
captionScale: CGFloat = 1, boldText: Bool = false) {
|
|
self.bodyScale = bodyScale
|
|
self.codeScale = codeScale
|
|
self.captionScale = captionScale
|
|
self.boldText = boldText
|
|
}
|
|
|
|
public var bodySize: CGFloat { 16 * bodyScale }
|
|
public var codeSize: CGFloat { 14 * codeScale }
|
|
public var captionSize: CGFloat { 12 * captionScale }
|
|
public var inlineCodeSize: CGFloat { 15 * bodyScale }
|
|
public var bodyLineSpacing: CGFloat { 3 * bodyScale }
|
|
public var codeLineSpacing: CGFloat { 2 * codeScale }
|
|
public var bodyFont: Font { .system(size: bodySize, weight: regularWeight) }
|
|
public var codeFont: Font { .system(size: codeSize, weight: regularWeight, design: .monospaced) }
|
|
public var captionFont: Font { .system(size: captionSize, weight: regularWeight) }
|
|
public var inlineCodeFont: Font { .system(size: inlineCodeSize, weight: regularWeight, design: .monospaced) }
|
|
public var captionMonoFont: Font { .system(size: captionSize, weight: regularWeight, design: .monospaced) }
|
|
|
|
private var regularWeight: Font.Weight { boldText ? .semibold : .regular }
|
|
|
|
public func headingSize(_ level: Int) -> CGFloat {
|
|
let base: CGFloat = switch level {
|
|
case 1: 24
|
|
case 2: 20
|
|
case 3: 18
|
|
default: 17
|
|
}
|
|
return base * bodyScale
|
|
}
|
|
|
|
public func headingFont(_ level: Int) -> Font {
|
|
.system(size: headingSize(level), weight: boldText ? .bold : .semibold)
|
|
}
|
|
}
|
|
|
|
private struct HapiTypographyKey: EnvironmentKey {
|
|
static let defaultValue = HapiTypography()
|
|
}
|
|
|
|
public extension EnvironmentValues {
|
|
var hapiTypography: HapiTypography {
|
|
get { self[HapiTypographyKey.self] }
|
|
set { self[HapiTypographyKey.self] = newValue }
|
|
}
|
|
}
|
|
|
|
private struct HapiTypographyModifier: ViewModifier {
|
|
@ScaledMetric(relativeTo: .body) private var bodyScale: CGFloat = 1
|
|
@ScaledMetric(relativeTo: .footnote) private var codeScale: CGFloat = 1
|
|
@ScaledMetric(relativeTo: .caption) private var captionScale: CGFloat = 1
|
|
@Environment(\.legibilityWeight) private var legibilityWeight
|
|
|
|
func body(content: Content) -> some View {
|
|
let platform = Self.platformScale
|
|
return content.environment(\.hapiTypography, HapiTypography(
|
|
bodyScale: bodyScale * platform, codeScale: codeScale * platform,
|
|
captionScale: captionScale * platform, boldText: legibilityWeight == .bold
|
|
))
|
|
}
|
|
|
|
/// The point sizes are tuned for touch screens. A Mac-idiom window renders
|
|
/// them 1:1 next to 13pt system text, so reading text steps down to 14pt.
|
|
private static var platformScale: CGFloat {
|
|
#if targetEnvironment(macCatalyst)
|
|
0.875
|
|
#else
|
|
1
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public enum HapiReadingLayout {
|
|
public static let horizontalInset: CGFloat = 16
|
|
public static let maximumWidth: CGFloat = 720
|
|
|
|
public static func contentWidth(in availableWidth: CGFloat) -> CGFloat {
|
|
max(1, min(maximumWidth, availableWidth - 2 * horizontalInset))
|
|
}
|
|
}
|
|
|
|
public extension View {
|
|
/// Install outside the chat list so hosted rows inherit resolved metrics.
|
|
func hapiTypography() -> some View { modifier(HapiTypographyModifier()) }
|
|
|
|
func hapiReadingColumn(horizontalInset: CGFloat = HapiReadingLayout.horizontalInset) -> some View {
|
|
frame(maxWidth: HapiReadingLayout.maximumWidth, alignment: .leading)
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.horizontal, horizontalInset)
|
|
}
|
|
}
|