@momo-kits/native-kits 0.167.1-beta.15 → 0.167.1-beta.16
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.
- package/CLAUDE.md +6 -4
- package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Example.xcscheme +32 -0
- package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/WorkspaceSettings.xcsettings +16 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +6 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +5 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Pods-Example.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImage.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SkeletonUI.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios-LottiePrivacyInfo.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +60 -0
- package/ios/Application/ApplicationEnvironment.swift +13 -6
- package/ios/Application/DesignSystemKits.swift +100 -0
- package/ios/Application/DesignSystemKitsPayload.swift +68 -0
- package/ios/Template/TrustBanner/TrustBanner.swift +39 -7
- package/ios/Typography/FontScale.swift +14 -11
- package/ios/Typography/Text.swift +19 -7
- package/ios/Typography/Typography.swift +2 -2
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/package.json +1 -1
- package/ios/Typography/FontScaleStore.swift +0 -58
- package/ios/Typography/KitDesignSystem.swift +0 -17
package/CLAUDE.md
CHANGED
|
@@ -41,7 +41,9 @@ Publishing is automated (`publish.sh` + GitLab CI on `[ci build]` / `main`). Do
|
|
|
41
41
|
|
|
42
42
|
- **Entry point:** wrap content in `NavigationContainer(...)` from `navigation/`. It builds the `Navigator`, takes a host-supplied `Localize` (falls back to an empty one), and provides the CompositionLocals below. **Do not use** `ApplicationContainer` (`application/NavigationContainer.kt`) — deprecated and does **not** wire `LocalLocalize` / `LocalNavigator` / `LocalMaxApi`.
|
|
43
43
|
- **Configuration flows via CompositionLocals**, not params. Public ones: `AppTheme`, `AppStatusBar`, `AppNavigationBar` (`const/Theme.kt`); `ApplicationContext`, `AppConfig`, `AppLanguage`, `LocalLocalize` (`application/`); `LocalNavigator`, `LocalMaxApi` (`navigation/`). Read with `X.current`.
|
|
44
|
-
-
|
|
44
|
+
- **`DesignSystemKits`** (`application/DesignSystemKits.kt`) is the single app-wide holder for the host-owned design-system inputs: font scale (`userScaleRate` / `useOSScaleRate`), whitelist (`isWhiteList`) and the remote `DesignSystemConfig`, bundled as `DesignSystemKitsConfigs`. The host calls `DesignSystemKits.init(source)` once at startup with its `DesignSystemKitsSource` (sync `getConfigs()` seed + `configsFlow`); `init` is idempotent. Without a source, call `setConfigs(...)` directly. Read with `getConfigs()` (snapshot state, recomposes); non-Compose observers use `configsFlow`.
|
|
45
|
+
- **SwiftUI twin:** `ios/Application/DesignSystemKits.swift` mirrors it field for field (`DesignSystemKitsConfigs` with `designSystemConfig: KitConfig?`, `setConfigs` / `getConfigs`), plus the OS Dynamic Type factor (`osFontScale`). There is no `init(source:)` (Swift reserves `init`): the host observes its own bridge and pushes. A host that can't share Swift types (the KMP bridge) calls `DesignSystemKits.shared.setConfigs(json:)` (`ios/Application/DesignSystemKitsPayload.swift`); the JSON field names are a contract with momo-app's serializer (`HostDesignSystemKits.observeConfigsJson`, payload classes in `DesignSystemData.kt`) — change both together. Invalid JSON is ignored. The SwiftUI kit can't link the Kotlin `kits` framework (a second Kotlin/Native runtime would hold a second, never-initialized singleton), so the render-time logic — `FontScale` formula, Dynamic Type observation — is intentionally duplicated in Swift; only values flow across.
|
|
46
|
+
- **Design-system config** (`trustBanner`, `headerBar`, `headerGradient`): `NavigationContainer` provides it as `LocalDesignSystemConfig` (param `designSystemConfig` → parent → `DesignSystemKits`) and applies `headerBar` as the header image when the theme has none.
|
|
45
47
|
- **Platform code** goes through `expect`/`actual` in `platform/` — never reference Android/iOS APIs from `commonMain` directly.
|
|
46
48
|
- **Native bridge:** `IMaxApi` (`vn.momo.maxapi`, exposed as `LocalMaxApi`) is the host-app bridge (dismiss, tracking, device info, language).
|
|
47
49
|
- Navigation uses a runtime `DynamicScreenRegistry`; overlays (modal/bottom-sheet/snackbar) go through `Navigator` + `OverplayComponentRegistry`.
|
|
@@ -69,9 +71,9 @@ The SwiftUI port differs from Compose in one important way: **it follows the hos
|
|
|
69
71
|
|
|
70
72
|
One formula, both platforms: the device-width scale (`width / 375`, capped at `+5pt`) and the OS Dynamic Type scale (capped at `1.5×`) are computed independently and the **larger wins**. When `useOSScaleRate` is false neither applies — the size is just multiplied by `userScaleRate`. Max font scale is fixed at 1.5; the old per-call/per-scope override is deprecated and ignored on both sides.
|
|
71
73
|
|
|
72
|
-
- **Compose** — `const/Typography.kt` (`scaleSize` overloads for `Float`/`Dp`/`TextUnit`/`TextStyle`). The `TextUnit` overload divides by `density.fontScale` so Compose's own `sp` scaling isn't applied twice. Inputs: `LocalContext.current` (`MiniAppContext.userScaleRate` / `useOSScaleRate`, per-field) falling back to
|
|
73
|
-
- **SwiftUI** — `ios/Typography/FontScale.swift`. `FontScale` is an immutable snapshot with `callAsFunction`, so a view reads `@Environment(\.momoFontScale) private var fontScale` and calls `fontScale(16)`. `
|
|
74
|
-
- **Never set `\.momoFontScale` directly** — same trap as `\.localize`. Inject via `.momoFontScale(_:context:)`, which holds
|
|
74
|
+
- **Compose** — `const/Typography.kt` (`scaleSize` overloads for `Float`/`Dp`/`TextUnit`/`TextStyle`). The `TextUnit` overload divides by `density.fontScale` so Compose's own `sp` scaling isn't applied twice. Inputs: `LocalContext.current` (`MiniAppContext.userScaleRate` / `useOSScaleRate`, per-field) falling back to `DesignSystemKits.getConfigs()`. On iOS, `platform/OsFontScale.ios.kt` observes `UIContentSizeCategoryDidChangeNotification` and overrides `LocalDensity` — Compose MP otherwise reads `fontScale` once at `ComposeUIViewController` creation. Wired at `NavigationContainer`.
|
|
75
|
+
- **SwiftUI** — `ios/Typography/FontScale.swift`. `FontScale` is an immutable snapshot with `callAsFunction`, so a view reads `@Environment(\.momoFontScale) private var fontScale` and calls `fontScale(16)`. `DesignSystemKits.shared` is the `ObservableObject` holding the host inputs and the OS factor (it observes `UIContentSizeCategory.didChangeNotification`); the host pushes them with `setConfigs(_:)`.
|
|
76
|
+
- **Never set `\.momoFontScale` directly** — same trap as `\.localize`. Inject via `.momoFontScale(_:context:)`, which holds `DesignSystemKits` as `@ObservedObject`; that is what re-publishes the snapshot. It also injects `\.designSystemKitsConfigs`, which `MomoText` (whitelist font) and `TrustBanner` (fallback after `ApplicationEnvironment.config`) read so a host push re-renders them. `NavigationContainer` does it (passing `MiniAppContext` so the per-mini-app override applies); a bare `UIHostingController` root must too. The environment default reads the live store, so an un-injected subtree still gets correct values — just not live ones.
|
|
75
77
|
- The global `scaleSize(_:)` and `Font.appFont(size:)` remain for call sites that can't read the environment (UIKit bridges, free functions). They are **not** reactive. `Font.header_default_bold` & friends are computed `static var`s for the same reason — as stored `static let`s they froze the scale at first access. `UIViewRepresentable`s should read `context.environment.momoFontScale` instead, and re-apply the font in `updateUIView`, not only in `makeUIView`.
|
|
76
78
|
|
|
77
79
|
## Code conventions
|
package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Example.xcscheme
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<Scheme
|
|
3
|
+
version = "1.3">
|
|
4
|
+
<BuildAction>
|
|
5
|
+
<BuildActionEntries>
|
|
6
|
+
<BuildActionEntry
|
|
7
|
+
buildForRunning = "YES">
|
|
8
|
+
<BuildableReference
|
|
9
|
+
BuildableIdentifier = "primary"
|
|
10
|
+
BlueprintIdentifier = "A6A920D22B2ADD1E00C1A11F"
|
|
11
|
+
BuildableName = "Example"
|
|
12
|
+
BlueprintName = "Example"
|
|
13
|
+
ReferencedContainer = "container:Example.xcodeproj">
|
|
14
|
+
</BuildableReference>
|
|
15
|
+
</BuildActionEntry>
|
|
16
|
+
</BuildActionEntries>
|
|
17
|
+
</BuildAction>
|
|
18
|
+
<LaunchAction
|
|
19
|
+
useCustomWorkingDirectory = "NO"
|
|
20
|
+
buildConfiguration = "Debug"
|
|
21
|
+
allowLocationSimulation = "YES">
|
|
22
|
+
<BuildableProductRunnable>
|
|
23
|
+
<BuildableReference
|
|
24
|
+
BuildableIdentifier = "primary"
|
|
25
|
+
BlueprintIdentifier = "A6A920D22B2ADD1E00C1A11F"
|
|
26
|
+
BuildableName = "Example"
|
|
27
|
+
BlueprintName = "Example"
|
|
28
|
+
ReferencedContainer = "container:Example.xcodeproj">
|
|
29
|
+
</BuildableReference>
|
|
30
|
+
</BuildableProductRunnable>
|
|
31
|
+
</LaunchAction>
|
|
32
|
+
</Scheme>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>SchemeUserState</key>
|
|
6
|
+
<dict>
|
|
7
|
+
<key>Example.xcscheme</key>
|
|
8
|
+
<dict>
|
|
9
|
+
<key>orderHint</key>
|
|
10
|
+
<integer>7</integer>
|
|
11
|
+
</dict>
|
|
12
|
+
</dict>
|
|
13
|
+
</dict>
|
|
14
|
+
</plist>
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>BuildLocationStyle</key>
|
|
6
|
+
<string>UseAppPreferences</string>
|
|
7
|
+
<key>CustomBuildLocationType</key>
|
|
8
|
+
<string>RelativeToDerivedData</string>
|
|
9
|
+
<key>DerivedDataCustomLocation</key>
|
|
10
|
+
<string>DerivedData</string>
|
|
11
|
+
<key>DerivedDataLocationStyle</key>
|
|
12
|
+
<string>WorkspaceRelativePath</string>
|
|
13
|
+
<key>ShowSharedSchemesAutomaticallyEnabled</key>
|
|
14
|
+
<true/>
|
|
15
|
+
</dict>
|
|
16
|
+
</plist>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<Scheme
|
|
3
|
+
LastUpgradeVersion = "1600"
|
|
4
|
+
version = "1.3">
|
|
5
|
+
<BuildAction
|
|
6
|
+
parallelizeBuildables = "YES"
|
|
7
|
+
buildImplicitDependencies = "YES">
|
|
8
|
+
<BuildActionEntries>
|
|
9
|
+
<BuildActionEntry
|
|
10
|
+
buildForTesting = "YES"
|
|
11
|
+
buildForRunning = "YES"
|
|
12
|
+
buildForProfiling = "YES"
|
|
13
|
+
buildForArchiving = "YES"
|
|
14
|
+
buildForAnalyzing = "YES">
|
|
15
|
+
<BuildableReference
|
|
16
|
+
BuildableIdentifier = "primary"
|
|
17
|
+
BlueprintIdentifier = "3B6FB503A75BF5BC1FA6F30BC06B9D28"
|
|
18
|
+
BuildableName = "MoMoUIKits.framework"
|
|
19
|
+
BlueprintName = "MoMoUIKits"
|
|
20
|
+
ReferencedContainer = "container:Pods.xcodeproj">
|
|
21
|
+
</BuildableReference>
|
|
22
|
+
</BuildActionEntry>
|
|
23
|
+
</BuildActionEntries>
|
|
24
|
+
</BuildAction>
|
|
25
|
+
<TestAction
|
|
26
|
+
buildConfiguration = "Debug"
|
|
27
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
28
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
29
|
+
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
30
|
+
<Testables>
|
|
31
|
+
</Testables>
|
|
32
|
+
</TestAction>
|
|
33
|
+
<LaunchAction
|
|
34
|
+
buildConfiguration = "Debug"
|
|
35
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
36
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
37
|
+
launchStyle = "0"
|
|
38
|
+
useCustomWorkingDirectory = "NO"
|
|
39
|
+
ignoresPersistentStateOnLaunch = "NO"
|
|
40
|
+
debugDocumentVersioning = "YES"
|
|
41
|
+
debugServiceExtension = "internal"
|
|
42
|
+
allowLocationSimulation = "YES">
|
|
43
|
+
</LaunchAction>
|
|
44
|
+
<ProfileAction
|
|
45
|
+
buildConfiguration = "Release"
|
|
46
|
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
47
|
+
savedToolIdentifier = ""
|
|
48
|
+
useCustomWorkingDirectory = "NO"
|
|
49
|
+
debugDocumentVersioning = "YES">
|
|
50
|
+
</ProfileAction>
|
|
51
|
+
<AnalyzeAction
|
|
52
|
+
buildConfiguration = "Debug">
|
|
53
|
+
</AnalyzeAction>
|
|
54
|
+
<ArchiveAction
|
|
55
|
+
buildConfiguration = "Release"
|
|
56
|
+
revealArchiveInOrganizer = "YES">
|
|
57
|
+
</ArchiveAction>
|
|
58
|
+
</Scheme>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<Scheme
|
|
3
|
+
LastUpgradeVersion = "1600"
|
|
4
|
+
version = "1.3">
|
|
5
|
+
<BuildAction
|
|
6
|
+
parallelizeBuildables = "YES"
|
|
7
|
+
buildImplicitDependencies = "YES">
|
|
8
|
+
<BuildActionEntries>
|
|
9
|
+
<BuildActionEntry
|
|
10
|
+
buildForTesting = "YES"
|
|
11
|
+
buildForRunning = "YES"
|
|
12
|
+
buildForProfiling = "YES"
|
|
13
|
+
buildForArchiving = "YES"
|
|
14
|
+
buildForAnalyzing = "YES">
|
|
15
|
+
<BuildableReference
|
|
16
|
+
BuildableIdentifier = "primary"
|
|
17
|
+
BlueprintIdentifier = "0AEE99A309977BD12A049FF48AF9BA4B"
|
|
18
|
+
BuildableName = "Pods_Example.framework"
|
|
19
|
+
BlueprintName = "Pods-Example"
|
|
20
|
+
ReferencedContainer = "container:Pods.xcodeproj">
|
|
21
|
+
</BuildableReference>
|
|
22
|
+
</BuildActionEntry>
|
|
23
|
+
</BuildActionEntries>
|
|
24
|
+
</BuildAction>
|
|
25
|
+
<TestAction
|
|
26
|
+
buildConfiguration = "Debug"
|
|
27
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
28
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
29
|
+
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
30
|
+
<Testables>
|
|
31
|
+
</Testables>
|
|
32
|
+
</TestAction>
|
|
33
|
+
<LaunchAction
|
|
34
|
+
buildConfiguration = "Debug"
|
|
35
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
36
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
37
|
+
launchStyle = "0"
|
|
38
|
+
useCustomWorkingDirectory = "NO"
|
|
39
|
+
ignoresPersistentStateOnLaunch = "NO"
|
|
40
|
+
debugDocumentVersioning = "YES"
|
|
41
|
+
debugServiceExtension = "internal"
|
|
42
|
+
allowLocationSimulation = "YES">
|
|
43
|
+
</LaunchAction>
|
|
44
|
+
<ProfileAction
|
|
45
|
+
buildConfiguration = "Release"
|
|
46
|
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
47
|
+
savedToolIdentifier = ""
|
|
48
|
+
useCustomWorkingDirectory = "NO"
|
|
49
|
+
debugDocumentVersioning = "YES">
|
|
50
|
+
</ProfileAction>
|
|
51
|
+
<AnalyzeAction
|
|
52
|
+
buildConfiguration = "Debug">
|
|
53
|
+
</AnalyzeAction>
|
|
54
|
+
<ArchiveAction
|
|
55
|
+
buildConfiguration = "Release"
|
|
56
|
+
revealArchiveInOrganizer = "YES">
|
|
57
|
+
</ArchiveAction>
|
|
58
|
+
</Scheme>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<Scheme
|
|
3
|
+
LastUpgradeVersion = "1600"
|
|
4
|
+
version = "1.3">
|
|
5
|
+
<BuildAction
|
|
6
|
+
parallelizeBuildables = "YES"
|
|
7
|
+
buildImplicitDependencies = "YES">
|
|
8
|
+
<BuildActionEntries>
|
|
9
|
+
<BuildActionEntry
|
|
10
|
+
buildForTesting = "YES"
|
|
11
|
+
buildForRunning = "YES"
|
|
12
|
+
buildForProfiling = "YES"
|
|
13
|
+
buildForArchiving = "YES"
|
|
14
|
+
buildForAnalyzing = "YES">
|
|
15
|
+
<BuildableReference
|
|
16
|
+
BuildableIdentifier = "primary"
|
|
17
|
+
BlueprintIdentifier = "3847153A6E5EEFB86565BA840768F429"
|
|
18
|
+
BuildableName = "SDWebImage.framework"
|
|
19
|
+
BlueprintName = "SDWebImage"
|
|
20
|
+
ReferencedContainer = "container:Pods.xcodeproj">
|
|
21
|
+
</BuildableReference>
|
|
22
|
+
</BuildActionEntry>
|
|
23
|
+
</BuildActionEntries>
|
|
24
|
+
</BuildAction>
|
|
25
|
+
<TestAction
|
|
26
|
+
buildConfiguration = "Debug"
|
|
27
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
28
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
29
|
+
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
30
|
+
<Testables>
|
|
31
|
+
</Testables>
|
|
32
|
+
</TestAction>
|
|
33
|
+
<LaunchAction
|
|
34
|
+
buildConfiguration = "Debug"
|
|
35
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
36
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
37
|
+
launchStyle = "0"
|
|
38
|
+
useCustomWorkingDirectory = "NO"
|
|
39
|
+
ignoresPersistentStateOnLaunch = "NO"
|
|
40
|
+
debugDocumentVersioning = "YES"
|
|
41
|
+
debugServiceExtension = "internal"
|
|
42
|
+
allowLocationSimulation = "YES">
|
|
43
|
+
</LaunchAction>
|
|
44
|
+
<ProfileAction
|
|
45
|
+
buildConfiguration = "Release"
|
|
46
|
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
47
|
+
savedToolIdentifier = ""
|
|
48
|
+
useCustomWorkingDirectory = "NO"
|
|
49
|
+
debugDocumentVersioning = "YES">
|
|
50
|
+
</ProfileAction>
|
|
51
|
+
<AnalyzeAction
|
|
52
|
+
buildConfiguration = "Debug">
|
|
53
|
+
</AnalyzeAction>
|
|
54
|
+
<ArchiveAction
|
|
55
|
+
buildConfiguration = "Release"
|
|
56
|
+
revealArchiveInOrganizer = "YES">
|
|
57
|
+
</ArchiveAction>
|
|
58
|
+
</Scheme>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<Scheme
|
|
3
|
+
LastUpgradeVersion = "1600"
|
|
4
|
+
version = "1.3">
|
|
5
|
+
<BuildAction
|
|
6
|
+
parallelizeBuildables = "YES"
|
|
7
|
+
buildImplicitDependencies = "YES">
|
|
8
|
+
<BuildActionEntries>
|
|
9
|
+
<BuildActionEntry
|
|
10
|
+
buildForTesting = "YES"
|
|
11
|
+
buildForRunning = "YES"
|
|
12
|
+
buildForProfiling = "YES"
|
|
13
|
+
buildForArchiving = "YES"
|
|
14
|
+
buildForAnalyzing = "YES">
|
|
15
|
+
<BuildableReference
|
|
16
|
+
BuildableIdentifier = "primary"
|
|
17
|
+
BlueprintIdentifier = "92EBFA3E7005B4C18A9C0B44324EB80F"
|
|
18
|
+
BuildableName = "SDWebImageSwiftUI.framework"
|
|
19
|
+
BlueprintName = "SDWebImageSwiftUI"
|
|
20
|
+
ReferencedContainer = "container:Pods.xcodeproj">
|
|
21
|
+
</BuildableReference>
|
|
22
|
+
</BuildActionEntry>
|
|
23
|
+
</BuildActionEntries>
|
|
24
|
+
</BuildAction>
|
|
25
|
+
<TestAction
|
|
26
|
+
buildConfiguration = "Debug"
|
|
27
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
28
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
29
|
+
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
30
|
+
<Testables>
|
|
31
|
+
</Testables>
|
|
32
|
+
</TestAction>
|
|
33
|
+
<LaunchAction
|
|
34
|
+
buildConfiguration = "Debug"
|
|
35
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
36
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
37
|
+
launchStyle = "0"
|
|
38
|
+
useCustomWorkingDirectory = "NO"
|
|
39
|
+
ignoresPersistentStateOnLaunch = "NO"
|
|
40
|
+
debugDocumentVersioning = "YES"
|
|
41
|
+
debugServiceExtension = "internal"
|
|
42
|
+
allowLocationSimulation = "YES">
|
|
43
|
+
</LaunchAction>
|
|
44
|
+
<ProfileAction
|
|
45
|
+
buildConfiguration = "Release"
|
|
46
|
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
47
|
+
savedToolIdentifier = ""
|
|
48
|
+
useCustomWorkingDirectory = "NO"
|
|
49
|
+
debugDocumentVersioning = "YES">
|
|
50
|
+
</ProfileAction>
|
|
51
|
+
<AnalyzeAction
|
|
52
|
+
buildConfiguration = "Debug">
|
|
53
|
+
</AnalyzeAction>
|
|
54
|
+
<ArchiveAction
|
|
55
|
+
buildConfiguration = "Release"
|
|
56
|
+
revealArchiveInOrganizer = "YES">
|
|
57
|
+
</ArchiveAction>
|
|
58
|
+
</Scheme>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<Scheme
|
|
3
|
+
LastUpgradeVersion = "1600"
|
|
4
|
+
version = "1.3">
|
|
5
|
+
<BuildAction
|
|
6
|
+
parallelizeBuildables = "YES"
|
|
7
|
+
buildImplicitDependencies = "YES">
|
|
8
|
+
<BuildActionEntries>
|
|
9
|
+
<BuildActionEntry
|
|
10
|
+
buildForTesting = "YES"
|
|
11
|
+
buildForRunning = "YES"
|
|
12
|
+
buildForProfiling = "YES"
|
|
13
|
+
buildForArchiving = "YES"
|
|
14
|
+
buildForAnalyzing = "YES">
|
|
15
|
+
<BuildableReference
|
|
16
|
+
BuildableIdentifier = "primary"
|
|
17
|
+
BlueprintIdentifier = "6510766A9670BFA3B251E2A62446FC5D"
|
|
18
|
+
BuildableName = "SkeletonUI.framework"
|
|
19
|
+
BlueprintName = "SkeletonUI"
|
|
20
|
+
ReferencedContainer = "container:Pods.xcodeproj">
|
|
21
|
+
</BuildableReference>
|
|
22
|
+
</BuildActionEntry>
|
|
23
|
+
</BuildActionEntries>
|
|
24
|
+
</BuildAction>
|
|
25
|
+
<TestAction
|
|
26
|
+
buildConfiguration = "Debug"
|
|
27
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
28
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
29
|
+
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
30
|
+
<Testables>
|
|
31
|
+
</Testables>
|
|
32
|
+
</TestAction>
|
|
33
|
+
<LaunchAction
|
|
34
|
+
buildConfiguration = "Debug"
|
|
35
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
36
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
37
|
+
launchStyle = "0"
|
|
38
|
+
useCustomWorkingDirectory = "NO"
|
|
39
|
+
ignoresPersistentStateOnLaunch = "NO"
|
|
40
|
+
debugDocumentVersioning = "YES"
|
|
41
|
+
debugServiceExtension = "internal"
|
|
42
|
+
allowLocationSimulation = "YES">
|
|
43
|
+
</LaunchAction>
|
|
44
|
+
<ProfileAction
|
|
45
|
+
buildConfiguration = "Release"
|
|
46
|
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
47
|
+
savedToolIdentifier = ""
|
|
48
|
+
useCustomWorkingDirectory = "NO"
|
|
49
|
+
debugDocumentVersioning = "YES">
|
|
50
|
+
</ProfileAction>
|
|
51
|
+
<AnalyzeAction
|
|
52
|
+
buildConfiguration = "Debug">
|
|
53
|
+
</AnalyzeAction>
|
|
54
|
+
<ArchiveAction
|
|
55
|
+
buildConfiguration = "Release"
|
|
56
|
+
revealArchiveInOrganizer = "YES">
|
|
57
|
+
</ArchiveAction>
|
|
58
|
+
</Scheme>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<Scheme
|
|
3
|
+
LastUpgradeVersion = "1600"
|
|
4
|
+
version = "1.3">
|
|
5
|
+
<BuildAction
|
|
6
|
+
parallelizeBuildables = "YES"
|
|
7
|
+
buildImplicitDependencies = "YES">
|
|
8
|
+
<BuildActionEntries>
|
|
9
|
+
<BuildActionEntry
|
|
10
|
+
buildForTesting = "YES"
|
|
11
|
+
buildForRunning = "YES"
|
|
12
|
+
buildForProfiling = "YES"
|
|
13
|
+
buildForArchiving = "YES"
|
|
14
|
+
buildForAnalyzing = "YES">
|
|
15
|
+
<BuildableReference
|
|
16
|
+
BuildableIdentifier = "primary"
|
|
17
|
+
BlueprintIdentifier = "BF2A15FEC3F3424BBC4B9AD5F86F2D54"
|
|
18
|
+
BuildableName = "LottiePrivacyInfo.bundle"
|
|
19
|
+
BlueprintName = "lottie-ios-LottiePrivacyInfo"
|
|
20
|
+
ReferencedContainer = "container:Pods.xcodeproj">
|
|
21
|
+
</BuildableReference>
|
|
22
|
+
</BuildActionEntry>
|
|
23
|
+
</BuildActionEntries>
|
|
24
|
+
</BuildAction>
|
|
25
|
+
<TestAction
|
|
26
|
+
buildConfiguration = "Debug"
|
|
27
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
28
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
29
|
+
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
30
|
+
<Testables>
|
|
31
|
+
</Testables>
|
|
32
|
+
</TestAction>
|
|
33
|
+
<LaunchAction
|
|
34
|
+
buildConfiguration = "Debug"
|
|
35
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
36
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
37
|
+
launchStyle = "0"
|
|
38
|
+
useCustomWorkingDirectory = "NO"
|
|
39
|
+
ignoresPersistentStateOnLaunch = "NO"
|
|
40
|
+
debugDocumentVersioning = "YES"
|
|
41
|
+
debugServiceExtension = "internal"
|
|
42
|
+
allowLocationSimulation = "YES">
|
|
43
|
+
</LaunchAction>
|
|
44
|
+
<ProfileAction
|
|
45
|
+
buildConfiguration = "Release"
|
|
46
|
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
47
|
+
savedToolIdentifier = ""
|
|
48
|
+
useCustomWorkingDirectory = "NO"
|
|
49
|
+
debugDocumentVersioning = "YES">
|
|
50
|
+
</ProfileAction>
|
|
51
|
+
<AnalyzeAction
|
|
52
|
+
buildConfiguration = "Debug">
|
|
53
|
+
</AnalyzeAction>
|
|
54
|
+
<ArchiveAction
|
|
55
|
+
buildConfiguration = "Release"
|
|
56
|
+
revealArchiveInOrganizer = "YES">
|
|
57
|
+
</ArchiveAction>
|
|
58
|
+
</Scheme>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<Scheme
|
|
3
|
+
LastUpgradeVersion = "1600"
|
|
4
|
+
version = "1.3">
|
|
5
|
+
<BuildAction
|
|
6
|
+
parallelizeBuildables = "YES"
|
|
7
|
+
buildImplicitDependencies = "YES">
|
|
8
|
+
<BuildActionEntries>
|
|
9
|
+
<BuildActionEntry
|
|
10
|
+
buildForTesting = "YES"
|
|
11
|
+
buildForRunning = "YES"
|
|
12
|
+
buildForProfiling = "YES"
|
|
13
|
+
buildForArchiving = "YES"
|
|
14
|
+
buildForAnalyzing = "YES">
|
|
15
|
+
<BuildableReference
|
|
16
|
+
BuildableIdentifier = "primary"
|
|
17
|
+
BlueprintIdentifier = "0B967D7F8561D42493EE289EC8D450D1"
|
|
18
|
+
BuildableName = "Lottie.framework"
|
|
19
|
+
BlueprintName = "lottie-ios"
|
|
20
|
+
ReferencedContainer = "container:Pods.xcodeproj">
|
|
21
|
+
</BuildableReference>
|
|
22
|
+
</BuildActionEntry>
|
|
23
|
+
</BuildActionEntries>
|
|
24
|
+
</BuildAction>
|
|
25
|
+
<TestAction
|
|
26
|
+
buildConfiguration = "Debug"
|
|
27
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
28
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
29
|
+
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
30
|
+
<Testables>
|
|
31
|
+
</Testables>
|
|
32
|
+
</TestAction>
|
|
33
|
+
<LaunchAction
|
|
34
|
+
buildConfiguration = "Debug"
|
|
35
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
36
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
37
|
+
launchStyle = "0"
|
|
38
|
+
useCustomWorkingDirectory = "NO"
|
|
39
|
+
ignoresPersistentStateOnLaunch = "NO"
|
|
40
|
+
debugDocumentVersioning = "YES"
|
|
41
|
+
debugServiceExtension = "internal"
|
|
42
|
+
allowLocationSimulation = "YES">
|
|
43
|
+
</LaunchAction>
|
|
44
|
+
<ProfileAction
|
|
45
|
+
buildConfiguration = "Release"
|
|
46
|
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
47
|
+
savedToolIdentifier = ""
|
|
48
|
+
useCustomWorkingDirectory = "NO"
|
|
49
|
+
debugDocumentVersioning = "YES">
|
|
50
|
+
</ProfileAction>
|
|
51
|
+
<AnalyzeAction
|
|
52
|
+
buildConfiguration = "Debug">
|
|
53
|
+
</AnalyzeAction>
|
|
54
|
+
<ArchiveAction
|
|
55
|
+
buildConfiguration = "Release"
|
|
56
|
+
revealArchiveInOrganizer = "YES">
|
|
57
|
+
</ArchiveAction>
|
|
58
|
+
</Scheme>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>SchemeUserState</key>
|
|
6
|
+
<dict>
|
|
7
|
+
<key>MoMoUIKits.xcscheme</key>
|
|
8
|
+
<dict>
|
|
9
|
+
<key>isShown</key>
|
|
10
|
+
<false />
|
|
11
|
+
<key>orderHint</key>
|
|
12
|
+
<integer>0</integer>
|
|
13
|
+
</dict>
|
|
14
|
+
<key>Pods-Example.xcscheme</key>
|
|
15
|
+
<dict>
|
|
16
|
+
<key>isShown</key>
|
|
17
|
+
<false />
|
|
18
|
+
<key>orderHint</key>
|
|
19
|
+
<integer>1</integer>
|
|
20
|
+
</dict>
|
|
21
|
+
<key>SDWebImage.xcscheme</key>
|
|
22
|
+
<dict>
|
|
23
|
+
<key>isShown</key>
|
|
24
|
+
<false />
|
|
25
|
+
<key>orderHint</key>
|
|
26
|
+
<integer>2</integer>
|
|
27
|
+
</dict>
|
|
28
|
+
<key>SDWebImageSwiftUI.xcscheme</key>
|
|
29
|
+
<dict>
|
|
30
|
+
<key>isShown</key>
|
|
31
|
+
<false />
|
|
32
|
+
<key>orderHint</key>
|
|
33
|
+
<integer>3</integer>
|
|
34
|
+
</dict>
|
|
35
|
+
<key>SkeletonUI.xcscheme</key>
|
|
36
|
+
<dict>
|
|
37
|
+
<key>isShown</key>
|
|
38
|
+
<false />
|
|
39
|
+
<key>orderHint</key>
|
|
40
|
+
<integer>4</integer>
|
|
41
|
+
</dict>
|
|
42
|
+
<key>lottie-ios-LottiePrivacyInfo.xcscheme</key>
|
|
43
|
+
<dict>
|
|
44
|
+
<key>isShown</key>
|
|
45
|
+
<false />
|
|
46
|
+
<key>orderHint</key>
|
|
47
|
+
<integer>5</integer>
|
|
48
|
+
</dict>
|
|
49
|
+
<key>lottie-ios.xcscheme</key>
|
|
50
|
+
<dict>
|
|
51
|
+
<key>isShown</key>
|
|
52
|
+
<false />
|
|
53
|
+
<key>orderHint</key>
|
|
54
|
+
<integer>6</integer>
|
|
55
|
+
</dict>
|
|
56
|
+
</dict>
|
|
57
|
+
<key>SuppressBuildableAutocreation</key>
|
|
58
|
+
<dict />
|
|
59
|
+
</dict>
|
|
60
|
+
</plist>
|
|
@@ -17,8 +17,8 @@ public class MiniAppContext {
|
|
|
17
17
|
public var toolkitConfig: [String: Any] = [:]
|
|
18
18
|
public var providerId: String = "momo"
|
|
19
19
|
public var permissions: [[String: Any]] = []
|
|
20
|
-
// Per-mini-app font-scale override; nil falls back to
|
|
21
|
-
// MiniAppContext.userScaleRate / useOSScaleRate, which win per field over
|
|
20
|
+
// Per-mini-app font-scale override; nil falls back to DesignSystemKits. Mirrors Compose's
|
|
21
|
+
// MiniAppContext.userScaleRate / useOSScaleRate, which win per field over DesignSystemKits.
|
|
22
22
|
public var userScaleRate: CGFloat? = nil
|
|
23
23
|
public var useOSScaleRate: Bool? = nil
|
|
24
24
|
|
|
@@ -37,10 +37,17 @@ public class MiniAppContext {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
public var
|
|
43
|
-
public var
|
|
40
|
+
// Value type so DesignSystemKitsConfigs can dedupe host pushes; mirrors Compose's DesignSystemConfig.
|
|
41
|
+
public struct KitConfig: Equatable {
|
|
42
|
+
public var trustBanner: TrustBannerData?
|
|
43
|
+
public var headerBar: String?
|
|
44
|
+
public var headerGradient: String?
|
|
45
|
+
|
|
46
|
+
public init(trustBanner: TrustBannerData? = nil, headerBar: String? = nil, headerGradient: String? = nil) {
|
|
47
|
+
self.trustBanner = trustBanner
|
|
48
|
+
self.headerBar = headerBar
|
|
49
|
+
self.headerGradient = headerGradient
|
|
50
|
+
}
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
public class ApplicationEnvironment: ObservableObject {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// File: ios/Application/DesignSystemKits.swift
|
|
2
|
+
// Created At: 2026-10-02 14:17:29 +07:00
|
|
3
|
+
// Created By: AI
|
|
4
|
+
// AI Agent: Claude Code
|
|
5
|
+
// Model: claude-opus-5-5
|
|
6
|
+
|
|
7
|
+
// AI-GENERATED START: app-wide design-system holder mirroring Compose's DesignSystemKits
|
|
8
|
+
import SwiftUI
|
|
9
|
+
|
|
10
|
+
/// Host-owned design-system inputs; mirrors Compose's `DesignSystemKitsConfigs` field for field.
|
|
11
|
+
public struct DesignSystemKitsConfigs: Equatable {
|
|
12
|
+
public var userScaleRate: CGFloat
|
|
13
|
+
public var useOSScaleRate: Bool
|
|
14
|
+
public var isWhiteList: Bool
|
|
15
|
+
public var designSystemConfig: KitConfig?
|
|
16
|
+
|
|
17
|
+
public init(
|
|
18
|
+
userScaleRate: CGFloat = 1,
|
|
19
|
+
useOSScaleRate: Bool = true,
|
|
20
|
+
isWhiteList: Bool = false,
|
|
21
|
+
designSystemConfig: KitConfig? = nil
|
|
22
|
+
) {
|
|
23
|
+
self.userScaleRate = userScaleRate
|
|
24
|
+
self.useOSScaleRate = useOSScaleRate
|
|
25
|
+
self.isWhiteList = isWhiteList
|
|
26
|
+
self.designSystemConfig = designSystemConfig
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// App-wide holder for the SwiftUI kit, mirroring Compose's `DesignSystemKits`. The kit is sandboxed from the
|
|
31
|
+
/// host, so the host pushes `setConfigs(_:)` at startup and on every change; the OS Dynamic Type factor is
|
|
32
|
+
/// tracked here. Views re-render on change as long as the subtree got `.momoFontScale()` (`NavigationContainer`
|
|
33
|
+
/// applies it). Swift reserves `init`, so there is no `init(source:)` twin: the host observes and pushes.
|
|
34
|
+
public final class DesignSystemKits: ObservableObject {
|
|
35
|
+
public static let shared = DesignSystemKits()
|
|
36
|
+
|
|
37
|
+
@Published private var configs = DesignSystemKitsConfigs()
|
|
38
|
+
@Published public private(set) var osFontScale: CGFloat = DesignSystemKits.currentOsFontScale()
|
|
39
|
+
|
|
40
|
+
private init() {
|
|
41
|
+
NotificationCenter.default.addObserver(
|
|
42
|
+
self,
|
|
43
|
+
selector: #selector(contentSizeCategoryDidChange),
|
|
44
|
+
name: UIContentSizeCategory.didChangeNotification,
|
|
45
|
+
object: nil
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public func setConfigs(_ configs: DesignSystemKitsConfigs) {
|
|
50
|
+
onMain {
|
|
51
|
+
// Re-assigning an unchanged value would still publish and invalidate every kit subtree.
|
|
52
|
+
guard self.configs != configs else { return }
|
|
53
|
+
self.configs = configs
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public func getConfigs() -> DesignSystemKitsConfigs {
|
|
58
|
+
configs
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public var fontScale: FontScale {
|
|
62
|
+
FontScale(
|
|
63
|
+
userScaleRate: configs.userScaleRate,
|
|
64
|
+
useOSScaleRate: configs.useOSScaleRate,
|
|
65
|
+
osFontScale: osFontScale
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@objc private func contentSizeCategoryDidChange() {
|
|
70
|
+
let next = Self.currentOsFontScale()
|
|
71
|
+
guard next != osFontScale else { return }
|
|
72
|
+
osFontScale = next
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Same factor Compose's iOS `ProvideOsFontScale` derives from
|
|
76
|
+
/// `UIFont.preferredFont(forTextStyle: .body).pointSize / 17`, so both iOS surfaces scale alike.
|
|
77
|
+
static func currentOsFontScale() -> CGFloat {
|
|
78
|
+
let base = UIFont.systemFont(ofSize: UIFont.labelFontSize)
|
|
79
|
+
return UIFontMetrics.default.scaledFont(for: base).pointSize / base.pointSize
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Synchronous when already on main — an async hop would defer the change by a frame.
|
|
83
|
+
private func onMain(_ work: @escaping () -> Void) {
|
|
84
|
+
if Thread.isMainThread { work() } else { DispatchQueue.main.async(execute: work) }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private struct DesignSystemKitsConfigsKey: EnvironmentKey {
|
|
89
|
+
/// Computed, never stored: an un-injected subtree still reads the live holder, just not reactively.
|
|
90
|
+
static var defaultValue: DesignSystemKitsConfigs { DesignSystemKits.shared.getConfigs() }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public extension EnvironmentValues {
|
|
94
|
+
/// Injected by `.momoFontScale()`; never set it directly (same trap as `\.momoFontScale`).
|
|
95
|
+
var designSystemKitsConfigs: DesignSystemKitsConfigs {
|
|
96
|
+
get { self[DesignSystemKitsConfigsKey.self] }
|
|
97
|
+
set { self[DesignSystemKitsConfigsKey.self] = newValue }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// AI-GENERATED END: app-wide design-system holder mirroring Compose's DesignSystemKits
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// File: ios/Application/DesignSystemKitsPayload.swift
|
|
2
|
+
// Created At: 2026-10-02 15:40:00 +07:00
|
|
3
|
+
// Created By: AI
|
|
4
|
+
// AI Agent: Claude Code
|
|
5
|
+
// Model: claude-opus-5-5
|
|
6
|
+
|
|
7
|
+
// AI-GENERATED START: JSON entry point for hosts that can't share Swift types with the kit
|
|
8
|
+
import Foundation
|
|
9
|
+
|
|
10
|
+
public extension DesignSystemKits {
|
|
11
|
+
/// Applies configs pushed as JSON by a host that can't share Swift types with the kit (e.g. the KMP bridge).
|
|
12
|
+
/// Invalid JSON is ignored and the current configs are kept.
|
|
13
|
+
func setConfigs(json: String) {
|
|
14
|
+
guard let data = json.data(using: .utf8),
|
|
15
|
+
let payload = try? JSONDecoder().decode(DesignSystemKitsPayload.self, from: data) else { return }
|
|
16
|
+
setConfigs(payload.configs)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Wire format; field names match the host's serializer (momo-app HostDesignSystemKits / DesignSystemData.kt).
|
|
21
|
+
private struct DesignSystemKitsPayload: Decodable {
|
|
22
|
+
let userScaleRate: CGFloat
|
|
23
|
+
let useOSScaleRate: Bool
|
|
24
|
+
let isWhiteList: Bool
|
|
25
|
+
let designSystemConfig: KitConfigPayload?
|
|
26
|
+
|
|
27
|
+
var configs: DesignSystemKitsConfigs {
|
|
28
|
+
DesignSystemKitsConfigs(
|
|
29
|
+
userScaleRate: userScaleRate,
|
|
30
|
+
useOSScaleRate: useOSScaleRate,
|
|
31
|
+
isWhiteList: isWhiteList,
|
|
32
|
+
designSystemConfig: designSystemConfig?.kitConfig
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private struct KitConfigPayload: Decodable {
|
|
38
|
+
let trustBanner: TrustBannerPayload?
|
|
39
|
+
let headerBar: String?
|
|
40
|
+
let headerGradient: String?
|
|
41
|
+
|
|
42
|
+
var kitConfig: KitConfig {
|
|
43
|
+
KitConfig(trustBanner: trustBanner?.trustBannerData, headerBar: headerBar, headerGradient: headerGradient)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private struct TrustBannerPayload: Decodable {
|
|
48
|
+
let content: [String: String]
|
|
49
|
+
let subContent: [String: String]
|
|
50
|
+
let pciImage: String?
|
|
51
|
+
let sslImage: String?
|
|
52
|
+
let urlConfig: String?
|
|
53
|
+
let icons: [String]?
|
|
54
|
+
let momoImage: String?
|
|
55
|
+
|
|
56
|
+
var trustBannerData: TrustBannerData {
|
|
57
|
+
TrustBannerData(
|
|
58
|
+
content: content,
|
|
59
|
+
subContent: subContent,
|
|
60
|
+
pciImage: pciImage,
|
|
61
|
+
sslImage: sslImage,
|
|
62
|
+
urlConfig: urlConfig,
|
|
63
|
+
icons: icons,
|
|
64
|
+
momoImage: momoImage
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// AI-GENERATED END: JSON entry point for hosts that can't share Swift types with the kit
|
|
@@ -11,7 +11,7 @@ import SDWebImageSwiftUI
|
|
|
11
11
|
|
|
12
12
|
let jsonUrl = "https://static.momocdn.net/app/json/component-kits/design_system.json"
|
|
13
13
|
|
|
14
|
-
public struct TrustBannerData: Decodable {
|
|
14
|
+
public struct TrustBannerData: Decodable, Equatable {
|
|
15
15
|
let content: [String: String]
|
|
16
16
|
let subContent: [String: String]
|
|
17
17
|
let pciImage: String
|
|
@@ -50,9 +50,41 @@ let defaultBanner = TrustBannerData(
|
|
|
50
50
|
momoImage: "https://static.momocdn.net/app/img/kits/trustBanner/ic_secu.png"
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
+
// AI-GENERATED START: public init so the host can push its remote trust banner config
|
|
54
|
+
public extension TrustBannerData {
|
|
55
|
+
/// Missing images / url fall back to the kit's default banner, like the view does per field.
|
|
56
|
+
init(
|
|
57
|
+
content: [String: String],
|
|
58
|
+
subContent: [String: String],
|
|
59
|
+
pciImage: String?,
|
|
60
|
+
sslImage: String?,
|
|
61
|
+
urlConfig: String?,
|
|
62
|
+
icons: [String]?,
|
|
63
|
+
momoImage: String?
|
|
64
|
+
) {
|
|
65
|
+
self.init(
|
|
66
|
+
content: content,
|
|
67
|
+
subContent: subContent,
|
|
68
|
+
pciImage: pciImage ?? defaultBanner.pciImage,
|
|
69
|
+
sslImage: sslImage ?? defaultBanner.sslImage,
|
|
70
|
+
urlConfig: urlConfig ?? defaultBanner.urlConfig,
|
|
71
|
+
icons: icons ?? defaultBanner.icons,
|
|
72
|
+
momoImage: momoImage ?? defaultBanner.momoImage
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// AI-GENERATED END: public init so the host can push its remote trust banner config
|
|
77
|
+
|
|
53
78
|
public struct TrustBanner: View {
|
|
54
79
|
@Environment(\.applicationEnvironment) var applicationEnvironment
|
|
55
80
|
@Environment(\.localize) private var localize
|
|
81
|
+
// AI-GENERATED START: container config wins, else the host-pushed DesignSystemKits config (Compose parity)
|
|
82
|
+
@Environment(\.designSystemKitsConfigs) private var designSystemKitsConfigs
|
|
83
|
+
|
|
84
|
+
private var trustBanner: TrustBannerData? {
|
|
85
|
+
(applicationEnvironment.config ?? designSystemKitsConfigs.designSystemConfig)?.trustBanner
|
|
86
|
+
}
|
|
87
|
+
// AI-GENERATED END: container config wins, else the host-pushed DesignSystemKits config (Compose parity)
|
|
56
88
|
|
|
57
89
|
var onPress: ((String)->Void)?
|
|
58
90
|
|
|
@@ -62,19 +94,19 @@ public struct TrustBanner: View {
|
|
|
62
94
|
|
|
63
95
|
func onPressSecurity() {
|
|
64
96
|
if (onPress != nil) {
|
|
65
|
-
onPress!(
|
|
97
|
+
onPress!(trustBanner?.urlConfig ?? defaultBanner.urlConfig)
|
|
66
98
|
}
|
|
67
99
|
}
|
|
68
100
|
|
|
69
101
|
|
|
70
102
|
public var body: some View {
|
|
71
103
|
HStack(alignment: .center, spacing: 8) {
|
|
72
|
-
WebImage(url: URL(string:
|
|
104
|
+
WebImage(url: URL(string: trustBanner?.momoImage ?? defaultBanner.momoImage))
|
|
73
105
|
.resizable()
|
|
74
106
|
.scaledToFit()
|
|
75
107
|
.frame(width: 64, height: 64)
|
|
76
108
|
VStack(alignment: .leading){
|
|
77
|
-
Text(
|
|
109
|
+
Text(trustBanner?.content[localize.currentLanguage]
|
|
78
110
|
?? localize.translateData(defaultBanner.content))
|
|
79
111
|
.foregroundColor(Color(hex: "484848"))
|
|
80
112
|
.lineLimit(2)
|
|
@@ -84,7 +116,7 @@ public struct TrustBanner: View {
|
|
|
84
116
|
.padding(.bottom, 8)
|
|
85
117
|
HStack {
|
|
86
118
|
HStack(spacing: 0) {
|
|
87
|
-
Text(
|
|
119
|
+
Text(trustBanner?.subContent[localize.currentLanguage]
|
|
88
120
|
?? localize.translateData(defaultBanner.subContent))
|
|
89
121
|
.foregroundColor(Colors.pink03)
|
|
90
122
|
.font(.system(size: 13, weight: .bold))
|
|
@@ -98,11 +130,11 @@ public struct TrustBanner: View {
|
|
|
98
130
|
|
|
99
131
|
Spacer()
|
|
100
132
|
HStack{
|
|
101
|
-
WebImage(url: URL(string:
|
|
133
|
+
WebImage(url: URL(string: trustBanner?.pciImage ?? defaultBanner.pciImage))
|
|
102
134
|
.resizable()
|
|
103
135
|
.scaledToFit()
|
|
104
136
|
.frame(width: 24, height: 20)
|
|
105
|
-
WebImage(url: URL(string:
|
|
137
|
+
WebImage(url: URL(string: trustBanner?.sslImage ?? defaultBanner.sslImage))
|
|
106
138
|
.resizable()
|
|
107
139
|
.scaledToFit()
|
|
108
140
|
.frame(width: 52, height: 20)
|
|
@@ -52,7 +52,7 @@ public struct FontScale: Equatable {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/// A host-pushed mini-app context wins per field, matching Compose's
|
|
55
|
-
/// `context?.useOSScaleRate ?:
|
|
55
|
+
/// `context?.useOSScaleRate ?: DesignSystemKits.getConfigs().useOSScaleRate`.
|
|
56
56
|
public func overriding(userScaleRate: CGFloat?, useOSScaleRate: Bool?) -> FontScale {
|
|
57
57
|
guard userScaleRate != nil || useOSScaleRate != nil else { return self }
|
|
58
58
|
return FontScale(
|
|
@@ -66,7 +66,7 @@ public struct FontScale: Equatable {
|
|
|
66
66
|
private struct FontScaleKey: EnvironmentKey {
|
|
67
67
|
/// Computed, never stored: a bare `UIHostingController` root that never got `.momoFontScale()`
|
|
68
68
|
/// still reads the live store instead of a value frozen at first access.
|
|
69
|
-
static var defaultValue: FontScale {
|
|
69
|
+
static var defaultValue: FontScale { DesignSystemKits.shared.fontScale }
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
public extension EnvironmentValues {
|
|
@@ -77,9 +77,10 @@ public extension EnvironmentValues {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
public extension View {
|
|
80
|
-
/// Injects a Dynamic-Type-observing font scale
|
|
80
|
+
/// Injects a Dynamic-Type-observing font scale plus `\.designSystemKitsConfigs` (whitelist, remote config)
|
|
81
|
+
/// and re-renders this subtree on change.
|
|
81
82
|
/// Use at every `UIHostingController` root that is not inside a `NavigationContainer`.
|
|
82
|
-
func momoFontScale(_ store:
|
|
83
|
+
func momoFontScale(_ store: DesignSystemKits = .shared, context: MiniAppContext? = nil) -> some View {
|
|
83
84
|
modifier(MomoFontScaleModifier(store: store, context: context))
|
|
84
85
|
}
|
|
85
86
|
}
|
|
@@ -87,17 +88,19 @@ public extension View {
|
|
|
87
88
|
/// `@ObservedObject` is load-bearing, same as `MomoLocalizeModifier`: `@Environment(\.momoFontScale)`
|
|
88
89
|
/// only invalidates when the boxed snapshot changes, which requires *this* body to re-run on publish.
|
|
89
90
|
private struct MomoFontScaleModifier: ViewModifier {
|
|
90
|
-
@ObservedObject var store:
|
|
91
|
+
@ObservedObject var store: DesignSystemKits
|
|
91
92
|
let context: MiniAppContext?
|
|
92
93
|
|
|
93
94
|
func body(content: Content) -> some View {
|
|
94
|
-
content
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
content
|
|
96
|
+
.environment(
|
|
97
|
+
\.momoFontScale,
|
|
98
|
+
store.fontScale.overriding(
|
|
99
|
+
userScaleRate: context?.userScaleRate,
|
|
100
|
+
useOSScaleRate: context?.useOSScaleRate
|
|
101
|
+
)
|
|
99
102
|
)
|
|
100
|
-
|
|
103
|
+
.environment(\.designSystemKitsConfigs, store.getConfigs())
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
106
|
// AI-GENERATED END: reactive font-scale snapshot injected through the environment
|
|
@@ -4,7 +4,7 @@ import SwiftUI
|
|
|
4
4
|
/// free functions). Inside a SwiftUI view prefer `@Environment(\.momoFontScale)` so the view
|
|
5
5
|
/// re-renders when Dynamic Type or the host preference changes.
|
|
6
6
|
public func scaleSize(_ size: CGFloat) -> CGFloat {
|
|
7
|
-
|
|
7
|
+
DesignSystemKits.shared.fontScale(size)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
@available(*, deprecated, message: "Max font scale is fixed at 1.5; this override is no longer applied. Drop the second argument.")
|
|
@@ -99,9 +99,9 @@ public extension TypographyStyle {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
private func momoFontName(family: String, weight: Font.Weight) -> String? {
|
|
102
|
+
private func momoFontName(family: String, weight: Font.Weight, isWhiteList: Bool) -> String? {
|
|
103
103
|
// Ngoai whitelist thi tra nil de momoFont() roi ve .system(...), dung nhu truoc DS-791.
|
|
104
|
-
guard
|
|
104
|
+
guard isWhiteList else { return nil }
|
|
105
105
|
guard family == "MomoTrustSans" else { return nil }
|
|
106
106
|
switch weight {
|
|
107
107
|
case .ultraLight, .thin: return "MomoTrustSans-ExtraLight"
|
|
@@ -115,8 +115,8 @@ private func momoFontName(family: String, weight: Font.Weight) -> String? {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
private func momoFont(size: CGFloat, weight: Font.Weight, family: String) -> Font {
|
|
119
|
-
guard let name = momoFontName(family: family, weight: weight),
|
|
118
|
+
private func momoFont(size: CGFloat, weight: Font.Weight, family: String, isWhiteList: Bool) -> Font {
|
|
119
|
+
guard let name = momoFontName(family: family, weight: weight, isWhiteList: isWhiteList),
|
|
120
120
|
UIFont(name: name, size: size) != nil else {
|
|
121
121
|
return .system(size: size, weight: weight)
|
|
122
122
|
}
|
|
@@ -126,6 +126,8 @@ private func momoFont(size: CGFloat, weight: Font.Weight, family: String) -> Fon
|
|
|
126
126
|
public struct MomoText: View {
|
|
127
127
|
@Environment(\.appTheme) private var theme
|
|
128
128
|
@Environment(\.momoFontScale) private var fontScale
|
|
129
|
+
// From the environment, not DesignSystemKits.shared, so a whitelist flip re-renders the text.
|
|
130
|
+
@Environment(\.designSystemKitsConfigs) private var designSystemKitsConfigs
|
|
129
131
|
private let content: String
|
|
130
132
|
private let typography: TypographyStyle
|
|
131
133
|
private let color: Color?
|
|
@@ -186,7 +188,12 @@ public struct MomoText: View {
|
|
|
186
188
|
private var baseText: SwiftUI.Text {
|
|
187
189
|
let keywords = highlightText.filter { !$0.isEmpty }.sorted { $0.count > $1.count }
|
|
188
190
|
guard !keywords.isEmpty else { return SwiftUI.Text(content) }
|
|
189
|
-
let highlightFont = momoFont(
|
|
191
|
+
let highlightFont = momoFont(
|
|
192
|
+
size: fontScale(typography.fontSize),
|
|
193
|
+
weight: TypographyStyle.headerXsSemibold.fontWeight,
|
|
194
|
+
family: theme.font,
|
|
195
|
+
isWhiteList: designSystemKitsConfigs.isWhiteList
|
|
196
|
+
)
|
|
190
197
|
func isWordChar(_ character: Character?) -> Bool {
|
|
191
198
|
guard let character else { return false }
|
|
192
199
|
return character.isLetter || character.isNumber || character == "_"
|
|
@@ -223,7 +230,12 @@ public struct MomoText: View {
|
|
|
223
230
|
let isStrikethrough = strikethrough || typography.isStrikethrough
|
|
224
231
|
|
|
225
232
|
var text = baseText
|
|
226
|
-
.font(momoFont(
|
|
233
|
+
.font(momoFont(
|
|
234
|
+
size: fontScale(typography.fontSize),
|
|
235
|
+
weight: typography.fontWeight,
|
|
236
|
+
family: theme.font,
|
|
237
|
+
isWhiteList: designSystemKitsConfigs.isWhiteList
|
|
238
|
+
))
|
|
227
239
|
.foregroundColor(textColor)
|
|
228
240
|
.strikethrough(isStrikethrough, color: textColor)
|
|
229
241
|
.underline(underline, color: textColor)
|
|
@@ -10,12 +10,12 @@ public extension View {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
public extension Font {
|
|
13
|
-
/// Non-reactive: resolves against the current `
|
|
13
|
+
/// Non-reactive: resolves against the current `DesignSystemKits` font scale at each access. The
|
|
14
14
|
/// styles below are computed properties for that reason — as stored `static let`s they would
|
|
15
15
|
/// freeze the scale at first access and never pick up a Dynamic Type change.
|
|
16
16
|
/// Inside a view, `@Environment(\.momoFontScale)` + `fontScale.font(size:weight:)` also re-renders.
|
|
17
17
|
static func appFont(size: CGFloat) -> Font {
|
|
18
|
-
|
|
18
|
+
DesignSystemKits.shared.fontScale.font(size: size)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// New supported typography styles
|
package/ios/native-kits.podspec
CHANGED
package/package.json
CHANGED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import SwiftUI
|
|
2
|
-
|
|
3
|
-
/// App-wide font-scale inputs for the SwiftUI kit, and the single place they publish from.
|
|
4
|
-
/// The host writes `update(...)` at startup (from the cache-backed `FontScaleProvider`) and again
|
|
5
|
-
/// whenever the in-app font-size preference changes; the OS Dynamic Type factor is tracked here.
|
|
6
|
-
/// Mirrors Compose's `KitFontScale` + `ProvideOsFontScale` pair, including its live behaviour —
|
|
7
|
-
/// views re-render on change as long as the subtree got `.momoFontScale()` (`NavigationContainer`
|
|
8
|
-
/// applies it). When `useOSScaleRate` is false the kit applies `userScaleRate` instead of the OS factor.
|
|
9
|
-
public final class FontScaleStore: ObservableObject {
|
|
10
|
-
public static let shared = FontScaleStore()
|
|
11
|
-
|
|
12
|
-
@Published public private(set) var userScaleRate: CGFloat = 1
|
|
13
|
-
@Published public private(set) var useOSScaleRate: Bool = true
|
|
14
|
-
@Published public private(set) var osFontScale: CGFloat = FontScaleStore.currentOsFontScale()
|
|
15
|
-
|
|
16
|
-
private init() {
|
|
17
|
-
NotificationCenter.default.addObserver(
|
|
18
|
-
self,
|
|
19
|
-
selector: #selector(contentSizeCategoryDidChange),
|
|
20
|
-
name: UIContentSizeCategory.didChangeNotification,
|
|
21
|
-
object: nil
|
|
22
|
-
)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public var snapshot: FontScale {
|
|
26
|
-
FontScale(
|
|
27
|
-
userScaleRate: userScaleRate,
|
|
28
|
-
useOSScaleRate: useOSScaleRate,
|
|
29
|
-
osFontScale: osFontScale
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public func update(userScaleRate: CGFloat, useOSScaleRate: Bool) {
|
|
34
|
-
onMain {
|
|
35
|
-
// Re-assigning an unchanged value would still publish and invalidate every kit subtree.
|
|
36
|
-
if self.userScaleRate != userScaleRate { self.userScaleRate = userScaleRate }
|
|
37
|
-
if self.useOSScaleRate != useOSScaleRate { self.useOSScaleRate = useOSScaleRate }
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
@objc private func contentSizeCategoryDidChange() {
|
|
42
|
-
let next = Self.currentOsFontScale()
|
|
43
|
-
guard next != osFontScale else { return }
|
|
44
|
-
osFontScale = next
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/// Same factor Compose's iOS `ProvideOsFontScale` derives from
|
|
48
|
-
/// `UIFont.preferredFont(forTextStyle: .body).pointSize / 17`, so both iOS surfaces scale alike.
|
|
49
|
-
static func currentOsFontScale() -> CGFloat {
|
|
50
|
-
let base = UIFont.systemFont(ofSize: UIFont.labelFontSize)
|
|
51
|
-
return UIFontMetrics.default.scaledFont(for: base).pointSize / base.pointSize
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/// Synchronous when already on main — an async hop would defer the change by a frame.
|
|
55
|
-
private func onMain(_ work: @escaping () -> Void) {
|
|
56
|
-
if Thread.isMainThread { work() } else { DispatchQueue.main.async(execute: work) }
|
|
57
|
-
}
|
|
58
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
|
-
/// Design-system rollout flag for the SwiftUI kit, mirroring Compose's `KitDesignSystem`.
|
|
4
|
-
/// The kit is sandboxed and can't read the host config, so the host writes `update(_:)` once at
|
|
5
|
-
/// startup from `DesignSystemConfig`. Startup-only and non-reactive, like `FontScaleStore` was
|
|
6
|
-
/// before it gained live updates: there is no SwiftUI navigation container to re-render from.
|
|
7
|
-
public final class KitDesignSystem {
|
|
8
|
-
public static let shared = KitDesignSystem()
|
|
9
|
-
|
|
10
|
-
public private(set) var isWhiteList: Bool = false
|
|
11
|
-
|
|
12
|
-
private init() {}
|
|
13
|
-
|
|
14
|
-
public func update(isWhiteList: Bool) {
|
|
15
|
-
self.isWhiteList = isWhiteList
|
|
16
|
-
}
|
|
17
|
-
}
|