@momo-kits/native-kits 0.162.21 → 0.162.22-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/build.gradle.kts +11 -0
- package/compose/build.gradle.kts +190 -0
- package/compose/build.gradle.kts.backup +190 -0
- package/compose/compose.podspec +47 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +123 -0
- package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
- package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +52 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +106 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +183 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +207 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +39 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +294 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +826 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +110 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +378 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +82 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +356 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +113 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +76 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +112 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +477 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +214 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +240 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +216 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +259 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +182 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +328 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +97 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +310 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +323 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +217 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +199 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +91 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +470 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +88 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +131 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +203 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +498 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +175 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +234 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +188 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +22 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +188 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +276 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +253 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +129 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +100 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +172 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +360 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +581 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +156 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +225 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +320 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +81 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +309 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +34 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +360 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +132 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +128 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +70 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +58 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +105 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +32 -0
- 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 +184 -0
- package/gradle/libs.versions.toml +69 -0
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/gradle.properties +26 -0
- package/gradlew +252 -0
- package/gradlew.bat +94 -0
- package/ios/Application/ApplicationEnvironment.swift +4 -2
- package/ios/Application/MaxApi.swift +18 -0
- package/ios/Application/Navigation/NavigationContainer.swift +21 -3
- package/ios/Application/Navigation/Navigator.swift +50 -29
- package/ios/Application/Navigation/Overplay/ModalScreen.swift +6 -0
- package/ios/Application/Navigation/Tracking/ScreenTracker.swift +117 -0
- package/ios/Application/StackScreen.swift +41 -0
- package/ios/Badge/BadgeRibbon.swift +80 -0
- package/ios/Button/Button.swift +67 -11
- 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/DateTimePicker/DateTimePicker.swift +4 -2
- package/ios/DateTimePicker/WheelPicker.swift +7 -3
- package/ios/Extensions/ActiveOpacityButtonStyle.swift +14 -0
- 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/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/Uploader/Uploader.swift +0 -9
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/ios-demo/README.md +120 -0
- package/package.json +1 -1
- package/settings.gradle.kts +64 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//
|
|
2
|
+
// ScreenTracker.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/tracking/ScreenTracker.kt`. Pure
|
|
6
|
+
// logic, no platform dependency beyond the `MaxApi` bridge.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
|
|
11
|
+
public final class ScreenTrackingState {
|
|
12
|
+
public var time: Double = Date().timeIntervalSince1970 * 1000
|
|
13
|
+
public var releaseNavigated: Bool = false
|
|
14
|
+
public var releaseLoad: Bool = false
|
|
15
|
+
public var releaseInteraction: Bool = false
|
|
16
|
+
public var previousScreenName: String?
|
|
17
|
+
public var action: String = "push"
|
|
18
|
+
|
|
19
|
+
public init() {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public enum ScreenTracker {
|
|
23
|
+
private static var lastScreenName: String?
|
|
24
|
+
|
|
25
|
+
public static func trackScreenNavigated(
|
|
26
|
+
maxApi: MaxApi?,
|
|
27
|
+
context: MiniAppContext?,
|
|
28
|
+
screenName: String,
|
|
29
|
+
action: String,
|
|
30
|
+
state: ScreenTrackingState
|
|
31
|
+
) {
|
|
32
|
+
guard !state.releaseNavigated else { return }
|
|
33
|
+
|
|
34
|
+
let previousScreen = lastScreenName
|
|
35
|
+
lastScreenName = screenName
|
|
36
|
+
|
|
37
|
+
var params = buildBaseParams(context: context, screenName: screenName)
|
|
38
|
+
params["pre_screen_name"] = previousScreen
|
|
39
|
+
params["state"] = "navigated"
|
|
40
|
+
params["action"] = action
|
|
41
|
+
maxApi?.track(eventName: "auto_screen_navigated", params: params)
|
|
42
|
+
|
|
43
|
+
state.releaseNavigated = true
|
|
44
|
+
state.previousScreenName = previousScreen
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public static func trackScreenDisplayed(
|
|
48
|
+
maxApi: MaxApi?,
|
|
49
|
+
context: MiniAppContext?,
|
|
50
|
+
screenName: String,
|
|
51
|
+
duration: Double,
|
|
52
|
+
state: ScreenTrackingState
|
|
53
|
+
) {
|
|
54
|
+
guard !state.releaseLoad else { return }
|
|
55
|
+
|
|
56
|
+
var params = buildBaseParams(context: context, screenName: screenName)
|
|
57
|
+
params["state"] = "load"
|
|
58
|
+
params["duration"] = duration
|
|
59
|
+
maxApi?.track(eventName: "auto_screen_displayed", params: params)
|
|
60
|
+
|
|
61
|
+
state.releaseLoad = true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public static func trackScreenInteracted(
|
|
65
|
+
maxApi: MaxApi?,
|
|
66
|
+
context: MiniAppContext?,
|
|
67
|
+
screenName: String,
|
|
68
|
+
duration: Double,
|
|
69
|
+
totalDuration: Double,
|
|
70
|
+
state: ScreenTrackingState
|
|
71
|
+
) {
|
|
72
|
+
guard !state.releaseInteraction else { return }
|
|
73
|
+
|
|
74
|
+
var params = buildBaseParams(context: context, screenName: screenName)
|
|
75
|
+
params["state"] = "interaction"
|
|
76
|
+
params["duration"] = duration
|
|
77
|
+
params["total_duration"] = totalDuration
|
|
78
|
+
maxApi?.track(eventName: "auto_screen_interacted", params: params)
|
|
79
|
+
|
|
80
|
+
state.releaseInteraction = true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public static func trackPopupDisplayed(
|
|
84
|
+
maxApi: MaxApi?,
|
|
85
|
+
context: MiniAppContext?,
|
|
86
|
+
parentScreenName: String?
|
|
87
|
+
) {
|
|
88
|
+
maxApi?.track(eventName: "auto_popup_displayed", params: [
|
|
89
|
+
"screen_name": parentScreenName,
|
|
90
|
+
"tracking_source": 1,
|
|
91
|
+
"app_id": context?.appId,
|
|
92
|
+
"feature_code": context?.appCode,
|
|
93
|
+
"kits_version": nil,
|
|
94
|
+
"component_name": "Modal"
|
|
95
|
+
])
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private static func buildBaseParams(
|
|
99
|
+
context: MiniAppContext?,
|
|
100
|
+
screenName: String,
|
|
101
|
+
componentName: String = "Screen"
|
|
102
|
+
) -> [String: Any?] {
|
|
103
|
+
[
|
|
104
|
+
"app_id": context?.appId,
|
|
105
|
+
"feature_code": context?.appCode,
|
|
106
|
+
"screen_name": screenName,
|
|
107
|
+
"component_name": componentName,
|
|
108
|
+
"tracking_source": 1
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public static func getLastScreenName() -> String? { lastScreenName }
|
|
113
|
+
|
|
114
|
+
public static func reset() {
|
|
115
|
+
lastScreenName = nil
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -39,6 +39,7 @@ public struct StackScreen<Content: View>: View {
|
|
|
39
39
|
var layoutOffset: CGFloat
|
|
40
40
|
var inputSearchProps: InputSearchProps?
|
|
41
41
|
var fabProps: FabProps?
|
|
42
|
+
var screenName: String = ""
|
|
42
43
|
var content: () -> Content
|
|
43
44
|
|
|
44
45
|
public init(
|
|
@@ -66,6 +67,7 @@ public struct StackScreen<Content: View>: View {
|
|
|
66
67
|
layoutOffset: CGFloat = 56,
|
|
67
68
|
inputSearchProps: InputSearchProps? = nil,
|
|
68
69
|
fabProps: FabProps? = nil,
|
|
70
|
+
screenName: String = "",
|
|
69
71
|
@ViewBuilder content: @escaping () -> Content
|
|
70
72
|
) {
|
|
71
73
|
self.backgroundColor = backgroundColor
|
|
@@ -92,6 +94,7 @@ public struct StackScreen<Content: View>: View {
|
|
|
92
94
|
self.layoutOffset = layoutOffset
|
|
93
95
|
self.inputSearchProps = inputSearchProps
|
|
94
96
|
self.fabProps = fabProps
|
|
97
|
+
self.screenName = screenName
|
|
95
98
|
self.content = content
|
|
96
99
|
}
|
|
97
100
|
|
|
@@ -100,6 +103,13 @@ public struct StackScreen<Content: View>: View {
|
|
|
100
103
|
@State private var headerRightWidth: CGFloat = 0
|
|
101
104
|
@State private var isBottomTabRoot: Bool = false
|
|
102
105
|
|
|
106
|
+
@Environment(\.applicationEnvironment) private var appEnv
|
|
107
|
+
|
|
108
|
+
// Auto-tracking state (mirrors Compose's `remember { ScreenTrackingState() }` +
|
|
109
|
+
// the once-computed push/back `action`).
|
|
110
|
+
@State private var trackingState = ScreenTrackingState()
|
|
111
|
+
@State private var trackingAction: String = ScreenTracker.getLastScreenName() != nil ? "push" : "back"
|
|
112
|
+
|
|
103
113
|
/// Header background fade tied to scroll: 0 → transparent (over a hero image
|
|
104
114
|
/// or transparent header), 1 → opaque surface with the drop shadow.
|
|
105
115
|
private var scrollOpacity: CGFloat { min(scrollOffset / 114, 1) }
|
|
@@ -193,6 +203,37 @@ public struct StackScreen<Content: View>: View {
|
|
|
193
203
|
// taps still reach buttons/fields while also dismissing the keyboard.
|
|
194
204
|
.modifier(HideKeyboardOnTap(enabled: keyboardShouldPersistTaps))
|
|
195
205
|
}
|
|
206
|
+
.task(id: screenName) {
|
|
207
|
+
ScreenTracker.trackScreenNavigated(
|
|
208
|
+
maxApi: appEnv.maxApi,
|
|
209
|
+
context: appEnv.applicationContext,
|
|
210
|
+
screenName: screenName,
|
|
211
|
+
action: trackingAction,
|
|
212
|
+
state: trackingState
|
|
213
|
+
)
|
|
214
|
+
do {
|
|
215
|
+
try await Task.sleep(nanoseconds: 2_000_000_000)
|
|
216
|
+
} catch {
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
let loadTime = Date().timeIntervalSince1970 * 1000 - trackingState.time
|
|
220
|
+
ScreenTracker.trackScreenDisplayed(
|
|
221
|
+
maxApi: appEnv.maxApi,
|
|
222
|
+
context: appEnv.applicationContext,
|
|
223
|
+
screenName: screenName,
|
|
224
|
+
duration: loadTime,
|
|
225
|
+
state: trackingState
|
|
226
|
+
)
|
|
227
|
+
let interactionTime = Date().timeIntervalSince1970 * 1000 - trackingState.time
|
|
228
|
+
ScreenTracker.trackScreenInteracted(
|
|
229
|
+
maxApi: appEnv.maxApi,
|
|
230
|
+
context: appEnv.applicationContext,
|
|
231
|
+
screenName: screenName,
|
|
232
|
+
duration: interactionTime - loadTime,
|
|
233
|
+
totalDuration: interactionTime,
|
|
234
|
+
state: trackingState
|
|
235
|
+
)
|
|
236
|
+
}
|
|
196
237
|
}
|
|
197
238
|
|
|
198
239
|
// MARK: - Header + content layer
|
|
@@ -196,6 +196,86 @@ public struct BadgeRibbon: View {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/// Ported from Compose's `RoundedBadgeRibbon` (BadgeRibbon.kt:164-326): a canvas-drawn
|
|
200
|
+
/// ribbon shape sized to its text, mirrored per corner via scaleX/scaleY (never an actual
|
|
201
|
+
/// rotation) — the label gets the same scale applied a second time so it stays right-reading
|
|
202
|
+
/// while only the ribbon shape flips.
|
|
203
|
+
public struct RoundedBadgeRibbon: View {
|
|
204
|
+
let text: String
|
|
205
|
+
let position: RibbonPosition
|
|
206
|
+
|
|
207
|
+
public init(text: String = "Label", position: RibbonPosition = .topLeft) {
|
|
208
|
+
self.text = text
|
|
209
|
+
self.position = position
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private var scale: (x: CGFloat, y: CGFloat) {
|
|
213
|
+
switch position {
|
|
214
|
+
case .topLeft: return (-1, 1)
|
|
215
|
+
case .topRight: return (1, 1)
|
|
216
|
+
case .bottomRight: return (1, -1)
|
|
217
|
+
case .bottomLeft: return (-1, -1)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private var measuredTextSize: CGSize {
|
|
222
|
+
let fontSize = scaleSize(TypographyStyle.labelXsMedium.fontSize)
|
|
223
|
+
let font = UIFont.systemFont(ofSize: fontSize, weight: .medium)
|
|
224
|
+
return text.size(withAttributes: [.font: font])
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
public var body: some View {
|
|
228
|
+
let textSize = measuredTextSize
|
|
229
|
+
let minRibbonHeight = max(textSize.height * 1.2, 16)
|
|
230
|
+
let padding = minRibbonHeight / 4
|
|
231
|
+
let badgeHeight = minRibbonHeight + padding
|
|
232
|
+
let badgeWidth = max(textSize.width + padding * 2, 28)
|
|
233
|
+
|
|
234
|
+
ZStack {
|
|
235
|
+
Canvas { context, size in
|
|
236
|
+
let ribbonHeight = size.height - padding
|
|
237
|
+
let cornerRadius = (badgeHeight - padding) / 2
|
|
238
|
+
let mainColor = Colors.orange03
|
|
239
|
+
let tailColor = Colors.red01
|
|
240
|
+
|
|
241
|
+
let headWidth: CGFloat = 4
|
|
242
|
+
// Kotlin computes `leftSectionWidth` + `middleSectionWidth` here, but those
|
|
243
|
+
// cancel algebraically to `width - headWidth` — kept as the direct result.
|
|
244
|
+
let rightX = size.width - headWidth
|
|
245
|
+
|
|
246
|
+
let leftMiddlePath = RoundedCorners(topLeft: cornerRadius, topRight: 0, bottomLeft: cornerRadius, bottomRight: 0)
|
|
247
|
+
.path(in: CGRect(x: 0, y: 0, width: rightX + 1, height: ribbonHeight))
|
|
248
|
+
context.fill(leftMiddlePath, with: .color(mainColor))
|
|
249
|
+
|
|
250
|
+
let headPath = RoundedCorners(topLeft: 0, topRight: cornerRadius, bottomLeft: 0, bottomRight: 0)
|
|
251
|
+
.path(in: CGRect(x: rightX, y: 0, width: headWidth, height: ribbonHeight))
|
|
252
|
+
context.fill(headPath, with: .color(mainColor))
|
|
253
|
+
|
|
254
|
+
let halfHeadWidth = headWidth / 2
|
|
255
|
+
let tailStartX = rightX + headWidth - halfHeadWidth
|
|
256
|
+
let tailY = ribbonHeight
|
|
257
|
+
let tailHeight = padding
|
|
258
|
+
|
|
259
|
+
context.fill(
|
|
260
|
+
Path(CGRect(x: tailStartX, y: tailY, width: halfHeadWidth, height: halfHeadWidth)),
|
|
261
|
+
with: .color(mainColor)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
let tailRightPath = RoundedCorners(topLeft: 0, topRight: tailHeight, bottomLeft: 0, bottomRight: tailHeight)
|
|
265
|
+
.path(in: CGRect(x: tailStartX, y: tailY, width: halfHeadWidth, height: tailHeight))
|
|
266
|
+
context.fill(tailRightPath, with: .color(tailColor))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
MomoText(text, typography: .labelXsMedium, color: Colors.black01, maxLines: 1)
|
|
270
|
+
.padding(.bottom, padding)
|
|
271
|
+
.padding(.horizontal, padding / 2)
|
|
272
|
+
.scaleEffect(x: scale.x, y: scale.y)
|
|
273
|
+
}
|
|
274
|
+
.frame(width: badgeWidth, height: badgeHeight)
|
|
275
|
+
.scaleEffect(x: scale.x, y: scale.y)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
199
279
|
// MARK: - Constants
|
|
200
280
|
private struct RibbonConstants {
|
|
201
281
|
static let ribbonHeight: CGFloat = 20
|
package/ios/Button/Button.swift
CHANGED
|
@@ -123,7 +123,6 @@ extension ButtonSize {
|
|
|
123
123
|
case .large: return .actionDefaultBold
|
|
124
124
|
case .medium: return .actionSBold
|
|
125
125
|
case .small: return .actionXsBold
|
|
126
|
-
default : return .actionDefaultBold
|
|
127
126
|
}
|
|
128
127
|
}
|
|
129
128
|
}
|
|
@@ -162,6 +161,35 @@ public struct Button: View {
|
|
|
162
161
|
self.useTintColor = useTintColor
|
|
163
162
|
}
|
|
164
163
|
|
|
164
|
+
/// Mirrors Kotlin's `Button(iconLeft: String, iconRight: String, ...)`, which resolves
|
|
165
|
+
/// icons by design-system source name through the shared `Icon` component rather than
|
|
166
|
+
/// a caller-supplied view. `iconLeft` is required here (unlike Kotlin's `= ""` default)
|
|
167
|
+
/// solely to keep this initializer unambiguous alongside the `AnyView`-based one above —
|
|
168
|
+
/// pass `""` explicitly for an icon-right-only button.
|
|
169
|
+
public init(
|
|
170
|
+
title: String = "Button",
|
|
171
|
+
action: @escaping () -> Void,
|
|
172
|
+
type: ButtonType = .primary,
|
|
173
|
+
size: ButtonSize = .large,
|
|
174
|
+
iconLeft: String,
|
|
175
|
+
iconRight: String = "",
|
|
176
|
+
isFull: Bool = true,
|
|
177
|
+
loading: Bool = false,
|
|
178
|
+
useTintColor: Bool = true
|
|
179
|
+
) {
|
|
180
|
+
self.init(
|
|
181
|
+
title: title,
|
|
182
|
+
action: action,
|
|
183
|
+
type: type,
|
|
184
|
+
size: size,
|
|
185
|
+
iconLeft: iconLeft.isEmpty ? nil : AnyView(Icon(source: iconLeft)),
|
|
186
|
+
iconRight: iconRight.isEmpty ? nil : AnyView(Icon(source: iconRight)),
|
|
187
|
+
isFull: isFull,
|
|
188
|
+
loading: loading,
|
|
189
|
+
useTintColor: useTintColor
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
165
193
|
private func shouldLoadingOnLeft(_ left: AnyView?, _ right: AnyView?) -> Bool {
|
|
166
194
|
let hasLeft = left != nil
|
|
167
195
|
let hasRight = right != nil
|
|
@@ -214,14 +242,15 @@ public struct Button: View {
|
|
|
214
242
|
.frame(maxWidth: isFull ? .infinity : nil)
|
|
215
243
|
.padding(.horizontal, size.padding)
|
|
216
244
|
.frame(height: size.height)
|
|
217
|
-
.background(bg)
|
|
218
|
-
.overlay(
|
|
219
|
-
RoundedRectangle(cornerRadius: size.radius)
|
|
220
|
-
.strokeBorder(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
|
|
221
|
-
)
|
|
222
|
-
.clipShape(RoundedRectangle(cornerRadius: size.radius))
|
|
223
245
|
}
|
|
224
|
-
.buttonStyle(ButtonPressFeedbackStyle(
|
|
246
|
+
.buttonStyle(ButtonPressFeedbackStyle(
|
|
247
|
+
isEnabled: isEnabled,
|
|
248
|
+
background: bg,
|
|
249
|
+
border: border,
|
|
250
|
+
borderWidth: borderWidth,
|
|
251
|
+
radius: size.radius,
|
|
252
|
+
height: size.height
|
|
253
|
+
))
|
|
225
254
|
.disabled(!isEnabled)
|
|
226
255
|
}
|
|
227
256
|
|
|
@@ -238,12 +267,39 @@ public struct Button: View {
|
|
|
238
267
|
}
|
|
239
268
|
}
|
|
240
269
|
|
|
270
|
+
/// Mirrors Compose's press feedback: the background shrinks via an animated
|
|
271
|
+
/// horizontal inset while the content fades, rather than a plain opacity dim on
|
|
272
|
+
/// the whole button (matching `animatedPadding`/`alpha` in Button.kt).
|
|
241
273
|
private struct ButtonPressFeedbackStyle: ButtonStyle {
|
|
242
274
|
let isEnabled: Bool
|
|
275
|
+
let background: Color
|
|
276
|
+
let border: Color?
|
|
277
|
+
let borderWidth: CGFloat
|
|
278
|
+
let radius: CGFloat
|
|
279
|
+
let height: CGFloat
|
|
243
280
|
|
|
244
281
|
func makeBody(configuration: Configuration) -> some View {
|
|
245
|
-
configuration.
|
|
246
|
-
|
|
247
|
-
|
|
282
|
+
let pressed = configuration.isPressed && isEnabled
|
|
283
|
+
|
|
284
|
+
ZStack {
|
|
285
|
+
RoundedRectangle(cornerRadius: radius)
|
|
286
|
+
.fill(background)
|
|
287
|
+
.overlay(
|
|
288
|
+
RoundedRectangle(cornerRadius: radius)
|
|
289
|
+
.strokeBorder(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
|
|
290
|
+
)
|
|
291
|
+
.padding(.horizontal, pressed ? 2 : 0)
|
|
292
|
+
|
|
293
|
+
configuration.label
|
|
294
|
+
.opacity(pressed ? 0.5 : 1)
|
|
295
|
+
}
|
|
296
|
+
// Pin the whole button to its intended height explicitly: the bare
|
|
297
|
+
// RoundedRectangle background has no intrinsic size of its own, so left
|
|
298
|
+
// unconstrained in a ZStack inside a non-height-constraining container
|
|
299
|
+
// (e.g. a footer VStack) it can report an oversized ideal height and
|
|
300
|
+
// stretch the button — and its container — far taller than intended.
|
|
301
|
+
.frame(height: height)
|
|
302
|
+
.clipShape(RoundedRectangle(cornerRadius: radius))
|
|
303
|
+
.animation(.easeInOut(duration: 0.1), value: pressed)
|
|
248
304
|
}
|
|
249
305
|
}
|
|
@@ -94,12 +94,27 @@ public struct Carousel<Content: View>: View {
|
|
|
94
94
|
// Offset so the current page is centered within the viewport padding.
|
|
95
95
|
let baseOffset = -CGFloat(currentPage) * step + contentPadding
|
|
96
96
|
|
|
97
|
+
// Windows the rendered range around `currentPage` instead of instantiating all
|
|
98
|
+
// `virtualPageCount` slides up front — with `loop=true`, that's `itemCount * 1000`
|
|
99
|
+
// eager views, a real memory/perf regression versus Compose's lazy `HorizontalPager`.
|
|
100
|
+
// Clear spacers of the skipped width keep the HStack's total layout size (and thus
|
|
101
|
+
// the existing `baseOffset` math) unchanged.
|
|
102
|
+
let windowRadius = 3
|
|
103
|
+
let windowStart = max(0, currentPage - windowRadius)
|
|
104
|
+
let windowEnd = min(virtualPageCount - 1, currentPage + windowRadius)
|
|
105
|
+
|
|
97
106
|
ZStack {
|
|
98
107
|
HStack(spacing: 0) {
|
|
99
|
-
|
|
108
|
+
if windowStart > 0 {
|
|
109
|
+
Color.clear.frame(width: CGFloat(windowStart) * step)
|
|
110
|
+
}
|
|
111
|
+
ForEach(windowStart...windowEnd, id: \.self) { page in
|
|
100
112
|
slide(page: page, step: step, viewportWidth: viewportWidth)
|
|
101
113
|
.frame(width: pageWidth)
|
|
102
114
|
}
|
|
115
|
+
if windowEnd < virtualPageCount - 1 {
|
|
116
|
+
Color.clear.frame(width: CGFloat(virtualPageCount - 1 - windowEnd) * step)
|
|
117
|
+
}
|
|
103
118
|
}
|
|
104
119
|
.offset(x: baseOffset + dragOffset)
|
|
105
120
|
.animation(isDragging ? nil : .easeInOut(duration: 0.25), value: currentPage)
|
|
@@ -28,29 +28,23 @@ public struct Checkbox: View{
|
|
|
28
28
|
public var body: some View {
|
|
29
29
|
HStack(spacing: Spacing.S) {
|
|
30
30
|
// Checkbox
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
SwiftUI.Button(action: onCheck) {
|
|
32
|
+
ZStack {
|
|
33
|
+
RoundedRectangle(cornerRadius: Radius.XS)
|
|
34
|
+
.fill(backgroundColor)
|
|
35
|
+
.frame(width: 20, height: 20)
|
|
36
|
+
.overlay(
|
|
37
|
+
RoundedRectangle(cornerRadius: Radius.XS)
|
|
38
|
+
.stroke(borderColor, lineWidth: Spacing.XXS)
|
|
39
|
+
)
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.scaledToFit()
|
|
44
|
-
.foregroundColor(Colors.black01)
|
|
45
|
-
.frame(width: 12, height: 12)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.onTapGesture {
|
|
50
|
-
if !disabled {
|
|
51
|
-
onCheck()
|
|
41
|
+
if checked {
|
|
42
|
+
Icon(source: indeterminate ? "navigation_minus" : "ic_checked", size: 20, color: Colors.black01)
|
|
43
|
+
}
|
|
52
44
|
}
|
|
53
45
|
}
|
|
46
|
+
.buttonStyle(ActiveOpacityButtonStyle(activeOpacity: 0.8))
|
|
47
|
+
.disabled(disabled)
|
|
54
48
|
|
|
55
49
|
// Title
|
|
56
50
|
if !title.isEmpty {
|
package/ios/Chip/Chip.swift
CHANGED
|
@@ -52,6 +52,13 @@ public struct Chip: View {
|
|
|
52
52
|
|
|
53
53
|
public var body: some View {
|
|
54
54
|
let dims = (size == .small) ? Self.Small : Self.Large
|
|
55
|
+
// Mirrors Compose's `scaleSize(...)` on every chip dimension so it responds
|
|
56
|
+
// to Dynamic Type scaling the same way the rest of the kit does.
|
|
57
|
+
let height = scaleSize(dims.height)
|
|
58
|
+
let horizontal = scaleSize(dims.horizontal)
|
|
59
|
+
let iconSize = scaleSize(dims.iconSize)
|
|
60
|
+
let iconSpacing = scaleSize(dims.iconSpacing)
|
|
61
|
+
let radius = scaleSize(Radius.L)
|
|
55
62
|
|
|
56
63
|
let bg: Color = {
|
|
57
64
|
if selected { return theme.colors.background.selected }
|
|
@@ -63,36 +70,38 @@ public struct Chip: View {
|
|
|
63
70
|
let leftTint: Color = selected ? theme.colors.primary : iconLeftTint ?? theme.colors.text.default
|
|
64
71
|
let rightTint: Color = selected ? theme.colors.primary : iconRightTint ?? theme.colors.text.default
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
SwiftUI.Button(action: onClick) {
|
|
74
|
+
HStack(spacing: iconSpacing) {
|
|
75
|
+
if let iconLeft {
|
|
76
|
+
Icon(source: iconLeft, size: iconSize, color: leftTint)
|
|
77
|
+
}
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
if let text = label, !text.isEmpty {
|
|
80
|
+
MomoText(text, typography: (size == .large) ? .labelDefaultMedium : .labelSMedium, color: textColor)
|
|
81
|
+
.lineLimit(1)
|
|
82
|
+
}
|
|
75
83
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
.padding(.horizontal, dims.horizontal)
|
|
81
|
-
.frame(height: dims.height)
|
|
82
|
-
.background(
|
|
83
|
-
RoundedRectangle(cornerRadius: Radius.L, style: .continuous)
|
|
84
|
-
.fill(bg)
|
|
85
|
-
)
|
|
86
|
-
.overlay(
|
|
87
|
-
Group {
|
|
88
|
-
if selected {
|
|
89
|
-
RoundedRectangle(cornerRadius: Radius.L, style: .continuous)
|
|
90
|
-
.stroke(theme.colors.secondary, lineWidth: 2)
|
|
84
|
+
if let iconRight {
|
|
85
|
+
Icon(source: iconRight, size: iconSize, color: rightTint)
|
|
91
86
|
}
|
|
92
87
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
.padding(.horizontal, horizontal)
|
|
89
|
+
.frame(height: height)
|
|
90
|
+
.background(
|
|
91
|
+
RoundedRectangle(cornerRadius: radius, style: .continuous)
|
|
92
|
+
.fill(bg)
|
|
93
|
+
)
|
|
94
|
+
.overlay(
|
|
95
|
+
Group {
|
|
96
|
+
if selected {
|
|
97
|
+
RoundedRectangle(cornerRadius: radius, style: .continuous)
|
|
98
|
+
.stroke(theme.colors.secondary, lineWidth: 2)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
.contentShape(RoundedRectangle(cornerRadius: radius, style: .continuous))
|
|
103
|
+
}
|
|
104
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
96
105
|
.modifier(ChipAccessibilityModifier(accessibilityLabel: accessibilityLabel))
|
|
97
106
|
}
|
|
98
107
|
}
|
|
@@ -30,6 +30,7 @@ public struct Collapse<Content: View>: View {
|
|
|
30
30
|
private let useBackgroundCollapseButton: Bool
|
|
31
31
|
private let backgroundColor: Color?
|
|
32
32
|
private let titleColor: Color?
|
|
33
|
+
private let accessibilityId: String?
|
|
33
34
|
private let onPress: ((Bool) -> Void)?
|
|
34
35
|
private let content: (() -> Content)?
|
|
35
36
|
|
|
@@ -49,6 +50,7 @@ public struct Collapse<Content: View>: View {
|
|
|
49
50
|
useBackgroundCollapseButton: Bool = false,
|
|
50
51
|
backgroundColor: Color? = nil,
|
|
51
52
|
titleColor: Color? = nil,
|
|
53
|
+
accessibilityId: String? = nil,
|
|
52
54
|
onPress: ((Bool) -> Void)? = nil,
|
|
53
55
|
@ViewBuilder content: @escaping () -> Content
|
|
54
56
|
) {
|
|
@@ -65,11 +67,17 @@ public struct Collapse<Content: View>: View {
|
|
|
65
67
|
self.useBackgroundCollapseButton = useBackgroundCollapseButton
|
|
66
68
|
self.backgroundColor = backgroundColor
|
|
67
69
|
self.titleColor = titleColor
|
|
70
|
+
self.accessibilityId = accessibilityId
|
|
68
71
|
self.onPress = onPress
|
|
69
72
|
self.content = content
|
|
70
73
|
_expanded = State(initialValue: expandDefault)
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
// Mirrors Kotlin's `accessibilityId ?: "Collapse/$title"`.
|
|
77
|
+
private var automationId: String {
|
|
78
|
+
accessibilityId ?? "Collapse/\(title)"
|
|
79
|
+
}
|
|
80
|
+
|
|
73
81
|
private var resolvedBackgroundColor: Color {
|
|
74
82
|
backgroundColor ?? theme.colors.background.surface
|
|
75
83
|
}
|
|
@@ -97,6 +105,7 @@ public struct Collapse<Content: View>: View {
|
|
|
97
105
|
.frame(maxWidth: .infinity)
|
|
98
106
|
.background(resolvedBackgroundColor)
|
|
99
107
|
.clipShape(RoundedRectangle(cornerRadius: Radius.S, style: .continuous))
|
|
108
|
+
.accessibilityIdentifier(automationId)
|
|
100
109
|
.overlay(
|
|
101
110
|
showBorder
|
|
102
111
|
? RoundedRectangle(cornerRadius: Radius.S, style: .continuous)
|
|
@@ -159,7 +168,8 @@ public struct Collapse<Content: View>: View {
|
|
|
159
168
|
.frame(maxWidth: .infinity)
|
|
160
169
|
.contentShape(Rectangle())
|
|
161
170
|
}
|
|
162
|
-
.buttonStyle(
|
|
171
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
172
|
+
.accessibilityIdentifier("\(automationId)|touch")
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
private var chevron: some View {
|
|
@@ -14,6 +14,8 @@ private let datePickerWithLabelsHeight: CGFloat = 238
|
|
|
14
14
|
/// - `"HH:mm"` shows hour + minute; a lone `"HH"` switches the hour wheel to a
|
|
15
15
|
/// 12-hour clock with an AM/PM column.
|
|
16
16
|
public struct DateTimePicker: View {
|
|
17
|
+
@Environment(\.appTheme) private var theme
|
|
18
|
+
|
|
17
19
|
// MARK: Lifecycle
|
|
18
20
|
|
|
19
21
|
public init(
|
|
@@ -84,7 +86,7 @@ public struct DateTimePicker: View {
|
|
|
84
86
|
|
|
85
87
|
VStack(spacing: 0) {
|
|
86
88
|
if hasLabel {
|
|
87
|
-
MomoText(arrayLabelTime[index], typography: .actionSBold, color:
|
|
89
|
+
MomoText(arrayLabelTime[index], typography: .actionSBold, color: theme.colors.text.default)
|
|
88
90
|
.padding(.bottom, Spacing.S)
|
|
89
91
|
}
|
|
90
92
|
|
|
@@ -100,7 +102,7 @@ public struct DateTimePicker: View {
|
|
|
100
102
|
}
|
|
101
103
|
.frame(maxWidth: .infinity)
|
|
102
104
|
.frame(height: pickerHeight)
|
|
103
|
-
.background(
|
|
105
|
+
.background(theme.colors.background.surface)
|
|
104
106
|
.padding(.horizontal, Spacing.M)
|
|
105
107
|
.onAppear { emitIfValid() }
|
|
106
108
|
}
|
|
@@ -14,6 +14,8 @@ private let wheelPickerBorderWidth: CGFloat = 1
|
|
|
14
14
|
/// real value can be centered. The currently centered row is reported back via
|
|
15
15
|
/// `onChange(name, value)`.
|
|
16
16
|
public struct WheelPicker: View {
|
|
17
|
+
@Environment(\.appTheme) private var theme
|
|
18
|
+
|
|
17
19
|
// MARK: Lifecycle
|
|
18
20
|
|
|
19
21
|
public init(
|
|
@@ -76,9 +78,9 @@ public struct WheelPicker: View {
|
|
|
76
78
|
.frame(height: wheelItemHeight * CGFloat(wheelVisibleCount))
|
|
77
79
|
.overlay(
|
|
78
80
|
RoundedRectangle(cornerRadius: Radius.S)
|
|
79
|
-
.stroke(
|
|
81
|
+
.stroke(theme.colors.border.default, lineWidth: wheelPickerBorderWidth)
|
|
80
82
|
)
|
|
81
|
-
.background(
|
|
83
|
+
.background(theme.colors.background.surface.clipShape(RoundedRectangle(cornerRadius: Radius.S)))
|
|
82
84
|
.overlay(
|
|
83
85
|
// Centered translucent selection band.
|
|
84
86
|
Rectangle()
|
|
@@ -121,6 +123,8 @@ public struct WheelPicker: View {
|
|
|
121
123
|
// MARK: - WheelPickerItem
|
|
122
124
|
|
|
123
125
|
private struct WheelPickerItem: View {
|
|
126
|
+
@Environment(\.appTheme) private var theme
|
|
127
|
+
|
|
124
128
|
let text: String
|
|
125
129
|
let isSelected: Bool
|
|
126
130
|
let index: Int
|
|
@@ -142,7 +146,7 @@ private struct WheelPickerItem: View {
|
|
|
142
146
|
let scale: CGFloat = isSelected ? 1.0 : 0.87
|
|
143
147
|
|
|
144
148
|
return ZStack {
|
|
145
|
-
MomoText(text, typography: .actionSBold, color:
|
|
149
|
+
MomoText(text, typography: .actionSBold, color: theme.colors.text.default)
|
|
146
150
|
.scaleEffect(scale)
|
|
147
151
|
}
|
|
148
152
|
.frame(maxWidth: .infinity)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
/// Mirrors Compose's `Modifier.activeOpacityClickable(activeOpacity:)` (modifier/Clickable.kt):
|
|
4
|
+
/// fades the whole tappable subtree to `activeOpacity` while pressed, animated, instead of
|
|
5
|
+
/// relying on a platform-default press indication.
|
|
6
|
+
struct ActiveOpacityButtonStyle: ButtonStyle {
|
|
7
|
+
var activeOpacity: Double = 0.2
|
|
8
|
+
|
|
9
|
+
func makeBody(configuration: Configuration) -> some View {
|
|
10
|
+
configuration.label
|
|
11
|
+
.opacity(configuration.isPressed ? activeOpacity : 1)
|
|
12
|
+
.animation(.default, value: configuration.isPressed)
|
|
13
|
+
}
|
|
14
|
+
}
|