@momo-kits/native-kits 0.162.2-test.1 → 0.162.2-test.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -46,5 +46,7 @@ public class ApplicationEnvironment: ObservableObject {
|
|
|
46
46
|
self.applicationContext = applicationContext
|
|
47
47
|
self.composeApi = composeApi
|
|
48
48
|
self.config = config
|
|
49
|
+
// Load + observe the host font-scale config into the global store read by scaleSize/appFont.
|
|
50
|
+
FontScaleStore.shared.bind(composeApi)
|
|
49
51
|
}
|
|
50
52
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//
|
|
2
|
+
// FontScaleStore.swift
|
|
3
|
+
// Momo Design System (SwiftUI)
|
|
4
|
+
//
|
|
5
|
+
// Global font-scale config read by scaleSize/appFont. SwiftUI counterpart of the
|
|
6
|
+
// Compose LocalFontScaleConfig and the RN ScaleSizeContext. Populated from the host's
|
|
7
|
+
// `font_scale_config` observer-storage key via the KitComposeApi bridge.
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
import SwiftUI
|
|
11
|
+
|
|
12
|
+
public struct FontScaleConfig {
|
|
13
|
+
public var useOSFontScale: Bool
|
|
14
|
+
public var userScaleRate: CGFloat?
|
|
15
|
+
|
|
16
|
+
public init(useOSFontScale: Bool = true, userScaleRate: CGFloat? = nil) {
|
|
17
|
+
self.useOSFontScale = useOSFontScale
|
|
18
|
+
self.userScaleRate = userScaleRate
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Single source of truth for the font scale. `scaleSize`/`appFont` read `shared.config`.
|
|
23
|
+
/// Views that want live updates observe this object (e.g. MomoText).
|
|
24
|
+
public final class FontScaleStore: ObservableObject {
|
|
25
|
+
public static let shared = FontScaleStore()
|
|
26
|
+
|
|
27
|
+
@Published public private(set) var config = FontScaleConfig()
|
|
28
|
+
|
|
29
|
+
private var didBind = false
|
|
30
|
+
private var observerId: String?
|
|
31
|
+
|
|
32
|
+
private init() {}
|
|
33
|
+
|
|
34
|
+
public func update(_ newConfig: FontScaleConfig) {
|
|
35
|
+
if Thread.isMainThread {
|
|
36
|
+
config = newConfig
|
|
37
|
+
} else {
|
|
38
|
+
DispatchQueue.main.async { [weak self] in self?.config = newConfig }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Load the current config + subscribe to live changes through the host MaxApi bridge.
|
|
43
|
+
/// Safe no-op when no bridge is provided (config stays at default = no scaling).
|
|
44
|
+
public func bind(_ api: KitComposeApi?) {
|
|
45
|
+
guard let api = api, !didBind else { return }
|
|
46
|
+
didBind = true
|
|
47
|
+
|
|
48
|
+
// Synchronous snapshot — flash-free first frame. The compose bridge runs the platform API
|
|
49
|
+
// synchronously (getFontScaleConfig's storage flow is take(1), so it completes inline and
|
|
50
|
+
// returns the current value before the first render reads scaleSize/appFont).
|
|
51
|
+
apply(api.request(funcName: "getFontScaleConfig", params: nil))
|
|
52
|
+
// live: re-read on every host write to the key
|
|
53
|
+
observerId = api.request(funcName: "observer", params: ["font_scale_config"]) { [weak self] response in
|
|
54
|
+
self?.apply(response)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private func apply(_ response: String) {
|
|
59
|
+
guard let parsed = FontScaleStore.parse(response) else { return }
|
|
60
|
+
update(parsed)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Tolerates the MaxApi envelope {"status":..,"response":{..}} as well as a bare config object.
|
|
64
|
+
static func parse(_ response: String) -> FontScaleConfig? {
|
|
65
|
+
guard
|
|
66
|
+
let data = response.data(using: .utf8),
|
|
67
|
+
let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any]
|
|
68
|
+
else { return nil }
|
|
69
|
+
|
|
70
|
+
let obj = (json["response"] as? [String: Any]) ?? (json["data"] as? [String: Any]) ?? json
|
|
71
|
+
guard obj["useOSFontScale"] != nil || obj["userScaleRate"] != nil else { return nil }
|
|
72
|
+
|
|
73
|
+
let useOS = (obj["useOSFontScale"] as? Bool) ?? true
|
|
74
|
+
let rate = (obj["userScaleRate"] as? NSNumber).map { CGFloat($0.doubleValue) }
|
|
75
|
+
return FontScaleConfig(useOSFontScale: useOS, userScaleRate: rate)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
import SwiftUI
|
|
2
2
|
|
|
3
3
|
public func scaleSize(_ size: CGFloat, _ scaleRate: CGFloat? = nil) -> CGFloat {
|
|
4
|
+
// Single source of truth: useOSFontScale + userScaleRate from the host config.
|
|
5
|
+
// `scaleRate` param keeps its existing meaning: a hard cap override (default 1.5).
|
|
6
|
+
let config = FontScaleStore.shared.config
|
|
4
7
|
let defaultScreenSize: CGFloat = 375
|
|
5
8
|
let maxFontScale: CGFloat = scaleRate ?? 1.5
|
|
6
9
|
let maxDeviceScale: CGFloat = 5
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
// useOSFontScale off -> the user has full control via userScaleRate; skip device-width scaling
|
|
12
|
+
// entirely and apply only the custom rate (capped at maxFontScale). Mirrors Compose / RN.
|
|
13
|
+
if !config.useOSFontScale {
|
|
14
|
+
// custom rate is uncapped (full user control); only the OS-follow path below caps at maxFontScale.
|
|
15
|
+
let customRate = config.userScaleRate ?? 1
|
|
16
|
+
return customRate > 1 ? size * customRate : size
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// useOSFontScale on -> device-width scale + OS font scale, take the larger.
|
|
20
|
+
let deviceScale = UIScreen.main.bounds.width / defaultScreenSize
|
|
10
21
|
|
|
11
22
|
let defaultFont = UIFont.systemFont(ofSize: UIFont.labelFontSize)
|
|
12
23
|
let scaledFont = UIFontMetrics.default.scaledFont(for: defaultFont)
|
|
13
|
-
let
|
|
24
|
+
let osFontScale = scaledFont.pointSize / defaultFont.pointSize
|
|
14
25
|
|
|
15
26
|
var fontSizeDeviceScale = size
|
|
16
27
|
var fontSizeOSScale = size
|
|
17
28
|
|
|
18
|
-
|
|
19
29
|
if deviceScale > 1 {
|
|
20
30
|
fontSizeDeviceScale = min(fontSizeDeviceScale * deviceScale, fontSizeDeviceScale + maxDeviceScale)
|
|
21
31
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
fontSizeOSScale = min(fontSizeOSScale * fontScale, fontSizeOSScale * maxFontScale)
|
|
32
|
+
if osFontScale > 1 {
|
|
33
|
+
fontSizeOSScale = min(fontSizeOSScale * osFontScale, fontSizeOSScale * maxFontScale)
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
return max(fontSizeDeviceScale, fontSizeOSScale)
|
|
@@ -118,6 +127,8 @@ public struct MomoText: View {
|
|
|
118
127
|
private let content: String
|
|
119
128
|
private let typography: TypographyStyle
|
|
120
129
|
private let color: Color
|
|
130
|
+
// Observe the global store so the text re-renders live when the host changes the config.
|
|
131
|
+
@ObservedObject private var fontScale = FontScaleStore.shared
|
|
121
132
|
|
|
122
133
|
public init(
|
|
123
134
|
_ content: String,
|
|
@@ -130,6 +141,7 @@ public struct MomoText: View {
|
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
public var body: some View {
|
|
144
|
+
_ = fontScale.config
|
|
133
145
|
let text = SwiftUI.Text(content)
|
|
134
146
|
.font(.system(size: scaleSize(typography.fontSize), weight: typography.fontWeight))
|
|
135
147
|
.foregroundColor(color)
|
|
@@ -2,25 +2,38 @@ import SwiftUI
|
|
|
2
2
|
|
|
3
3
|
public extension Font {
|
|
4
4
|
static func appFont(size: CGFloat) -> Font {
|
|
5
|
+
// Single source of truth: useOSFontScale + userScaleRate from the host config.
|
|
6
|
+
// NOTE: the static Font.xxx constants below are evaluated once at type load, so they
|
|
7
|
+
// capture the config at first access. Live updates apply to scaleSize()/MomoText and
|
|
8
|
+
// any fresh appFont(size:) call, not to the cached static constants.
|
|
9
|
+
let config = FontScaleStore.shared.config
|
|
5
10
|
let defaultScreenSize: CGFloat = 375
|
|
6
11
|
let maxFontScale: CGFloat = 1.5
|
|
7
12
|
let maxDeviceScale: CGFloat = 5
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
|
|
14
|
+
// useOSFontScale off -> the user has full control via userScaleRate; skip device-width
|
|
15
|
+
// scaling entirely and apply only the custom rate (capped at maxFontScale).
|
|
16
|
+
if !config.useOSFontScale {
|
|
17
|
+
// custom rate is uncapped (full user control); only the OS-follow path below caps at maxFontScale.
|
|
18
|
+
let customRate = config.userScaleRate ?? 1
|
|
19
|
+
let scaled = customRate > 1 ? size * customRate : size
|
|
20
|
+
return Font.system(size: scaled)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// useOSFontScale on -> device-width scale, then OS font scale on top (existing behavior).
|
|
24
|
+
let deviceScale = UIScreen.main.bounds.width / defaultScreenSize
|
|
25
|
+
|
|
12
26
|
let defaultFont = UIFont.systemFont(ofSize: UIFont.labelFontSize)
|
|
13
27
|
let scaledFont = UIFontMetrics.default.scaledFont(for: defaultFont)
|
|
14
|
-
let
|
|
28
|
+
let osFontScale = scaledFont.pointSize / defaultFont.pointSize
|
|
15
29
|
|
|
16
30
|
var fontSize = size
|
|
17
31
|
|
|
18
32
|
if deviceScale > 1 {
|
|
19
33
|
fontSize = min(fontSize * deviceScale, fontSize + maxDeviceScale)
|
|
20
34
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
fontSize = min(fontSize * fontScale, fontSize * maxFontScale)
|
|
35
|
+
if osFontScale > 1 {
|
|
36
|
+
fontSize = min(fontSize * osFontScale, fontSize * maxFontScale)
|
|
24
37
|
}
|
|
25
38
|
|
|
26
39
|
return Font.system(size: fontSize)
|