@momo-kits/native-kits 0.162.2-sp.7-debug → 0.162.2-sp.8-debug
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/settings.local.json +15 -1
- package/CLAUDE.md +78 -0
- package/compose/build.gradle.kts +12 -31
- package/compose/build.gradle.kts.backup +11 -30
- package/compose/compose.podspec +1 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +2 -7
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +8 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +6 -24
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +4 -19
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +1 -10
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +6 -17
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +6 -101
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +8 -19
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +8 -33
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +22 -25
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +27 -29
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +3 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +30 -31
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +3 -13
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +3 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +3 -18
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +11 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +42 -65
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +69 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +84 -59
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +13 -71
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +10 -51
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +6 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +4 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +16 -75
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +5 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +3 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +16 -40
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +3 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -28
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +2 -13
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +4 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +4 -10
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +3 -28
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +8 -47
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +4 -25
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +2 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +1 -17
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +1 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +50 -111
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +1 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +1 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +1 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +4 -96
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +5 -7
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +2 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +9 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +9 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +8 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +22 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +11 -20
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +94 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +3 -7
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +9 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +36 -24
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +48 -33
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +67 -61
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +6 -13
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +13 -31
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +6 -24
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +11 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +9 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +3 -13
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +3 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +1 -40
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -8
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +2 -17
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +3 -3
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +2 -3
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -2
- package/gradle/libs.versions.toml +2 -0
- package/gradle.properties +1 -1
- package/ios/Application/ApplicationEnvironment.swift +4 -2
- package/ios/Application/Components.swift +16 -0
- package/ios/Application/Localize.swift +277 -0
- package/ios/Application/MaxApi.swift +18 -0
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +1 -0
- package/ios/Application/Navigation/NavigationContainer.swift +27 -3
- package/ios/Application/Navigation/Navigator.swift +50 -29
- package/ios/Application/Navigation/Overplay/BottomSheet.swift +87 -19
- package/ios/Application/Navigation/Overplay/ModalScreen.swift +6 -0
- package/ios/Application/Navigation/Tracking/ScreenTracker.swift +117 -0
- package/ios/Application/Screen.swift +1 -1
- package/ios/Application/StackScreen.swift +49 -1
- package/ios/Badge/BadgeRibbon.swift +80 -0
- package/ios/Button/Button.swift +68 -12
- package/ios/Carousel/Carousel.swift +16 -1
- package/ios/Checkbox/Checkbox.swift +14 -20
- package/ios/Chip/Chip.swift +35 -26
- package/ios/Collapse/Collapse.swift +11 -1
- package/ios/Colors+Radius+Spacing/Colors.swift +9 -6
- package/ios/Colors+Radius+Spacing/Spacing.swift +3 -1
- package/ios/DateTimePicker/DateTimePicker.swift +4 -2
- package/ios/DateTimePicker/WheelPicker.swift +7 -3
- package/ios/Extensions/ActiveOpacityButtonStyle.swift +14 -0
- package/ios/Extensions/Color++.swift +5 -2
- package/ios/Icon/Icon.swift +5 -1
- package/ios/IconButton/IconButton.swift +35 -7
- package/ios/Image/Image.swift +16 -3
- package/ios/Information/Information.swift +3 -1
- package/ios/Input/ErrorView.swift +3 -1
- package/ios/Input/Input.swift +31 -8
- package/ios/Input/InputPhoneNumber.swift +14 -3
- package/ios/Input/InputSearch.swift +7 -1
- package/ios/Input/InputTextArea.swift +78 -34
- package/ios/InputDropDown/InputDropDown.swift +4 -1
- package/ios/InputMoney/InputMoney.swift +16 -4
- package/ios/PARITY_AUDIT.md +641 -0
- package/ios/Popup/PopupDisplay.swift +3 -4
- package/ios/Popup/PopupNotify.swift +3 -4
- package/ios/Popup/PopupPromotion.swift +16 -8
- package/ios/ProgressInfo/ProgressInfo.swift +4 -2
- package/ios/Radio/Radio.swift +12 -13
- package/ios/Rating/Rating.swift +1 -1
- package/ios/Stepper/Stepper.swift +2 -2
- package/ios/Steps/Steps.swift +22 -6
- package/ios/SuggestAction/SuggestAction.swift +5 -3
- package/ios/Swipeable/SwipeCell.swift +5 -3
- package/ios/Switch/Switch.swift +53 -28
- package/ios/TabView/TabView.swift +18 -14
- package/ios/Template/TrustBanner/TrustBanner.swift +6 -7
- package/ios/Tooltip/Tooltip.swift +3 -0
- package/ios/Typography/FontScaleStore.swift +19 -0
- package/ios/Typography/Text.swift +3 -0
- package/ios/Typography/Typography.swift +8 -5
- package/ios/Uploader/Uploader.swift +0 -9
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/package.json +1 -1
- package/ios/Colors+Radius+Spacing/MoMoUIKitsResources.swift +0 -2
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
# iOS Swift Kit Parity Audit
|
|
2
|
+
|
|
3
|
+
Tracks the ongoing parity pass between `compose/src/commonMain/kotlin/vn/momo/kits/` (source of truth)
|
|
4
|
+
and the hand-maintained native SwiftUI port under `ios/`. Appended to per tier/batch so the pass is
|
|
5
|
+
resumable across sessions without re-deriving prior findings.
|
|
6
|
+
|
|
7
|
+
## Tier 0 — Tokens
|
|
8
|
+
|
|
9
|
+
- **Colors.swift**: fixed scrambled violet scale (violet04/05/06/09/10 held wrong Kotlin values, shifted
|
|
10
|
+
by 1-3 steps); added missing `violet04` value (`a03ff1`), `violet11`, `violet11Stroke`; added missing
|
|
11
|
+
`pinkMoMoBranding`; fixed `info` alias to reference `blue03` instead of a duplicated hardcoded hex.
|
|
12
|
+
No existing call sites used the wrong violet tokens (grepped, zero hits), so this was a pure bug fix
|
|
13
|
+
with no visual regression risk.
|
|
14
|
+
- **Spacing.swift**: added missing `Size4XL`(56)/`Size5XL`(64); renamed `XXXL`→`Size3XL` to match Kotlin
|
|
15
|
+
(zero external call sites found, so the rename is safe, not just aliased).
|
|
16
|
+
- **Typography.swift** (`ios/Typography/Typography.swift`): **no fix needed.** The initial audit flagged
|
|
17
|
+
missing strikethrough variants here, but this file's snake_case `Font` extension is dead code (zero
|
|
18
|
+
call sites anywhere in `ios/`). The actual typography system used throughout the port is
|
|
19
|
+
`TypographyStyle`/`MomoText` in `ios/Typography/Text.swift`, which already has full 1:1 parity with
|
|
20
|
+
Kotlin's `AppTypography`, including all three strikethrough variants. Flagged as stale/legacy, not
|
|
21
|
+
touched — removing dead code was out of scope for this pass.
|
|
22
|
+
|
|
23
|
+
## Tier 1 — Navigation core
|
|
24
|
+
|
|
25
|
+
- **`Navigator.pop()` snackbar bug — fixed.** Kotlin's `pop()` special-cases `SNACK_BAR` to fall through
|
|
26
|
+
and pop the screen underneath (confirmed at `Navigator.kt:84-91`); Swift's `pop()` previously treated
|
|
27
|
+
any `overplay != nil` (including snackbar) as fully consuming the pop via `hideOverplay()`, silently
|
|
28
|
+
swallowing back-navigation while a snackbar was showing. Fixed by special-casing `.snackBar` to clear
|
|
29
|
+
the overplay immediately (no exit animation, matching Kotlin's `hardClearAfterDismiss()`) and falling
|
|
30
|
+
through to pop the screen. Extracted the screen-pop branch into a `popScreen() -> Bool` helper to avoid
|
|
31
|
+
duplicating it between the snackbar and normal branches while preserving the original "nothing left to
|
|
32
|
+
pop → request host dismiss and break" behavior. Internal control-flow only, no signature change.
|
|
33
|
+
- **`DynamicScreenRegistry` singleton → instance-scoped — fixed.** Was a process-wide `static let shared`
|
|
34
|
+
(`Navigator.swift:33`); Kotlin scopes one registry per `NavigationContainer` and disposes it
|
|
35
|
+
(`remember { DynamicScreenRegistry() }`). Converted to a `public let registry = DynamicScreenRegistry()`
|
|
36
|
+
owned by `Navigator`; disposal now happens implicitly via ARC when the `Navigator` (`@StateObject` in
|
|
37
|
+
`NavigationContainer`) deallocates, rather than needing an explicit teardown call. Contained to 2 files
|
|
38
|
+
(`Navigator.swift`, `NavigationContainer.swift` — the only two referencing the old singleton, confirmed
|
|
39
|
+
by repo-wide grep). **Behavior-changing**: multiple concurrent `NavigationContainer`s no longer share
|
|
40
|
+
global screen-ID state — call this out explicitly if this pass gets reviewed/merged.
|
|
41
|
+
- **Deferred, documented gaps (not fixed this pass):** Kotlin's `Navigation.kt` (per-screen mutable
|
|
42
|
+
`NavigationOptions` + `setOptions()` + `LocalNavigation`, letting a pushed screen imperatively mutate
|
|
43
|
+
its own header at runtime) has no Swift port at all — new API surface, bigger scope than a parity fix.
|
|
44
|
+
The `IsShowBaseLineDebug` global debug-border flag also has no Swift equivalent — low priority.
|
|
45
|
+
- Verified via full `iosApp` scheme build (`xcodebuild ... -scheme iosApp -destination 'id=EFD1594B-...'`,
|
|
46
|
+
iPhone 14 / iOS 16.4 simulator) — `** BUILD SUCCEEDED **`. This exercises the real `Demo/*.swift` call
|
|
47
|
+
sites, confirming no signature breakage from the registry/pop changes.
|
|
48
|
+
|
|
49
|
+
## Tier 2 — Cross-cutting subsystems (Localize, ScreenTracker)
|
|
50
|
+
|
|
51
|
+
- **`ios/Application/MaxApi.swift` (new).** Minimal `MaxApi` protocol (`track`, `dismiss`, `getLanguage`)
|
|
52
|
+
mirroring `vn.momo.maxapi.IMaxApi` — scoped to exactly the 3 methods actually called anywhere in the
|
|
53
|
+
Kotlin `kits` source (confirmed by a repo-wide grep for `maxApi?.`/`api.` call sites: `ScreenTracker.kt`
|
|
54
|
+
calls `track`, `Navigator.kt` calls `dismiss`, `NavigationContainer.kt` calls `getLanguage`). `IMaxApi`
|
|
55
|
+
itself is an external compiled dependency with no source in this repo, so this is intentionally not a
|
|
56
|
+
guess at its full surface.
|
|
57
|
+
- **`ios/Application/Navigation/Tracking/ScreenTracker.swift` (new).** 1:1 port of `ScreenTracker.kt` —
|
|
58
|
+
`ScreenTrackingState`, `trackScreenNavigated/Displayed/Interacted`, `trackPopupDisplayed`,
|
|
59
|
+
`getLastScreenName`/`reset`. Pure logic, no platform dependency beyond `MaxApi`.
|
|
60
|
+
- **`ios/Application/Localize.swift` (new).** 1:1 port of `Localize.kt` — `LocalizationObject`,
|
|
61
|
+
`Localize` (`ObservableObject`, `@Published currentLanguage` so a language switch republishes to
|
|
62
|
+
consumers), `translate`/`translateData`(×2)/`addTranslations`/`changeLanguage`, and the full 101-key
|
|
63
|
+
VI/EN default dictionary. **Verified via scripted key-set AND value diff against `Localize.kt`: 0
|
|
64
|
+
mismatches** (all 202 key/value pairs across both languages transcribed exactly).
|
|
65
|
+
- **Wiring**: `ApplicationEnvironment` gained an additive `maxApi: MaxApi?` field (default `nil`).
|
|
66
|
+
`NavigationContainer` gained additive `maxApi`/`localize` params; on `.onAppear` it now calls
|
|
67
|
+
`maxApi?.getLanguage` and switches to `EN` only when the host reports it (mirrors Compose's
|
|
68
|
+
`LaunchedEffect(maxApi, resolvedLocalize)` + `parseLanguage`). `StackScreen` gained an additive
|
|
69
|
+
`screenName` param and a `.task(id: screenName)` that fires `trackScreenNavigated` immediately, then
|
|
70
|
+
`trackScreenDisplayed`/`trackScreenInteracted` after a 2s debounce — mirrors Compose's
|
|
71
|
+
`LaunchedEffect(name)` + `delay(2000ms)` exactly, including the once-computed push/back `action` value.
|
|
72
|
+
`ModalScreen`/`BottomSheetView` each gained `trackPopupDisplayed` in their existing `onAppear` guard.
|
|
73
|
+
`SnackBarView` intentionally gets **no** tracking call — confirmed Kotlin's `SnackBar.kt` doesn't call
|
|
74
|
+
`ScreenTracker` either, so this isn't a gap.
|
|
75
|
+
- **Demo-only verification aid**: `sample/iosApp/iosApp/Demo/ConsoleMaxApi.swift` (new, demo target only,
|
|
76
|
+
not shipped in the pod) implements `MaxApi` by logging every call; wired into `Home.swift`'s root
|
|
77
|
+
`NavigationContainer`. Added to the `iosApp` Xcode target via the `xcodeproj` Ruby gem (safer than
|
|
78
|
+
hand-editing `project.pbxproj`).
|
|
79
|
+
- **Verification**: `pod install` (new source files needed the Pods project regenerated to pick them up —
|
|
80
|
+
the podspec's `Application/**/*` glob already covered the new paths, just needed re-scanning), then both
|
|
81
|
+
`MoMoUIKits` and `iosApp` scheme builds — `** BUILD SUCCEEDED **`, zero signature-breakage. A live
|
|
82
|
+
simulator console-log smoke check was attempted (launch the demo app, capture stdout/stderr via
|
|
83
|
+
`simctl launch --stdout/--stderr` and via `log stream`) but did not surface `print()` output in this
|
|
84
|
+
sandboxed environment — GUI apps launched via `simctl` don't reliably pipe raw stdout the way a CLI
|
|
85
|
+
process does, independent of whether the tracking/localize code itself is correct. Not pursued further
|
|
86
|
+
since it's a documented "nice to have" per the plan, not the mandatory gate; the compile + dictionary-diff
|
|
87
|
+
verification already gives strong evidence the wiring is structurally correct.
|
|
88
|
+
|
|
89
|
+
## Tier 3 — Component batches
|
|
90
|
+
|
|
91
|
+
### Batch 1 — Static display atoms (Avatar, Badge, BadgeDot, BadgeRibbon, Divider, Icon, Image, Tag)
|
|
92
|
+
|
|
93
|
+
- **Avatar, Badge, BadgeDot, Divider**: full parity, no findings (only the already-deferred
|
|
94
|
+
`IsShowBaseLineDebug` debug border gap, consistent with Tier 1's decision to leave that undone).
|
|
95
|
+
- **BadgeRibbon**: `RoundedBadgeRibbon` (BadgeRibbon.kt:164-326) — a canvas-drawn ribbon shape sized to
|
|
96
|
+
its text — had **zero Swift port**. **Fixed**: added `ios/Badge/BadgeRibbon.swift`'s new
|
|
97
|
+
`RoundedBadgeRibbon` view, using SwiftUI `Canvas` + the file's existing `RoundedCorners` shape helper
|
|
98
|
+
for the per-corner-radius path math, and the file's pre-existing (previously unused)
|
|
99
|
+
`String.size(withAttributes:)` extension for text measurement — that extension being already present
|
|
100
|
+
but unused strongly suggests someone scaffolded this port before and didn't finish it. Colors
|
|
101
|
+
(`orange03`/`red01`) reference tokens instead of Kotlin's hardcoded hex literals. Flagged, not touched:
|
|
102
|
+
Swift's extra public `backgroundColor` param on `BadgeRibbon` (not in Kotlin's public API) — removing
|
|
103
|
+
would break existing callers for no parity benefit.
|
|
104
|
+
- **Icon**: two findings.
|
|
105
|
+
1. Kotlin defaults `color` to the theme's text-default color (tinting every icon unless in
|
|
106
|
+
`noThemeIcons`); Swift defaults to `nil` (untinted). Changes rendering for most existing callers —
|
|
107
|
+
**flagged only**, not changed.
|
|
108
|
+
2. Swift's public `accessibilityLabel` param was accepted but never applied anywhere in `body`. **Fixed**
|
|
109
|
+
— wired via `.accessibilityLabel(accessibilityLabel ?? "img|\(uri)")`, mirroring Kotlin's
|
|
110
|
+
auto-generated `contentDescription = "img|$iconUrl"` default while still honoring an explicit
|
|
111
|
+
override. Non-breaking (previously-dead prop becoming live is a strict improvement, not a behavior
|
|
112
|
+
change for existing callers who weren't setting it).
|
|
113
|
+
- **Image / ImageView**: several findings.
|
|
114
|
+
1. Default `contentMode` differs (Kotlin crops by default, Swift fits) — changes default visual fit for
|
|
115
|
+
unconfigured callers. **Flagged only**, not changed.
|
|
116
|
+
2. Hardcoded `Colors.black08` error-placeholder tint bypassing the theme (Kotlin uses
|
|
117
|
+
`theme.colors.text.disable`). **Fixed** — added `@Environment(\.appTheme)` and swapped in
|
|
118
|
+
`theme.colors.text.disable`.
|
|
119
|
+
3. Missing `loading: Bool` toggle (to suppress the skeleton placeholder) and `onStateChanged` callback +
|
|
120
|
+
`ImageState` enum. **Fixed** — both added additively (defaults preserve current behavior:
|
|
121
|
+
`loading` defaults `true`, `onStateChanged` defaults `nil`).
|
|
122
|
+
4. **Deferred, not fixed**: Kotlin's CDN size-optimization (`supportedDomains`/`sizeChart`/
|
|
123
|
+
`appendSizeQueryParam`, appending a `?size=` query param sized to the rendered width) and GIF
|
|
124
|
+
detection/`GifImage` support have no Swift equivalent. These are real feature gaps but substantially
|
|
125
|
+
larger scope than a parity bug fix (new URL-processing pipeline, new dependency for GIF playback) —
|
|
126
|
+
documented here rather than silently dropped.
|
|
127
|
+
- **Tag**: Kotlin's public `customColor: Color?` param has no Swift equivalent — **but confirmed dead code
|
|
128
|
+
even in the Kotlin source itself**: `Tag.kt:58-61`'s `if (primaryColors.contains(customColor))` branch
|
|
129
|
+
reassigns `tagColor`/`labelColor` to the exact values they already held, regardless of the branch outcome.
|
|
130
|
+
Porting an inert param for signature-parity's sake would replicate a no-op rather than fix a gap —
|
|
131
|
+
**not ported**, documented as an intentional skip.
|
|
132
|
+
- Verified via `MoMoUIKits` scheme build and full `iosApp` scheme build (exercises real Demo call sites) —
|
|
133
|
+
both `** BUILD SUCCEEDED **`.
|
|
134
|
+
|
|
135
|
+
### Batch 2 — Typographic/status atoms (Text, Title, Tooltip, Information, ProgressInfo, Loader, Skeleton, BaselineView)
|
|
136
|
+
|
|
137
|
+
- **Title, Loader, BaselineView**: full parity, no findings.
|
|
138
|
+
- **Text/MomoText**: Kotlin's `Text` has a missing-ellipsis mode (`overflow = Clip` default, no truncation
|
|
139
|
+
indicator) that `MomoText`/SwiftUI's `truncationMode` can't express (always shows "…" when clipped) —
|
|
140
|
+
**flagged only**, would need a new API shape. Also missing `onTextLayout`, `inlineContent` (rich inline
|
|
141
|
+
spans), an `AnnotatedString` overload, and a `fontFamily` override — all real gaps but substantial new
|
|
142
|
+
capability (rich-text spans, layout measurement callbacks), not simple parity bugs. **Deferred**,
|
|
143
|
+
documented rather than partially implemented, consistent with the Image CDN/GIF deferral in Batch 1.
|
|
144
|
+
- **Tooltip**: two findings.
|
|
145
|
+
1. Positioning architecture differs fundamentally (Compose: OS-level `Popup` with outside-tap dismiss +
|
|
146
|
+
no clipping; Swift: in-hierarchy overlay, no outside-tap dismiss, can be clipped by ancestors).
|
|
147
|
+
**Flagged only** — an architecture change, not a small fix.
|
|
148
|
+
2. Missing width-yielding constraint on the 2-button secondary action (Compose:
|
|
149
|
+
`Modifier.weight(1f, fill=false)`). **Fixed** — added `.layoutPriority(-1)` to
|
|
150
|
+
`TooltipSecondaryButton` so it shrinks before the primary button under width pressure. Low practical
|
|
151
|
+
impact since both buttons already pre-truncate their title to 16 chars, but cheap and correct to add.
|
|
152
|
+
- **Information**: close-icon color was hardcoded to `Colors.black17` instead of following the theme
|
|
153
|
+
(Kotlin's icon has no explicit color, defaulting to `theme.colors.text.default`). Values coincide under
|
|
154
|
+
the default theme so no visible change today, but breaks for non-default themes. **Fixed** — added
|
|
155
|
+
`@Environment(\.appTheme)` and switched to `theme.colors.text.default`.
|
|
156
|
+
- **ProgressInfo**: two findings, both **fixed**.
|
|
157
|
+
1. Vertical connector line color was hardcoded to the asset-catalog `Colors.primary` instead of
|
|
158
|
+
`theme.colors.primary.opacity(0.2)` — added `@Environment(\.appTheme)` to `ProgressInfoVertical` and
|
|
159
|
+
switched. Changes rendered output under non-default themes only (matches source-of-truth behavior).
|
|
160
|
+
2. `progressInfoAccentColor` duplicated the `pinkMoMoBranding` hex as a raw literal (with a comment
|
|
161
|
+
already noting what it was meant to be) instead of referencing the token added in Tier 0 — swapped to
|
|
162
|
+
`Colors.pinkMoMoBranding`.
|
|
163
|
+
- **Skeleton**: two findings.
|
|
164
|
+
1. Extra public `durationMillis`/`angleOfAxisY` params not in Kotlin's zero-arg `Skeleton()`. **Flagged
|
|
165
|
+
only** — existing Swift callers may depend on these, can't remove.
|
|
166
|
+
2. Shimmer travel-distance formula differs from Kotlin's literal `durationMillis + widthOfShadowBrush`
|
|
167
|
+
(which mixes milliseconds with pixels). **Investigated, not changed**: the Swift file already carries
|
|
168
|
+
an explanatory comment showing this was a deliberate, reasoned substitution — Kotlin's formula is
|
|
169
|
+
dimensionally inconsistent and produces an arguably-unintended sweep distance/speed depending on
|
|
170
|
+
container width; Swift's `size.width + widthOfShadowBrush` is dimensionally consistent and always
|
|
171
|
+
produces a full-width sweep regardless of container size. Re-matching Kotlin's literal formula here
|
|
172
|
+
would reintroduce a questionable behavior rather than fix a real bug — left as is, documented rather
|
|
173
|
+
than silently reverted.
|
|
174
|
+
- Verified via `MoMoUIKits` scheme build — `** BUILD SUCCEEDED **`.
|
|
175
|
+
|
|
176
|
+
### Button / IconButton / Input — fixes from the initial (pre-plan) exploration round
|
|
177
|
+
|
|
178
|
+
These three were audited before the formal plan existed; applying their fixes was deferred until now.
|
|
179
|
+
|
|
180
|
+
- **Button.swift**:
|
|
181
|
+
1. Removed the dead unreachable `default:` case in `typographyStyle` (the switch over `.large/.medium/.small` was already exhaustive).
|
|
182
|
+
2. **Fixed the press-squeeze animation**: previously alpha-only; restructured so `ButtonPressFeedbackStyle`
|
|
183
|
+
builds the full background+content layering itself (background as a `ZStack` layer with an animated
|
|
184
|
+
0→2pt horizontal inset, content as `configuration.label` with the alpha fade) — mirrors Kotlin's
|
|
185
|
+
`animatedPadding`-on-background + separate content alpha exactly, rather than dimming the whole button
|
|
186
|
+
uniformly.
|
|
187
|
+
3. **Added a `String`-based icon initializer** alongside the existing `AnyView`-based one, resolving
|
|
188
|
+
icons through the shared `Icon` component like Kotlin's `iconLeft`/`iconRight: String` params. Made
|
|
189
|
+
`iconLeft` required (no default) on this new overload specifically to avoid an `init` ambiguity — a
|
|
190
|
+
call like `Button(action: {})` would otherwise match both overloads since both have all-defaulted
|
|
191
|
+
params past `action`. Verified via a full `iosApp` build that no existing call site broke.
|
|
192
|
+
4. Default title on the **new** String-icon overload matches Kotlin's `"Button"`; the original
|
|
193
|
+
`AnyView`-based init's default (`""`) was left untouched (Flag) — changing it would alter output for
|
|
194
|
+
any existing Swift caller omitting `title`.
|
|
195
|
+
- **IconButton.swift**: added the missing `IconSize.medium` (36pt box / 16pt icon, matching Kotlin exactly)
|
|
196
|
+
and the missing `IconShape` (`.circle`/`.square`) enum + param (default `.circle`, matching Kotlin).
|
|
197
|
+
Fixed `.small`'s box size from a fabricated `40` to the correct `28`. The previous `Radius.XL`
|
|
198
|
+
rounded-rect shape (which matched neither Kotlin variant) is replaced by a real `Circle()`/`Radius.S`
|
|
199
|
+
rounded-rect switch via a small hand-rolled `Shape` type eraser (`AnyShape` needs iOS 17+, deployment
|
|
200
|
+
target is 15). This does change default rendering for existing callers (true circle instead of a
|
|
201
|
+
large-radius rounded rect) — treated as a Fix rather than a Flag since the old shape was never a
|
|
202
|
+
deliberate design choice, just an unlabeled placeholder value matching neither source-of-truth variant.
|
|
203
|
+
Left the extra public `color` override alone (Flag, unchanged from earlier audit).
|
|
204
|
+
- **Input.swift** / **ErrorView.swift**:
|
|
205
|
+
1. Wired the previously-dead `autofocus` prop via `@FocusState` on the plain `TextField` path and
|
|
206
|
+
`textField.becomeFirstResponder()` (dispatched to next run loop) on the `SecureInputField` path.
|
|
207
|
+
2. Fixed the floating-label vertical offset from a hardcoded `-8` to `-(size == .small ? 48 : 56) / 2`,
|
|
208
|
+
matching Kotlin's `-size.values.height / 2` (24pt small / 28pt large instead of always 8pt).
|
|
209
|
+
3. Fixed the `maxLength` trim race: the text binding's setter now trims atomically before publishing
|
|
210
|
+
(`let limited = limitText(newValue); self.text = limited`) instead of setting the raw value and
|
|
211
|
+
correcting it a render later via a separate `.onChange` — removed that now-redundant correction block.
|
|
212
|
+
4. Fixed `ErrorView`'s hardcoded `Colors.red03`/`Colors.black12` to use `theme.colors.error.primary`/
|
|
213
|
+
`theme.colors.text.hint` (added `@Environment(\.appTheme)`).
|
|
214
|
+
5. **Investigated, not changed**: the "bold styling swaps the whole typography token" finding turned out
|
|
215
|
+
to be a non-issue in practice — `TypographyStyle.bodyDefaultRegular` and `.actionSBold` share the same
|
|
216
|
+
14pt font size and 20pt line height (differing only in weight), and `Input`'s `TextField`/`SecureField`
|
|
217
|
+
never applies `.lineSpacing()` at all, so the "different token" is functionally just a weight toggle
|
|
218
|
+
either way. Kotlin's own local text style (14sp/24sp, independent of the shared `Typography` object)
|
|
219
|
+
does differ in line-height from Swift's shared tokens (20pt), but that value has no visible effect on
|
|
220
|
+
a single-line field without explicit line-spacing — not touched.
|
|
221
|
+
6. **Flagged, not changed**: the dual-control-type architecture for password visibility toggling
|
|
222
|
+
(`SecureInputField` UIViewRepresentable ↔ SwiftUI `TextField`, swapped based on `isPasswordHidden`)
|
|
223
|
+
carries a real risk of focus/cursor loss on toggle, vs. Kotlin's single `BasicTextField` with a
|
|
224
|
+
`VisualTransformation` swap. Fixing this properly means unifying both modes behind one control
|
|
225
|
+
(e.g. a single `UIViewRepresentable` toggling `isSecureTextEntry` in place) — a larger, riskier
|
|
226
|
+
rewrite than this pass's other fixes; documented rather than attempted.
|
|
227
|
+
7. **Flagged, not changed**: `icon`/`iconColor` (Kotlin) vs `rightIcon`/`rightIconColor` (Swift) naming
|
|
228
|
+
— renaming the existing Swift params would break current callers for a cosmetic parity gain only.
|
|
229
|
+
- Verified via `MoMoUIKits` scheme build and full `iosApp` scheme build — both `** BUILD SUCCEEDED **`.
|
|
230
|
+
|
|
231
|
+
### Batch 3 — Selection controls (CheckBox, Radio, Switch, Slider, Rating, Stepper, Steps, Chip)
|
|
232
|
+
|
|
233
|
+
- **Cross-cutting press-feedback gap — fixed.** Kotlin's `activeOpacityClickable` (fades the tappable
|
|
234
|
+
subtree to a target opacity while pressed) had no shared Swift equivalent; a private
|
|
235
|
+
`ActiveOpacityButtonStyle` existed only inside `Uploader.swift` (unshared). Extracted it to
|
|
236
|
+
`ios/Extensions/ActiveOpacityButtonStyle.swift` as a parameterized `ButtonStyle(activeOpacity:)`
|
|
237
|
+
(default 0.2, matching Kotlin's default), updated `Uploader.swift`'s 3 call sites to use the shared
|
|
238
|
+
version with its existing `0.5` value preserved (not a Batch 3 component; left its opacity value
|
|
239
|
+
untouched — that's Batch 6's scope). Wired the shared style into: `Checkbox` (0.8, matching Kotlin's
|
|
240
|
+
explicit override), `Radio`, `Chip`, `Stepper`'s +/- buttons, `Rating`'s star buttons (replacing
|
|
241
|
+
`.buttonStyle(.plain)`), and `Steps`'s two clickable-column variants (`column`/`row`, replacing
|
|
242
|
+
`.onTapGesture` with a conditional `Button` wrap so non-clickable steps still render with zero press
|
|
243
|
+
interaction, matching Kotlin's `if (clickable) activeOpacityClickable(...)`).
|
|
244
|
+
- **Checkbox**: was using SF Symbols (`"checkmark"`/`"minus"`, 12×12) instead of the shared `Icon`
|
|
245
|
+
component with the design-system icon set (`ic_checked`/`navigation_minus`, 20×20, matching Kotlin
|
|
246
|
+
exactly). **Fixed** — swapped to `Icon(source:size:color:)`, and converted the tap target from
|
|
247
|
+
`.onTapGesture` to a `Button` + the shared `ActiveOpacityButtonStyle`.
|
|
248
|
+
- **Switch — most significant fix in this batch.** Four real bugs, all internal/no signature change:
|
|
249
|
+
1. **On-state color was pink instead of green** (`Colors.pink03` used for both track and inner dot when
|
|
250
|
+
`value == true`; Kotlin uses `green_03`/`green_09`). **Fixed.**
|
|
251
|
+
2. **No disabled-specific palette** — Swift blanket-dimmed the whole control via `.opacity(0.4)`; Kotlin
|
|
252
|
+
uses distinct muted tokens (`green_09`/`black_05`) with no opacity overlay at all. **Fixed** — removed
|
|
253
|
+
the opacity dim, added a `(disabled, value)`-keyed color switch matching Kotlin's 4-way logic exactly.
|
|
254
|
+
3. **Track size was 40×24 instead of Kotlin's 38×20**, and the thumb inset was a hardcoded `5` instead of
|
|
255
|
+
`Spacing.XS` (4). **Fixed** — both corrected to match.
|
|
256
|
+
4. **`title`/`accessibilityId` params were entirely missing.** Re-reading Kotlin's `Switch.kt` closely:
|
|
257
|
+
these are **not** rendered as a visible label at all — they only feed an automation/test identifier
|
|
258
|
+
string (`"toggle_${title}_$value"`). Ported faithfully as two new optional params (default `""`,
|
|
259
|
+
non-breaking) building the same identifier string via `.accessibilityIdentifier(...)`, without adding
|
|
260
|
+
any visual label (there isn't one in the source of truth either).
|
|
261
|
+
Also fixed: the outer 14pt thumb-circle color didn't toggle with `value` at all (hardcoded `Colors.black01`
|
|
262
|
+
always) — now correctly switches `black01`/`black03` per Kotlin's `circleBackgroundColor`.
|
|
263
|
+
- **Radio, Slider, Rating, Steps**: faithful ports beyond the shared press-feedback gap above — no other
|
|
264
|
+
findings.
|
|
265
|
+
- **Stepper**: Kotlin's `Stepper` is uncontrolled (seeded once from `defaultValue`); Swift's `MomoStepper`
|
|
266
|
+
is a controlled `Binding<Int>` component. **Flagged only** — a deliberate-looking SwiftUI idiom, but a
|
|
267
|
+
genuine contract difference for anyone porting usage code 1:1; not changed since it'd be a breaking
|
|
268
|
+
redesign of the current, presumably-intentional Swift API.
|
|
269
|
+
- **Chip**: no `scaleSize(...)` applied to any dimension (height/horizontal padding/icon size/icon
|
|
270
|
+
spacing/corner radius), unlike every other component in this pass — Dynamic Type scaling silently didn't
|
|
271
|
+
apply to Chips. **Fixed** — wrapped all five dimensions in `scaleSize(...)` at the point of use (the
|
|
272
|
+
static `Dimensions` structs themselves stay unscaled, matching how Kotlin's raw dp constants are only
|
|
273
|
+
scaled at composition time). Converted `.onTapGesture` to `Button` + `ActiveOpacityButtonStyle` in the
|
|
274
|
+
same pass since Chip was also missing the press-feedback fix.
|
|
275
|
+
- Verified via `MoMoUIKits` scheme build and full `iosApp` scheme build — both `** BUILD SUCCEEDED **`.
|
|
276
|
+
|
|
277
|
+
### Batch 4 — Text-entry family (InputDropDown, InputMoney, InputOTP, InputPhoneNumber, InputSearch, InputTextArea)
|
|
278
|
+
|
|
279
|
+
- **InputDropDown, InputMoney**: both had the same bug — `RenderRightIcon` used `if loading { ... } else if
|
|
280
|
+
!icon.isEmpty { ... }`, suppressing the icon whenever `loading == true`; Kotlin's `RenderRightIcon` is two
|
|
281
|
+
independent `if` blocks (both can render at once). **Fixed** in both files.
|
|
282
|
+
- **InputMoney**: also had the exact "floating-label offset doesn't scale with size" bug just fixed in base
|
|
283
|
+
`Input.swift` — hardcoded `-8` instead of `-(size == .small ? 48 : 56) / 2`. **Fixed.** (Its extra
|
|
284
|
+
`.fill(theme.colors.background.surface)` on the input box, absent in Kotlin, was left alone — it's a
|
|
285
|
+
proper theme-token addition consistent with how base `Input.swift`'s own input box is styled, not a bug.)
|
|
286
|
+
- **InputOTP**: Kotlin's free-form (`length == null`) branch references a stray top-level constant
|
|
287
|
+
(`MAX_LENGTH = 300` from `InputTextArea.kt`) instead of its own computed `maxLength` (`length ?: 10`) —
|
|
288
|
+
a genuine bug in the Kotlin source itself, meaning caret/spacer logic there never triggers in practice.
|
|
289
|
+
Swift's `InputOTP.swift` correctly uses its own 10-char `maxLength` and does **not** reproduce this bug.
|
|
290
|
+
**Flagged, not changed** — "fixing" Swift to match would mean deliberately re-introducing broken
|
|
291
|
+
behavior; this needs a Kotlin-side/product decision, not a mechanical Swift edit.
|
|
292
|
+
- **InputPhoneNumber**: two dead public params, both **fixed**.
|
|
293
|
+
1. `required` was accepted by the initializer but never even stored as a property — completely discarded.
|
|
294
|
+
Added the missing `public var required: Bool` and wired it to append `" *"` to the placeholder when
|
|
295
|
+
set (mirroring how base `Input.swift` renders its own `required` marker; Kotlin's
|
|
296
|
+
`InputPhoneNumber.kt` has no `required` param at all, so there's no source-of-truth rendering to
|
|
297
|
+
match exactly — this follows the sibling component's convention instead).
|
|
298
|
+
2. `autofocus` was stored but never wired to anything. Fixed via `@FocusState`, same pattern as base
|
|
299
|
+
`Input.swift`.
|
|
300
|
+
Also investigated the flagged divider-color "mismatch" (Kotlin hardcodes `Colors.black_04`; Swift uses
|
|
301
|
+
`theme.colors.border.default`): confirmed `defaultTheme.border.default = Colors.black_04` in Kotlin's own
|
|
302
|
+
`Theme.kt`, so under the default theme these are pixel-identical — Swift's theme-token approach is
|
|
303
|
+
actually more correct (Kotlin's hardcoded literal is the one that silently breaks under a non-default
|
|
304
|
+
theme). No change needed; not a real divergence today.
|
|
305
|
+
- **InputSearch**: Kotlin's `InputSearchProps` exposes `backgroundColor`, `clearCondition`, `onClearPress`,
|
|
306
|
+
and `leftPosition` with no Swift equivalents — a real but substantial API-surface gap. **Flagged, not
|
|
307
|
+
added** (larger scope than a mechanical fix). Did fix the narrower, clearly-broken piece: the clear
|
|
308
|
+
button previously fired **no callback at all** on tap (just `text = ""`); now also calls `onChangeText("")`
|
|
309
|
+
so clearing isn't a silent no-op, even without porting the dedicated `onClearPress`/`clearCondition` API.
|
|
310
|
+
- **InputTextArea**: three findings, all **fixed**.
|
|
311
|
+
1. Missing clear ("x") button entirely (Kotlin shows one when focused + non-empty).
|
|
312
|
+
2. `icon`/`iconColor`/`loading`/`onRightIconPressed` were declared, stored, and never referenced anywhere
|
|
313
|
+
in `body` — completely dead public props.
|
|
314
|
+
3. Floating-label offset used `-height / 10` instead of `-height / 2` — the same offset-scaling bug
|
|
315
|
+
class as base `Input.swift`, but with an outright wrong divisor rather than a fixed constant.
|
|
316
|
+
Restructured the text-field row into an `HStack` (text field + clear button + loading/right-icon, mirroring
|
|
317
|
+
Kotlin's `Row` layout) to fit the new controls in.
|
|
318
|
+
- Verified via `MoMoUIKits` scheme build and full `iosApp` scheme build — both `** BUILD SUCCEEDED **`.
|
|
319
|
+
|
|
320
|
+
### Batch 5 — Scroll/list/pagination (Carousel, Pagination family, LazyColumnWithBouncing, CupertinoOverscroll, ScaleSizeScope, TabView)
|
|
321
|
+
|
|
322
|
+
- **PaginationDot, PaginationNumber, PaginationWhiteDot, PaginationScroll**: faithful parity, no
|
|
323
|
+
significant findings (PaginationScroll's missing outer-layout-customization param was flagged but is
|
|
324
|
+
low value with no direct SwiftUI equivalent to Compose's `Modifier` — not added).
|
|
325
|
+
- **ScaleSizeScope**: confirmed no port needed — the Kotlin source itself is `@Deprecated`, a no-op
|
|
326
|
+
passthrough; `FontScaleStore`/`scaleSize` in `ios/Typography/` already provide the real functionality.
|
|
327
|
+
- **Carousel**: real perf bug, **fixed**. `HStack(ForEach(0..<virtualPageCount))` instantiated every
|
|
328
|
+
virtual page eagerly — with `loop=true`, `virtualPageCount = itemCount * 1000`, so thousands of slide
|
|
329
|
+
views were built up front (Compose's `HorizontalPager` is lazy). Fixed by windowing the rendered range
|
|
330
|
+
to `currentPage ± 3` and padding the HStack with `Color.clear` spacers sized to the skipped width, so
|
|
331
|
+
the existing offset math (`baseOffset = -currentPage * step + ...`) is completely unchanged — only the
|
|
332
|
+
render cost drops. Deferred, documented (not fixed): autoplay doesn't restart its interval when the user
|
|
333
|
+
stops scrolling (Kotlin's `LaunchedEffect(autoplayInterval, isUserScrolling)` does); Swift's extra
|
|
334
|
+
`enableSnap` param and symmetric-only `contentPadding` (vs Kotlin's asymmetric `PaddingValues`) are
|
|
335
|
+
flagged API differences, not touched.
|
|
336
|
+
- **TabView**: press feedback was plain `.onTapGesture` in both `CardTabBar` and `TabItemView` — Kotlin
|
|
337
|
+
uses `activeOpacityClickable` on both. **Fixed** — wired the shared `ActiveOpacityButtonStyle` (same
|
|
338
|
+
fix applied to CheckBox/Radio/Chip/Stepper/Rating/Steps in Batch 3) into both tap targets. Deferred,
|
|
339
|
+
documented: `userScrollEnabled: false` still animates a real page transition in Kotlin (only the swipe
|
|
340
|
+
gesture is disabled) but instant-swaps content with no transition in Swift — flagged, not changed since
|
|
341
|
+
it'd alter current output for existing callers of that flag. Also deferred: Kotlin lazily composes only
|
|
342
|
+
visited tab pages; Swift's `ForEach` builds `AnyView` content for every tab up front — a real perf gap,
|
|
343
|
+
same class as Carousel's, but not fixed this pass given the remaining batch scope.
|
|
344
|
+
- **LazyColumnWithBouncing, CupertinoOverscroll**: confirmed **zero Swift port exists** for either — the
|
|
345
|
+
custom Cupertino rubber-band overscroll container and the bespoke segmented-ring pull-to-refresh spinner
|
|
346
|
+
(with haptic-at-threshold behavior) have no iOS equivalent anywhere in `ios/`. Native `UIScrollView`/
|
|
347
|
+
`.refreshable` covers baseline bounce/refresh but not the distinctive spinner visual or haptics. This is
|
|
348
|
+
a real, substantial feature gap — flagged for a dedicated follow-up rather than attempted as part of this
|
|
349
|
+
batch (building a custom pull-to-refresh spinner + haptic sequencing is new-component-scale work, not a
|
|
350
|
+
parity fix).
|
|
351
|
+
- Verified via `MoMoUIKits` scheme build and full `iosApp` scheme build — both `** BUILD SUCCEEDED **`.
|
|
352
|
+
|
|
353
|
+
### Batch 6 — Overlay & misc interaction (PopupPromotion, Collapse, Uploader, SuggestAction, Swipe)
|
|
354
|
+
|
|
355
|
+
- **Uploader**: its 3 `ActiveOpacityButtonStyle` call sites (deduplicated to the shared style in Batch 3)
|
|
356
|
+
hardcoded `activeOpacity: 0.5`, a value that was carried over from the old private duplicate without
|
|
357
|
+
ever being checked against source. Confirmed Kotlin's 3 call sites all omit `activeOpacity`, using the
|
|
358
|
+
modifier's own default (0.2). **Fixed** — dropped the explicit `0.5` at all 3 sites to use the shared
|
|
359
|
+
style's default.
|
|
360
|
+
- **Collapse**: three findings, all **fixed**.
|
|
361
|
+
1. Header row used `.buttonStyle(.plain)` instead of the shared `ActiveOpacityButtonStyle` (Kotlin uses
|
|
362
|
+
`activeOpacityClickable`) — swapped.
|
|
363
|
+
2. Missing `accessibilityId` entirely (Kotlin exposes it plus a 6-identifier hierarchy: `|touch`, `|img`,
|
|
364
|
+
`|text`, `|description-text`, `|sub-title-text`, `|icon`). Added the param (additive) and wired the
|
|
365
|
+
two most functionally significant identifiers — the outer container (`automationId`) and the header
|
|
366
|
+
touch target (`|touch`). **Deferred, documented**: the remaining 4 sub-element identifiers
|
|
367
|
+
(`|img`/`|text`/`|description-text`/`|sub-title-text`/`|icon`) were not wired individually — lower
|
|
368
|
+
marginal value for meaningfully more call-site plumbing across the header's sub-views.
|
|
369
|
+
3. **Deferred, documented** (not fixed): expand/collapse uses `.transition(.move + .opacity)` (slide+fade)
|
|
370
|
+
instead of Kotlin's true `expandVertically`/`shrinkVertically` height animation — same 0.3s duration,
|
|
371
|
+
different visual feel. Fixing this properly needs `GeometryReader`-based height measurement; left as
|
|
372
|
+
a documented gap rather than attempted this pass.
|
|
373
|
+
- **PopupPromotion**: three findings, all **fixed**.
|
|
374
|
+
1. Image content scale used `.aspectRatio(0.72, contentMode: .fit)` (letterboxes) instead of matching
|
|
375
|
+
Kotlin's `Modifier.fillMaxWidth().aspectRatio(0.72f)` + `ContentScale.FillBounds` (a fixed-ratio box
|
|
376
|
+
the image stretches to exactly fill, ignoring its own aspect ratio). Fixed by giving the image an
|
|
377
|
+
explicit `.frame(width:height:)` with no `.aspectRatio` modifier, which is SwiftUI's equivalent of
|
|
378
|
+
"stretch to fill" for a `.resizable()` image.
|
|
379
|
+
2. Close-button circle was hardcoded `Colors.black01`/`Colors.black17`; Kotlin uses
|
|
380
|
+
`theme.colors.background.surface` (border + icon) / `theme.colors.text.default` (fill). Fixed —
|
|
381
|
+
added `@Environment(\.appTheme)` and swapped all three color usages.
|
|
382
|
+
3. Kotlin explicitly disables system press-highlight (`indication = null`) on the close button; Swift had
|
|
383
|
+
no `.buttonStyle`, showing the default system highlight. Fixed with `.buttonStyle(.plain)`.
|
|
384
|
+
Deferred, documented: Kotlin renders nothing for an empty `source` string; Swift shows a full-width
|
|
385
|
+
`media_fail` icon — a real behavior difference, flagged rather than changed since it'd alter output for
|
|
386
|
+
existing empty-string callers.
|
|
387
|
+
- **SuggestAction**: close icon used plain `.onTapGesture` (no press feedback at all); Kotlin uses
|
|
388
|
+
`activeOpacityClickable`. **Fixed** — wrapped in a `Button` + the shared `ActiveOpacityButtonStyle`.
|
|
389
|
+
- **Swipe**: `SwipeCell.swift`'s `Swipe` view never read `@Environment(\.appTheme)` at all, hardcoding
|
|
390
|
+
`Colors.primary`/`Colors.error`/`Colors.card` for the left/right action defaults and content background;
|
|
391
|
+
Kotlin uses `theme.colors.primary`/`theme.colors.error.primary`/`theme.colors.background.surface`.
|
|
392
|
+
**Fixed** — added theme access and swapped all three. Deferred, documented (not changed): a
|
|
393
|
+
`minimumDistance: 8` drag-gesture threshold that Kotlin doesn't have (changes real-time drag feel for
|
|
394
|
+
existing callers — Flag); and a large chunk of unused public API (`SwipeCellModel.swift` entirely, plus
|
|
395
|
+
`SwipeCellModifier`/`.swipeCell(...)` in `SwipeCell.swift`) with zero callers anywhere in the repo and no
|
|
396
|
+
Kotlin counterpart — vestigial, unrelated to `Swipe.kt` parity. Left in place since removing public API
|
|
397
|
+
during a parity pass risks breaking unknown external consumers; flagged for a separate removal decision.
|
|
398
|
+
- Verified via `MoMoUIKits` scheme build and full `iosApp` scheme build — both `** BUILD SUCCEEDED **`.
|
|
399
|
+
|
|
400
|
+
### Batch 7 — Localize-dependent (PopupDisplay, PopupNotify, TrustBanner)
|
|
401
|
+
|
|
402
|
+
Deferred from Tier 2 until the `Localize` subsystem landed (it now has, see Tier 2 above). All three
|
|
403
|
+
previously read language via an ad-hoc mechanism instead of any shared abstraction.
|
|
404
|
+
|
|
405
|
+
- **PopupDisplay.swift**: read `UserDefaults.standard.string(forKey: "language")` directly and looked up
|
|
406
|
+
an inline `["vi": "Mã lỗi: ", "en": "Error code: "]` dict — values that happen to be character-for-character
|
|
407
|
+
identical to `Localize`'s own `"errorCode"` dictionary entry. **Fixed** — added
|
|
408
|
+
`@EnvironmentObject private var localize: Localize` and replaced both with `localize.translate("errorCode")`.
|
|
409
|
+
- **PopupNotify.swift**: same pattern (`UserDefaults` read + inline `en`/`vi` ternary for the same error-code
|
|
410
|
+
prefix). **Fixed** — identical replacement with `localize.translate("errorCode")`.
|
|
411
|
+
- **TrustBanner.swift**: **not actually reading `UserDefaults` at all** — closer inspection found
|
|
412
|
+
`var language: String? = "vi"` hardcoded and never reassigned anywhere, meaning the banner's content/
|
|
413
|
+
learn-more text was **permanently stuck in Vietnamese regardless of device or app language** — a real,
|
|
414
|
+
live bug, not just an architecture smell. **Fixed** — removed the dead hardcoded property, added
|
|
415
|
+
`@EnvironmentObject private var localize: Localize`, and switched both dictionary lookups
|
|
416
|
+
(`content`/`subContent`, which are already `[String: String]` keyed by language code, matching
|
|
417
|
+
`Localize`'s own dictionary shape) to key off `localize.currentLanguage`.
|
|
418
|
+
- Verified via `MoMoUIKits` scheme build and full `iosApp` scheme build — both `** BUILD SUCCEEDED **`.
|
|
419
|
+
|
|
420
|
+
### Batch 8 — DateTimePicker subsystem (DateTimePicker, DateTimePickerTypes, DateTimePickerUtils, WheelPicker) — final batch
|
|
421
|
+
|
|
422
|
+
- **DateTimePicker.swift, WheelPicker.swift**: both bypassed the theme system entirely — neither file read
|
|
423
|
+
`@Environment(\.appTheme)` at all, using static asset constants (`Colors.background`, `Colors.border`,
|
|
424
|
+
`Colors.text`) instead of `theme.colors.background.surface`/`theme.colors.border.default`/
|
|
425
|
+
`theme.colors.text.default`. Every other component in this pass correctly reads the theme; these two
|
|
426
|
+
were outliers. **Fixed** — added `@Environment(\.appTheme)` to `DateTimePicker`, `WheelPicker`, and the
|
|
427
|
+
private `WheelPickerItem`, and swapped all 5 hardcoded color usages across the two files. (Note:
|
|
428
|
+
`WheelPicker.swift`'s `Colors.blue10` background accent was confirmed intentional — matches Kotlin's own
|
|
429
|
+
hardcoded `Colors.blue_10`, not a theme bypass.)
|
|
430
|
+
- **DateTimePickerUtils.swift**: `getMinutes`'s interval guard (`max(1, interval)`) already correctly
|
|
431
|
+
avoids a Kotlin-source infinite-loop bug (Kotlin has no such guard) — confirmed as a case where Swift is
|
|
432
|
+
already better, not a gap. **Investigated, not changed**: `getDaysInMonth`/`getMonths`/`getYears` return
|
|
433
|
+
a single-element fallback array when given an inverted min/max range, where Kotlin's `for` loop would
|
|
434
|
+
produce an empty list. Judged this an intentional, reasonable defensive choice rather than a bug to
|
|
435
|
+
"fix" — an empty wheel-picker column (no scrollable values at all) is arguably a worse failure mode than
|
|
436
|
+
a one-item fallback, so matching Kotlin's literal empty-list behavior here would make the edge case
|
|
437
|
+
worse, not better. Left as is (same reasoning pattern as the Skeleton shimmer-formula judgment call in
|
|
438
|
+
Batch 2).
|
|
439
|
+
- **WheelPicker.swift**: **deferred, documented** (not fixed) — the wheel currently reports
|
|
440
|
+
`onChange(name, value)` continuously on every scroll-position update during an active drag, and never
|
|
441
|
+
animates a snap-to-center after release; Kotlin only fires `onChange` once scrolling settles
|
|
442
|
+
(`!isScrollInProgress`) and then animates the list to precisely center the selected row. Confirmed
|
|
443
|
+
real and would need restructuring the scroll-tracking to distinguish "dragging" from "settled" plus a
|
|
444
|
+
genuine snap-to-center animation — a materially bigger rewrite than this pass's other fixes, flagged for
|
|
445
|
+
a dedicated follow-up rather than attempted here.
|
|
446
|
+
- **DateTimePickerTypes.swift**: clean 1:1 port, no findings. No hardcoded locale strings found in this
|
|
447
|
+
subsystem needing a `Localize` hookup — the wheel columns render zero-padded numbers only, no month/
|
|
448
|
+
day names.
|
|
449
|
+
- Verified via `MoMoUIKits` scheme build and full `iosApp` scheme build — both `** BUILD SUCCEEDED **`.
|
|
450
|
+
|
|
451
|
+
**Post-pass fix — BottomSheet: keyboard avoidance, scrim alpha correction, drag-performance refactor**:
|
|
452
|
+
three issues reported by the user, all in `ios/Application/Navigation/Overplay/BottomSheet.swift`.
|
|
453
|
+
1. **Keyboard avoidance (real gap)**: `BottomSheetView` is rendered via `NavigationContainer`'s own
|
|
454
|
+
`.overlay { OverplayHost(...) }`, structurally outside any pushed `StackScreen`, so it never inherited
|
|
455
|
+
`StackScreen`'s existing keyboard-avoidance (`ios/Application/StackScreen.swift:102,195-201`). Fixed by
|
|
456
|
+
adding the same `keyboardWillChangeFrameNotification`-based tracking directly to `BottomSheetView`, and
|
|
457
|
+
replacing (not adding to) the bottom-inset filler with the keyboard height while it's up.
|
|
458
|
+
2. **Scrim alpha — my own research was wrong at first, caught by the user.** Initially reported "BottomSheet
|
|
459
|
+
(peak 0.3) intentionally differs from Modal (peak 0.6), not a bug" — but Kotlin's `BottomSheet.kt:67-69`
|
|
460
|
+
is **additive** (`backgroundAlpha + dynamicAlpha`, each terming out at 0.3), so its true steady-state peak
|
|
461
|
+
is 0.3+0.3=**0.6**, exactly matching `ModalScreen.kt`. The user directly re-checked the Kotlin source and
|
|
462
|
+
caught this before I did. Swift's `BottomSheet.swift` only implemented the single `dynamicAlpha` term
|
|
463
|
+
(peaking at 0.3) — genuinely under-dimmed by half. **Fixed**: split into two terms
|
|
464
|
+
(`backgroundAlpha` state, only touched by open()/close(); `dynamicAlpha` computed property tracking
|
|
465
|
+
drag position) matching Kotlin's model exactly — scrim now peaks at 0.6 at rest and only fades to a 0.3
|
|
466
|
+
floor while dragging (never fully transparent until the sheet has completely left the screen). Verified
|
|
467
|
+
by pixel-sampling a screenshot: `(102,102,102)` = black-at-60%-opacity over white, exact match.
|
|
468
|
+
3. **Drag jank**: the raw per-touch update mechanism already matched Kotlin's unanimated `Animatable.snapTo`
|
|
469
|
+
1:1 — not an animation-vs-no-animation gap. Root cause: two independent `@State` writes per touch-move
|
|
470
|
+
(`sheetOffset`, `scrimAlpha`) each forced the whole `body` (including `content()` and a `GeometryReader`)
|
|
471
|
+
to reconstruct at touch-move frequency. **Fixed** by refactoring to `@GestureState` for the live drag
|
|
472
|
+
value (isolated from `content()`'s reconstruction), combined with `sheetOffset` only for the settled
|
|
473
|
+
position — matching an idiom already established elsewhere in this codebase
|
|
474
|
+
(`ios/PopupView/PopupView.swift`'s `@GestureState dragState` + `lastDragPosition` capture-before-reset
|
|
475
|
+
pattern). **Honest limitation**: smoothness itself isn't screenshot-verifiable — confirmed clean compile
|
|
476
|
+
and correct rest/open/closed positions, but true "no more jank" confirmation needs the user's own
|
|
477
|
+
hands-on feel on a simulator/device, which I said explicitly rather than overclaim.
|
|
478
|
+
Verified via `MoMoUIKits` + full `iosApp` scheme builds, plus a temporary root-view-swap reproduction
|
|
479
|
+
(`BottomSheetView` wrapping an autofocused `Input`) — screenshotted keyboard-up state showing the sheet
|
|
480
|
+
correctly staying above the keyboard. Reverted the temporary harness cleanly (confirmed via `git diff`,
|
|
481
|
+
including restoring an accidental trailing-whitespace diff introduced by the revert itself).
|
|
482
|
+
|
|
483
|
+
**Follow-up — drag still jerked twice before settling.** The `@GestureState` refactor above didn't fully
|
|
484
|
+
fix the jank: the user reported a specific pattern (touch, drag, hold → 2 visible jerks → settles at the
|
|
485
|
+
correct finger position). No touch-injection tooling is available in this environment (no `idb`, no
|
|
486
|
+
XCUITest harness), so this couldn't be reproduced/observed directly — diagnosed by code inspection only.
|
|
487
|
+
Found two plausible races that both fit the exact symptom shape:
|
|
488
|
+
1. `open()` animates `sheetOffset` 0-target over 350ms; if a drag starts before that settles, the live
|
|
489
|
+
`dragTranslation` value gets added on top of a `sheetOffset` that's *also* mid-animation — two
|
|
490
|
+
competing sources of motion for the same frame.
|
|
491
|
+
2. The keyboard-driven `effectiveBottomInset` swap (issue 1 fix) animates the bottom filler's height,
|
|
492
|
+
which changes the sheet's total height; since the sheet is bottom-anchored, that shifts its *top* edge
|
|
493
|
+
— exactly where the drag handle sits — via layout, independent of the offset-driven motion from the
|
|
494
|
+
drag itself.
|
|
495
|
+
**Fixed** both: added `.onChanged` to the drag gesture that snaps `sheetOffset` to `0` (no animation) the
|
|
496
|
+
instant a drag begins, settling any in-flight open/keyboard-resize animation immediately rather than
|
|
497
|
+
having the live drag fight a moving target. Also (at the time) added `.onChange(of: geo.size.height)` to
|
|
498
|
+
the `sheetHeight`-measuring `GeometryReader`, reasoning that its `.onAppear`-only measurement would go
|
|
499
|
+
stale after the keyboard-driven resize changed the sheet's actual height.
|
|
500
|
+
|
|
501
|
+
**Follow-up #2 — that `.onChange` addition was itself a regression.** User reported a *continuous* up/down
|
|
502
|
+
jitter for the whole duration of a drag (not just a settle-in jerk at the start) — a materially different,
|
|
503
|
+
more severe symptom. Prime suspect: `sheet` is a computed property re-evaluated on every touch-move (since
|
|
504
|
+
`dragTranslation` changing forces `body` to re-render), which reconstructs the `GeometryReader` each time;
|
|
505
|
+
the newly-added `.onChange(of: geo.size.height)` closure writing to `sheetHeight` on every such
|
|
506
|
+
reconstruction is a plausible feedback-loop shape (state write → re-render → reconstruct → possible
|
|
507
|
+
re-fire → state write again) that fits a *continuous* oscillation far better than the one-time settle-in
|
|
508
|
+
case it was meant to fix. No `.animation()` (scoped or unscoped) exists anywhere in this file or
|
|
509
|
+
`NavigationContainer.swift`, ruling out the other classic cause of "gesture updates chase an animated
|
|
510
|
+
target." **Reverted** the `.onChange(of: geo.size.height)` addition back to `.onAppear`-only (accepting
|
|
511
|
+
the minor, lower-severity `sheetHeight`-staleness-after-keyboard-resize issue rather than risk
|
|
512
|
+
reintroducing a continuous jitter); kept the `.onChanged` drag-start snap fix, which is much lower-risk
|
|
513
|
+
(fires once per drag, a no-op on every subsequent call). Verified clean compile on both schemes only —
|
|
514
|
+
**still could not verify at runtime** whether the continuous jitter is actually resolved, since no
|
|
515
|
+
touch-injection tooling (no `idb`, no XCUITest harness) is available in this environment. Told the user
|
|
516
|
+
plainly rather than claim a fix that can't be felt from here, and asked for confirmation plus, if it
|
|
517
|
+
persists, more precise repro details (does `content()`'s makeup matter — e.g. an `Input` field present or
|
|
518
|
+
not — and does it happen from the very first touch-move or only after some movement).
|
|
519
|
+
|
|
520
|
+
**Follow-up #3 — confirmed still jittery even with plain-text content**, ruling out anything specific to
|
|
521
|
+
`Input`'s own internal measurement. Two more attempts, one of which caused a worse regression:
|
|
522
|
+
- **Tried and reverted**: `.drawingGroup()` on `sheet`, reasoning that `RoundedCorner`'s clip path (built
|
|
523
|
+
via a `UIBezierPath` bridge call) plus the rest of `sheet`'s layout was being rebuilt on every touch-move
|
|
524
|
+
(since `sheet` is a computed property re-evaluated whenever `dragTranslation` changes) regardless of
|
|
525
|
+
`content()`'s complexity — consistent with the plain-text repro. **This introduced a worse, more visible
|
|
526
|
+
regression**: the header's close icon rendered as a blank gray box instead of the "X" glyph, screenshotted
|
|
527
|
+
and confirmed before reverting. Likely cause: the icon is loaded via an async image library
|
|
528
|
+
(SDWebImageSwiftUI-backed `ImageView`) that doesn't composite correctly once flattened into a
|
|
529
|
+
`drawingGroup`'s rasterized Metal-backed layer. Reverted immediately; re-screenshotted to confirm the
|
|
530
|
+
icon renders correctly again post-revert.
|
|
531
|
+
- **Applied, not yet confirmed**: changed `DragGesture()` to `DragGesture(coordinateSpace: .global)`.
|
|
532
|
+
Reasoning: the gesture is attached to the drag handle, which is itself repositioned every frame by this
|
|
533
|
+
same gesture's own output (`sheet.offset(y: effectiveOffset)`, computed from `dragTranslation`) — the
|
|
534
|
+
default `.local` coordinate space ties gesture tracking to that same (continuously moving) view, a
|
|
535
|
+
known category of SwiftUI feedback-jitter source. `.global` anchors tracking to the window instead,
|
|
536
|
+
decoupling it from the view's own motion. Verified clean compile + no visual regression via screenshot
|
|
537
|
+
(icon renders fine, layout unchanged) — **have not been able to verify whether the continuous jitter
|
|
538
|
+
itself is resolved**, same tooling limitation as before (no touch-injection available in this
|
|
539
|
+
environment). Asked the user to retest.
|
|
540
|
+
|
|
541
|
+
**Confirmed fixed** — user retested and reported the jitter is gone. Root cause: `DragGesture()`'s default
|
|
542
|
+
`.local` coordinate space tied gesture tracking to the drag handle's own view, which was itself being
|
|
543
|
+
repositioned every frame by that same gesture's output — a feedback loop between "where the view is now"
|
|
544
|
+
and "where the gesture thinks the touch is relative to that view." Switching to
|
|
545
|
+
`.global` (anchoring tracking to the window instead of the moving view) resolved it. This is the actual
|
|
546
|
+
final state of the drag-jank issue from this whole BottomSheet follow-up thread; the `@GestureState`
|
|
547
|
+
refactor, the drag-start `sheetOffset` snap, and this coordinate-space fix together are what shipped —
|
|
548
|
+
`.onChange(of: geo.size.height)` and `.drawingGroup()` were both tried and reverted along the way (see
|
|
549
|
+
above) as they either regressed or didn't address the root cause.
|
|
550
|
+
|
|
551
|
+
## Pass summary
|
|
552
|
+
|
|
553
|
+
All 8 Tier-3 batches (Batches 1–8) plus Tiers 0–2 (tokens, navigation core, Localize/ScreenTracker
|
|
554
|
+
subsystems) and the original Button/IconButton/Input fixes are complete. Every fix in this log was
|
|
555
|
+
verified via a real `xcodebuild` compile of both the `MoMoUIKits` library scheme and the full `iosApp`
|
|
556
|
+
demo scheme (catching any public-signature breakage against real `Demo/*.swift` call sites), plus targeted
|
|
557
|
+
value-diffs for the token and Localize dictionary ports. Deferred items (documented above, not silently
|
|
558
|
+
dropped) worth a dedicated follow-up pass: Text's rich-text/inline-content API, Tooltip's popup-based
|
|
559
|
+
positioning architecture, Input's dual-control-type password-toggle, TabView/Carousel's remaining
|
|
560
|
+
lazy-render gaps, LazyColumnWithBouncing/CupertinoOverscroll (zero port), Collapse's true height-animation,
|
|
561
|
+
WheelPicker's settle-only snap behavior, and Navigation.kt's per-screen `setOptions()` runtime API.
|
|
562
|
+
|
|
563
|
+
**Post-pass addendum — Localize demo screen**: added `sample/iosApp/iosApp/Demo/LocalizeDemo.swift`,
|
|
564
|
+
a SwiftUI port of the Compose sample's `sample/shared/.../screens/LocalizeUsage.kt` demo (same section
|
|
565
|
+
structure: language switch, `translate(key)` on kit defaults, missing-key fallback, `{placeholder}`
|
|
566
|
+
substitution, `translateData(vi:en:)`, `translateData(map:)`, `addTranslations` with app-specific keys,
|
|
567
|
+
and a realistic error-code + Cancel/Confirm example). Registered in `Home.swift`'s `ScreenUsages` list.
|
|
568
|
+
Verified at runtime (not just compiled) by temporarily swapping `NativeContentView`'s root to the new
|
|
569
|
+
screen, screenshotting via the simulator, and reverting — confirmed via `git diff` that the revert left
|
|
570
|
+
zero trace. Found and fixed one cosmetic layout bug during that check (a long key name like
|
|
571
|
+
`voucherRemindHour` overlapped its value in a fixed-width `HStack` row; switched `TranslationRow` to a
|
|
572
|
+
`VStack` layout).
|
|
573
|
+
|
|
574
|
+
**Post-pass fix — BottomTab pushed up above the bottom safe-area inset**: reported by the user
|
|
575
|
+
("bottom tab đang bị đẩy lên cao hơn chiều cao của inset bottom"). Root cause traced to the
|
|
576
|
+
`Navigation.kt` gap already documented in Tier 1: Kotlin's `StackScreen.kt` skips its own bottom
|
|
577
|
+
navigation-bar padding when hosting a `BottomTab` (`MainContent(... isBottomTab = navigation.isBottomTabRoot)`,
|
|
578
|
+
`.padding(bottom = if (options.footerComponent != null || isBottomTab) 0.dp else navigationBar)`),
|
|
579
|
+
where `isBottomTabRoot` is set by `BottomTab.kt` itself calling `navigation.markAsBottomTabRoot()` on
|
|
580
|
+
mount. Swift's `StackScreen.swift` had no equivalent — its `contentBottomInset` only checked
|
|
581
|
+
`footer == nil`, so whenever `BottomTab` was hosted inside a pushed screen (as the "Bottom Tab" demo
|
|
582
|
+
entry is), the outer `StackScreen` added up to 21pt of extra bottom padding on top of `BottomTab`'s own
|
|
583
|
+
internal safe-area handling, squeezing it into less than the full screen height and leaving an
|
|
584
|
+
unreachable gap below the tab bar (`.ignoresSafeArea` can't undo a literal `.padding()` imposed by an
|
|
585
|
+
ancestor). **Fixed** without porting the full `Navigation` class: added a `PreferenceKey`
|
|
586
|
+
(`IsBottomTabRootKey` in `Components.swift`) that `BottomTab` sets via `.preference(...)` on appear and
|
|
587
|
+
`StackScreen` reads via `.onPreferenceChange(...)`, mirroring Kotlin's self-registering
|
|
588
|
+
`markAsBottomTabRoot()` mechanism (the closest existing precedent in this file is the
|
|
589
|
+
`HeaderRightWidthKey` preference already used for header-right measurement) rather than requiring every
|
|
590
|
+
call site to manually flag it. Verified by temporarily reproducing the exact push path from `Home.swift`
|
|
591
|
+
(`navigator.push({ BottomTabDemo() }, options: NavigationOptions(title: "Bottom Tab", headerType: .none,
|
|
592
|
+
scrollable: false))`) and screenshotting — tab bar now sits flush with the true bottom edge. Reverted the
|
|
593
|
+
temporary root-view change cleanly (confirmed via `git diff`).
|
|
594
|
+
|
|
595
|
+
**Post-pass fix — BottomSheet's bottom inset showed the backdrop color instead of the sheet's own
|
|
596
|
+
background**: reported by the user. `BottomSheetView.sheet` already had an explicit
|
|
597
|
+
`backgroundColor.frame(height: bottomInset)` filler row at the bottom of its `VStack` (matching the
|
|
598
|
+
sheet's own `theme.colors.background.surface`/`.default`), but nothing in the view made it reach the true
|
|
599
|
+
screen edge — so SwiftUI auto-reserved the real bottom safe-area padding *on top of* that filler, leaving
|
|
600
|
+
a gap below it where the dimmed scrim (rendered full-screen behind the sheet) showed through instead of
|
|
601
|
+
the sheet's background. Not a Kotlin parity gap — `BottomSheet.kt` has no equivalent inset handling at
|
|
602
|
+
all, confirming this filler was bespoke (and incomplete) iOS-only code.
|
|
603
|
+
**First attempt (did not work, verified by pixel-sampling the screenshot):** added
|
|
604
|
+
`.ignoresSafeArea(edges: .bottom)` to the nested `sheet` view specifically. Compiled fine and looked
|
|
605
|
+
plausible, but sampling actual pixel colors at the bottom of a screenshot showed a ~33pt strip of
|
|
606
|
+
`(178,178,178)` — exactly black-at-30%-opacity over white, i.e. the scrim alpha value — proving the gap
|
|
607
|
+
was untouched. `ignoresSafeArea` on a fixed-intrinsic-height child evidently doesn't reposition/resize it
|
|
608
|
+
the way it does for a flexible one.
|
|
609
|
+
**Actual fix:** moved `.ignoresSafeArea()` to wrap the *entire* outer `ZStack` in `body` (both scrim and
|
|
610
|
+
sheet together) instead of applying it separately per child, and removed the now-redundant modifier on
|
|
611
|
+
`sheet`. Re-verified the same way — pixel-sampled the new screenshot bottom strip: white
|
|
612
|
+
`(255,255,255)` all the way to the true edge, with only the OS-drawn home-indicator pill
|
|
613
|
+
(`(93,93,93)`→`(125,125,125)`, normal system chrome) visible on top, not a uniform scrim-gray band.
|
|
614
|
+
Reverted the temporary root-view-swap reproduction cleanly (confirmed via `git diff`) both times.
|
|
615
|
+
|
|
616
|
+
**Post-pass correction — floating-label offset fix from earlier in this pass was actually wrong**:
|
|
617
|
+
reported by the user ("floatingValue của các Input, bị lệch so với trước khi sửa"). Screenshotting
|
|
618
|
+
`InputDemo` showed the "Momo" floating label floating so high it overlapped completely unrelated content
|
|
619
|
+
(the "Secure Text Entry" section header above the input), confirming a real regression from the
|
|
620
|
+
`-(size == .small ? 48 : 56) / 2` change applied to `Input.swift`/`InputMoney.swift`/`InputTextArea.swift`
|
|
621
|
+
earlier in this pass (Batches 3/4 area).
|
|
622
|
+
**Root cause of the original mistake**: misread which layout anchor Kotlin's `offsetY = -size.values.height / 2`
|
|
623
|
+
is relative to. Kotlin's `BasicTextField` `decorationBox` centers its children (the `FloatingLabel` and the
|
|
624
|
+
input `Box`) vertically by default within the box-height-tall `BasicTextField` — so `-boxHeight/2` there
|
|
625
|
+
nets out to centering the label exactly on the box's top border (spans `-labelHeight/2 … +labelHeight/2`
|
|
626
|
+
around y=0). Swift's `ZStack(alignment: .topLeading)` anchors from the top instead, so applying the same
|
|
627
|
+
`-boxHeight/2` formula there is a completely different, much larger offset — hence the label flying
|
|
628
|
+
several times too far up. The original hardcoded `-8` (which I "fixed" away) was a reasonable
|
|
629
|
+
approximation of `-labelHeight/2`, not related to box height at all — reverting to a hardcoded constant
|
|
630
|
+
would have worked, but instead **measured the label's actual rendered height via a `GeometryReader`
|
|
631
|
+
background** (`floatingLabelHeight` state) and offset by `-floatingLabelHeight / 2` in all three files, so
|
|
632
|
+
it's correct regardless of font size/Dynamic Type scaling rather than relying on a magic constant tuned
|
|
633
|
+
for one specific rendered size. Verified by reproducing `InputDemo` via a temporary root-view swap,
|
|
634
|
+
screenshotting before/after, and confirming the label now sits correctly straddling each box's top border
|
|
635
|
+
with no overlap into unrelated content. Reverted the temporary change cleanly (confirmed via `git diff`).
|
|
636
|
+
|
|
637
|
+
**End-of-pass `pod lib lint` note**: `pod lib lint ios/native-kits.podspec --allow-warnings --quick` fails
|
|
638
|
+
with `ERROR | name: The name of the spec should match the name of the file` (spec name `MoMoUIKits` vs
|
|
639
|
+
filename `native-kits.podspec`). Confirmed pre-existing via `git diff` — neither podspec file was touched
|
|
640
|
+
anywhere in this pass, so this isn't a regression, just a packaging/naming convention issue outside a
|
|
641
|
+
Kotlin-vs-Swift parity pass's scope. Flagged here rather than silently ignored.
|