Library design: cards + redesigned list on the bookmarks screen, tag picker sheet #9
@@ -12,6 +12,8 @@ private let defaultConfig = ServerConfig(
|
||||
struct MarksApp: App {
|
||||
@State private var serverConfig: ServerConfig = ServerConfig.load() ?? defaultConfig
|
||||
|
||||
init() { PaperAppearance.apply() }
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
MainContainer(config: serverConfig) {
|
||||
|
||||
@@ -83,11 +83,20 @@ enum Paper {
|
||||
}
|
||||
|
||||
static func dynamic(light: UInt32, dark: UInt32) -> Color {
|
||||
Color(uiColor: UIColor { traits in
|
||||
UIColor(rgb: traits.userInterfaceStyle == .dark ? dark : light)
|
||||
})
|
||||
Color(uiColor: dynamicUI(light: light, dark: dark))
|
||||
}
|
||||
|
||||
static func dynamicUI(light: UInt32, dark: UInt32) -> UIColor {
|
||||
UIColor { traits in
|
||||
UIColor(rgb: traits.userInterfaceStyle == .dark ? dark : light)
|
||||
}
|
||||
}
|
||||
|
||||
/// UIKit equivalents, for the chrome SwiftUI can't reach — chiefly the
|
||||
/// navigation bar, whose title font has no SwiftUI API at all.
|
||||
static let uiSheet = dynamicUI(light: 0xF8F5EF, dark: 0x15130F)
|
||||
static let uiInk = dynamicUI(light: 0x14110C, dark: 0xF1ECE1)
|
||||
|
||||
static func luminance(of hex: UInt32) -> Double {
|
||||
let r = Double((hex >> 16) & 0xFF) / 255
|
||||
let g = Double((hex >> 8) & 0xFF) / 255
|
||||
@@ -154,6 +163,56 @@ extension View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIKit chrome
|
||||
|
||||
/// The navigation and tab bars are UIKit underneath, and their fonts have no
|
||||
/// SwiftUI equivalent — `navigationTitle` will render in the system bold sans
|
||||
/// whatever you do to the view. Both are configured once at launch so screen
|
||||
/// titles speak the same serif as the content beneath them.
|
||||
enum PaperAppearance {
|
||||
static func apply() {
|
||||
let bar = UINavigationBarAppearance()
|
||||
// Transparent, not opaque: the screens already paint the paper ground
|
||||
// themselves, and an opaque bar config stops iOS 26 laying out the
|
||||
// large title at all.
|
||||
bar.configureWithTransparentBackground()
|
||||
bar.largeTitleTextAttributes = [
|
||||
.font: serif(34, weight: .regular),
|
||||
.foregroundColor: Paper.uiInk,
|
||||
]
|
||||
bar.titleTextAttributes = [
|
||||
.font: serif(17, weight: .medium),
|
||||
.foregroundColor: Paper.uiInk,
|
||||
]
|
||||
UINavigationBar.appearance().standardAppearance = bar
|
||||
UINavigationBar.appearance().compactAppearance = bar
|
||||
UINavigationBar.appearance().scrollEdgeAppearance = bar
|
||||
|
||||
let tabs = UITabBarAppearance()
|
||||
tabs.configureWithDefaultBackground()
|
||||
let label: [NSAttributedString.Key: Any] = [
|
||||
.font: UIFont.monospacedSystemFont(ofSize: 10, weight: .medium)
|
||||
]
|
||||
for item in [tabs.stackedLayoutAppearance,
|
||||
tabs.inlineLayoutAppearance,
|
||||
tabs.compactInlineLayoutAppearance] {
|
||||
item.normal.titleTextAttributes = label
|
||||
item.selected.titleTextAttributes = label
|
||||
}
|
||||
UITabBar.appearance().standardAppearance = tabs
|
||||
UITabBar.appearance().scrollEdgeAppearance = tabs
|
||||
}
|
||||
|
||||
/// `withDesign(.serif)` is the only route to the system serif in UIKit, and
|
||||
/// it returns nil if the design is unavailable — fall back rather than
|
||||
/// force-unwrap a font.
|
||||
private static func serif(_ size: CGFloat, weight: UIFont.Weight) -> UIFont {
|
||||
let base = UIFont.systemFont(ofSize: size, weight: weight)
|
||||
guard let descriptor = base.fontDescriptor.withDesign(.serif) else { return base }
|
||||
return UIFont(descriptor: descriptor, size: size)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Empty state
|
||||
|
||||
/// The paper equivalent of `ContentUnavailableView`, which can't be restyled —
|
||||
|
||||
Reference in New Issue
Block a user