@momo-kits/native-kits 0.162.2-sp.7 → 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/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/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 +98 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +157 -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 +562 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +154 -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/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/ios-demo/README.md +120 -0
- package/local.properties +8 -0
- package/package.json +1 -1
- package/settings.gradle.kts +64 -0
- package/ios/Colors+Radius+Spacing/MoMoUIKitsResources.swift +0 -2
|
@@ -14,9 +14,11 @@ import SwiftUI
|
|
|
14
14
|
public struct NavigationContainer<Initial: View>: View {
|
|
15
15
|
@StateObject private var navigator: Navigator
|
|
16
16
|
@StateObject private var applicationEnvironment: ApplicationEnvironment
|
|
17
|
+
@StateObject private var localize: Localize
|
|
17
18
|
|
|
18
19
|
private let setNavigator: ((Navigator) -> Void)?
|
|
19
20
|
private let composeApi: KitComposeApi?
|
|
21
|
+
private let maxApi: MaxApi?
|
|
20
22
|
|
|
21
23
|
public init(
|
|
22
24
|
initialScreenName: String = "",
|
|
@@ -25,6 +27,8 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
25
27
|
applicationContext: MiniAppContext? = nil,
|
|
26
28
|
composeApi: KitComposeApi? = nil,
|
|
27
29
|
config: KitConfig? = nil,
|
|
30
|
+
maxApi: MaxApi? = nil,
|
|
31
|
+
localize: Localize? = nil,
|
|
28
32
|
setNavigator: ((Navigator) -> Void)? = nil
|
|
29
33
|
) {
|
|
30
34
|
let nav = Navigator(
|
|
@@ -38,10 +42,13 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
38
42
|
wrappedValue: ApplicationEnvironment(
|
|
39
43
|
applicationContext: applicationContext,
|
|
40
44
|
composeApi: composeApi,
|
|
41
|
-
config: config
|
|
45
|
+
config: config,
|
|
46
|
+
maxApi: maxApi
|
|
42
47
|
)
|
|
43
48
|
)
|
|
49
|
+
_localize = StateObject(wrappedValue: localize ?? Localize())
|
|
44
50
|
self.composeApi = composeApi
|
|
51
|
+
self.maxApi = maxApi
|
|
45
52
|
self.setNavigator = setNavigator
|
|
46
53
|
}
|
|
47
54
|
|
|
@@ -55,11 +62,13 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
55
62
|
.environmentObject(navigator)
|
|
56
63
|
.environmentObject(applicationEnvironment)
|
|
57
64
|
.environment(\.applicationEnvironment, applicationEnvironment)
|
|
65
|
+
.environmentObject(localize)
|
|
58
66
|
.fullScreenCover(item: $navigator.presented) { route in
|
|
59
67
|
screenView(forDialog: route)
|
|
60
68
|
.environmentObject(navigator)
|
|
61
69
|
.environmentObject(applicationEnvironment)
|
|
62
70
|
.environment(\.applicationEnvironment, applicationEnvironment)
|
|
71
|
+
.environmentObject(localize)
|
|
63
72
|
}
|
|
64
73
|
.overlay {
|
|
65
74
|
if let item = navigator.overplay {
|
|
@@ -67,6 +76,7 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
67
76
|
.environmentObject(navigator)
|
|
68
77
|
.environmentObject(applicationEnvironment)
|
|
69
78
|
.environment(\.applicationEnvironment, applicationEnvironment)
|
|
79
|
+
.environmentObject(localize)
|
|
70
80
|
}
|
|
71
81
|
}
|
|
72
82
|
.onAppear {
|
|
@@ -74,12 +84,19 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
74
84
|
navigator.composeApi = composeApi
|
|
75
85
|
}
|
|
76
86
|
setNavigator?(navigator)
|
|
87
|
+
// Mirrors Compose's LaunchedEffect(maxApi, resolvedLocalize): host-reported
|
|
88
|
+
// language wins over the default "vi" only when it resolves to English.
|
|
89
|
+
maxApi?.getLanguage { data in
|
|
90
|
+
if parseLanguage(data) == Localize.EN {
|
|
91
|
+
localize.changeLanguage(Localize.EN)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
77
94
|
}
|
|
78
95
|
}
|
|
79
96
|
|
|
80
97
|
@ViewBuilder
|
|
81
98
|
private func screenView(for route: DynamicScreenRoute) -> some View {
|
|
82
|
-
if let screen =
|
|
99
|
+
if let screen = navigator.registry.getScreen(id: route.id) {
|
|
83
100
|
ScreenHost(screen: screen, navigator: navigator)
|
|
84
101
|
.navigationBarBackButtonHidden(true)
|
|
85
102
|
} else {
|
|
@@ -89,7 +106,7 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
89
106
|
|
|
90
107
|
@ViewBuilder
|
|
91
108
|
private func screenView(forDialog route: DynamicDialogRoute) -> some View {
|
|
92
|
-
if let screen =
|
|
109
|
+
if let screen = navigator.registry.getScreen(id: route.id) {
|
|
93
110
|
ScreenHost(screen: screen, navigator: navigator)
|
|
94
111
|
} else {
|
|
95
112
|
EmptyView()
|
|
@@ -128,6 +145,7 @@ private struct ScreenHost: View {
|
|
|
128
145
|
layoutOffset: options.layoutOffset,
|
|
129
146
|
inputSearchProps: options.inputSearchProps,
|
|
130
147
|
fabProps: options.fabProps,
|
|
148
|
+
screenName: screen.name,
|
|
131
149
|
content: { screen.content() }
|
|
132
150
|
)
|
|
133
151
|
}
|
|
@@ -174,3 +192,9 @@ private func safeAreaBottomInset() -> CGFloat {
|
|
|
174
192
|
.compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
|
|
175
193
|
.first ?? 0
|
|
176
194
|
}
|
|
195
|
+
|
|
196
|
+
private func parseLanguage(_ raw: [String: Any?]?) -> String? {
|
|
197
|
+
guard let response = (raw?["response"] as? String)?.trimmingCharacters(in: .whitespaces).lowercased(),
|
|
198
|
+
!response.isEmpty else { return nil }
|
|
199
|
+
return response.hasPrefix(Localize.EN) ? Localize.EN : Localize.VI
|
|
200
|
+
}
|
|
@@ -30,12 +30,10 @@ public struct DynamicScreen {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
public final class DynamicScreenRegistry {
|
|
33
|
-
public static let shared = DynamicScreenRegistry()
|
|
34
|
-
|
|
35
33
|
private var screens: [Int: DynamicScreen] = [:]
|
|
36
34
|
private var idCounter: Int = 1
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
public init() {}
|
|
39
37
|
|
|
40
38
|
@discardableResult
|
|
41
39
|
public func register(
|
|
@@ -95,6 +93,11 @@ public final class Navigator: ObservableObject {
|
|
|
95
93
|
@Published public var presented: DynamicDialogRoute?
|
|
96
94
|
@Published public var overplay: OverplayItem?
|
|
97
95
|
|
|
96
|
+
/// Owned per-Navigator (mirrors Compose's `remember { DynamicScreenRegistry() }` +
|
|
97
|
+
/// disposal): screens registered by this navigator are released via ARC when the
|
|
98
|
+
/// navigator itself deallocates, instead of accumulating in a process-wide singleton.
|
|
99
|
+
public let registry = DynamicScreenRegistry()
|
|
100
|
+
|
|
98
101
|
/// Animated-close handler registered by the active overplay component
|
|
99
102
|
/// (mirrors Compose's `OverplayComponentRegistry.bindClose`). When set, a
|
|
100
103
|
/// dismiss request runs the component's exit animation before the item is
|
|
@@ -111,7 +114,7 @@ public final class Navigator: ObservableObject {
|
|
|
111
114
|
composeApi: KitComposeApi? = nil
|
|
112
115
|
) {
|
|
113
116
|
self.composeApi = composeApi
|
|
114
|
-
self.rootRoute =
|
|
117
|
+
self.rootRoute = registry.register(
|
|
115
118
|
screenName: initialScreenName,
|
|
116
119
|
content: initialContent,
|
|
117
120
|
options: options
|
|
@@ -121,7 +124,7 @@ public final class Navigator: ObservableObject {
|
|
|
121
124
|
// MARK: Stack
|
|
122
125
|
|
|
123
126
|
public func push<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
124
|
-
let route =
|
|
127
|
+
let route = registry.register(
|
|
125
128
|
screenName: options?.screenName ?? "",
|
|
126
129
|
content: { AnyView(content()) },
|
|
127
130
|
options: options
|
|
@@ -134,7 +137,7 @@ public final class Navigator: ObservableObject {
|
|
|
134
137
|
@ViewBuilder _ content: @escaping () -> V,
|
|
135
138
|
options: NavigationOptions? = nil
|
|
136
139
|
) {
|
|
137
|
-
let route =
|
|
140
|
+
let route = registry.register(
|
|
138
141
|
screenName: screenName,
|
|
139
142
|
content: { AnyView(content()) },
|
|
140
143
|
options: options
|
|
@@ -145,7 +148,7 @@ public final class Navigator: ObservableObject {
|
|
|
145
148
|
public func replace<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
146
149
|
guard !path.isEmpty else { return }
|
|
147
150
|
let removed = path.removeLast()
|
|
148
|
-
|
|
151
|
+
registry.unregister(id: removed.id)
|
|
149
152
|
push(content, options: options)
|
|
150
153
|
}
|
|
151
154
|
|
|
@@ -156,15 +159,15 @@ public final class Navigator: ObservableObject {
|
|
|
156
159
|
) {
|
|
157
160
|
guard !path.isEmpty else { return }
|
|
158
161
|
let removed = path.removeLast()
|
|
159
|
-
|
|
162
|
+
registry.unregister(id: removed.id)
|
|
160
163
|
push(screenName: screenName, content, options: options)
|
|
161
164
|
}
|
|
162
165
|
|
|
163
166
|
public func reset<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
164
|
-
for route in path {
|
|
165
|
-
|
|
167
|
+
for route in path { registry.unregister(id: route.id) }
|
|
168
|
+
registry.unregister(id: rootRoute.id)
|
|
166
169
|
path.removeAll()
|
|
167
|
-
let route =
|
|
170
|
+
let route = registry.register(
|
|
168
171
|
screenName: options?.screenName ?? "",
|
|
169
172
|
content: { AnyView(content()) },
|
|
170
173
|
options: options
|
|
@@ -177,10 +180,10 @@ public final class Navigator: ObservableObject {
|
|
|
177
180
|
@ViewBuilder _ content: @escaping () -> V,
|
|
178
181
|
options: NavigationOptions? = nil
|
|
179
182
|
) {
|
|
180
|
-
for route in path {
|
|
181
|
-
|
|
183
|
+
for route in path { registry.unregister(id: route.id) }
|
|
184
|
+
registry.unregister(id: rootRoute.id)
|
|
182
185
|
path.removeAll()
|
|
183
|
-
let route =
|
|
186
|
+
let route = registry.register(
|
|
184
187
|
screenName: screenName,
|
|
185
188
|
content: { AnyView(content()) },
|
|
186
189
|
options: options
|
|
@@ -191,17 +194,16 @@ public final class Navigator: ObservableObject {
|
|
|
191
194
|
public func pop(_ count: Int = 1, callback: (() -> Void)? = nil) {
|
|
192
195
|
var remaining = count
|
|
193
196
|
while remaining > 0 {
|
|
194
|
-
if overplay
|
|
197
|
+
if overplay?.type == .snackBar {
|
|
198
|
+
// Mirrors Kotlin: a snackbar doesn't block back-navigation, so popping
|
|
199
|
+
// while one is showing pops the screen underneath and clears the
|
|
200
|
+
// snackbar immediately, without playing its dismiss animation.
|
|
201
|
+
overplayDismissHandler = nil
|
|
202
|
+
overplay = nil
|
|
203
|
+
if !popScreen() { break }
|
|
204
|
+
} else if overplay != nil {
|
|
195
205
|
hideOverplay()
|
|
196
|
-
} else if
|
|
197
|
-
let dismissedId = presented?.id
|
|
198
|
-
presented = nil
|
|
199
|
-
if let id = dismissedId { DynamicScreenRegistry.shared.unregister(id: id) }
|
|
200
|
-
} else if !path.isEmpty {
|
|
201
|
-
let removed = path.removeLast()
|
|
202
|
-
DynamicScreenRegistry.shared.unregister(id: removed.id)
|
|
203
|
-
} else {
|
|
204
|
-
composeApi?.requestCallback(funcName: "dismiss", params: nil, onResponse: nil)
|
|
206
|
+
} else if !popScreen() {
|
|
205
207
|
break
|
|
206
208
|
}
|
|
207
209
|
remaining -= 1
|
|
@@ -209,13 +211,32 @@ public final class Navigator: ObservableObject {
|
|
|
209
211
|
callback?()
|
|
210
212
|
}
|
|
211
213
|
|
|
214
|
+
/// Pops the topmost presented screen or stack entry. Returns `false` (and requests
|
|
215
|
+
/// a host dismiss) when there is nothing left to pop.
|
|
216
|
+
@discardableResult
|
|
217
|
+
private func popScreen() -> Bool {
|
|
218
|
+
if presented != nil {
|
|
219
|
+
let dismissedId = presented?.id
|
|
220
|
+
presented = nil
|
|
221
|
+
if let id = dismissedId { registry.unregister(id: id) }
|
|
222
|
+
return true
|
|
223
|
+
} else if !path.isEmpty {
|
|
224
|
+
let removed = path.removeLast()
|
|
225
|
+
registry.unregister(id: removed.id)
|
|
226
|
+
return true
|
|
227
|
+
} else {
|
|
228
|
+
composeApi?.requestCallback(funcName: "dismiss", params: nil, onResponse: nil)
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
212
233
|
public func popToRoot() {
|
|
213
|
-
for route in path {
|
|
234
|
+
for route in path { registry.unregister(id: route.id) }
|
|
214
235
|
path.removeAll()
|
|
215
236
|
}
|
|
216
237
|
|
|
217
238
|
public func onBackSafe(callback: (() -> Void)? = nil) {
|
|
218
|
-
let latest =
|
|
239
|
+
let latest = registry.latest()
|
|
219
240
|
if let onBack = latest?.options?.onBackHandler {
|
|
220
241
|
onBack()
|
|
221
242
|
return
|
|
@@ -256,7 +277,7 @@ public final class Navigator: ObservableObject {
|
|
|
256
277
|
// MARK: Modal-style screen (vertical slide)
|
|
257
278
|
|
|
258
279
|
public func present<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
259
|
-
let route =
|
|
280
|
+
let route = registry.register(
|
|
260
281
|
screenName: options?.screenName ?? "",
|
|
261
282
|
content: { AnyView(content()) },
|
|
262
283
|
options: options
|
|
@@ -269,7 +290,7 @@ public final class Navigator: ObservableObject {
|
|
|
269
290
|
@ViewBuilder _ content: @escaping () -> V,
|
|
270
291
|
options: NavigationOptions? = nil
|
|
271
292
|
) {
|
|
272
|
-
let route =
|
|
293
|
+
let route = registry.register(
|
|
273
294
|
screenName: screenName,
|
|
274
295
|
content: { AnyView(content()) },
|
|
275
296
|
options: options
|
|
@@ -279,7 +300,7 @@ public final class Navigator: ObservableObject {
|
|
|
279
300
|
|
|
280
301
|
public func dismiss() {
|
|
281
302
|
if let id = presented?.id {
|
|
282
|
-
|
|
303
|
+
registry.unregister(id: id)
|
|
283
304
|
}
|
|
284
305
|
presented = nil
|
|
285
306
|
}
|
|
@@ -43,42 +43,84 @@ struct BottomSheetView<Content: View>: View {
|
|
|
43
43
|
let content: () -> Content
|
|
44
44
|
|
|
45
45
|
@EnvironmentObject private var navigator: Navigator
|
|
46
|
+
@Environment(\.appTheme) private var theme
|
|
47
|
+
@Environment(\.applicationEnvironment) private var appEnv
|
|
46
48
|
|
|
47
49
|
/// Vertical translation of the sheet; starts off-screen and animates to 0.
|
|
48
50
|
@State private var sheetOffset: CGFloat = UIScreen.main.bounds.height
|
|
49
|
-
|
|
51
|
+
/// Mirrors Compose's `backgroundAlpha`: only touched by open()/close(), never by drag.
|
|
52
|
+
@State private var backgroundAlpha: CGFloat = 0
|
|
50
53
|
@State private var sheetHeight: CGFloat = 0
|
|
51
54
|
@State private var didAppear = false
|
|
55
|
+
@State private var keyboardHeight: CGFloat = 0
|
|
56
|
+
/// Live drag offset, isolated from `sheetOffset` so a touch-move only invalidates
|
|
57
|
+
/// this gesture-scoped value instead of forcing the whole body (including
|
|
58
|
+
/// `content()`) to reconstruct every frame — mirrors the pattern already used in
|
|
59
|
+
/// `ios/PopupView/PopupView.swift` for smooth drag-to-dismiss.
|
|
60
|
+
@GestureState private var dragTranslation: CGFloat = 0
|
|
52
61
|
|
|
53
62
|
/// Mirrors Compose's `sheetCloseOffset` (100dp drag threshold to dismiss).
|
|
54
63
|
private let closeThreshold: CGFloat = 100
|
|
55
64
|
|
|
65
|
+
/// Mirrors Compose's per-term peak (0.3f). Combined with `backgroundAlpha` the
|
|
66
|
+
/// steady-state (fully open) scrim peaks at 0.6, matching ModalScreen's peak.
|
|
67
|
+
private let maxDynamicAlpha: CGFloat = 0.3
|
|
68
|
+
|
|
56
69
|
private var screenHeight: CGFloat { UIScreen.main.bounds.height }
|
|
57
70
|
private var bottomInset: CGFloat { safeAreaBottomInset() }
|
|
71
|
+
/// While the keyboard is up, it already covers the home-indicator area, so it
|
|
72
|
+
/// replaces (rather than adds to) the usual bottom-inset filler.
|
|
73
|
+
private var effectiveBottomInset: CGFloat { keyboardHeight > 0 ? keyboardHeight : bottomInset }
|
|
58
74
|
|
|
59
75
|
private var backgroundColor: Color {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
76
|
+
isSurface ? theme.colors.background.surface : theme.colors.background.default
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private var effectiveOffset: CGFloat { sheetOffset + dragTranslation }
|
|
80
|
+
|
|
81
|
+
/// Mirrors Compose's `dynamicAlpha`: a plain derived value (not itself animated)
|
|
82
|
+
/// that tracks the current offset live, shrinking toward 0 as the sheet is
|
|
83
|
+
/// dragged down.
|
|
84
|
+
private var dynamicAlpha: CGFloat {
|
|
85
|
+
let denom = sheetHeight > 0 ? sheetHeight : screenHeight
|
|
86
|
+
guard denom > 0 else { return maxDynamicAlpha }
|
|
87
|
+
let progress = min(max(1 - effectiveOffset / denom, 0), 1)
|
|
88
|
+
return progress * maxDynamicAlpha
|
|
63
89
|
}
|
|
64
90
|
|
|
91
|
+
/// Mirrors Compose's `alpha = backgroundAlpha + dynamicAlpha`: additive, so the
|
|
92
|
+
/// scrim peaks at 0.6 at rest and only fades to a 0.3 floor while dragging
|
|
93
|
+
/// (backgroundAlpha holds steady until an explicit close()).
|
|
94
|
+
private var totalScrimAlpha: CGFloat { backgroundAlpha + dynamicAlpha }
|
|
95
|
+
|
|
65
96
|
var body: some View {
|
|
66
97
|
ZStack(alignment: .bottom) {
|
|
67
|
-
// Dimmed scrim
|
|
68
|
-
Color.black.opacity(
|
|
69
|
-
.ignoresSafeArea()
|
|
98
|
+
// Dimmed scrim — additive backgroundAlpha + dynamicAlpha (see above).
|
|
99
|
+
Color.black.opacity(totalScrimAlpha)
|
|
70
100
|
.contentShape(Rectangle())
|
|
71
101
|
.onTapGesture {
|
|
72
102
|
if barrierDismissible { close() }
|
|
73
103
|
}
|
|
74
104
|
|
|
75
105
|
sheet
|
|
76
|
-
.offset(y:
|
|
106
|
+
.offset(y: effectiveOffset)
|
|
77
107
|
}
|
|
78
108
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
109
|
+
.ignoresSafeArea()
|
|
110
|
+
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)) { notification in
|
|
111
|
+
guard let endFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
|
|
112
|
+
withAnimation(.easeOut(duration: 0.25)) {
|
|
113
|
+
keyboardHeight = UIScreen.main.bounds.height - endFrame.origin.y
|
|
114
|
+
}
|
|
115
|
+
}
|
|
79
116
|
.onAppear {
|
|
80
117
|
guard !didAppear else { return }
|
|
81
118
|
didAppear = true
|
|
119
|
+
ScreenTracker.trackPopupDisplayed(
|
|
120
|
+
maxApi: appEnv.maxApi,
|
|
121
|
+
context: appEnv.applicationContext,
|
|
122
|
+
parentScreenName: ScreenTracker.getLastScreenName()
|
|
123
|
+
)
|
|
82
124
|
navigator.registerOverplayDismiss { close() }
|
|
83
125
|
open()
|
|
84
126
|
}
|
|
@@ -103,16 +145,16 @@ struct BottomSheetView<Content: View>: View {
|
|
|
103
145
|
.gesture(dragGesture)
|
|
104
146
|
|
|
105
147
|
Rectangle()
|
|
106
|
-
.fill(
|
|
148
|
+
.fill(theme.colors.border.default)
|
|
107
149
|
.frame(height: 1)
|
|
108
150
|
|
|
109
151
|
content()
|
|
110
152
|
|
|
111
153
|
backgroundColor
|
|
112
|
-
.frame(height:
|
|
154
|
+
.frame(height: effectiveBottomInset)
|
|
113
155
|
}
|
|
114
156
|
.frame(maxWidth: .infinity)
|
|
115
|
-
.frame(
|
|
157
|
+
.frame(alignment: .bottom)
|
|
116
158
|
.background(
|
|
117
159
|
GeometryReader { geo in
|
|
118
160
|
Color.clear.onAppear { sheetHeight = geo.size.height }
|
|
@@ -143,13 +185,33 @@ struct BottomSheetView<Content: View>: View {
|
|
|
143
185
|
// MARK: - Drag handling (mirrors Compose's detectDragGestures)
|
|
144
186
|
|
|
145
187
|
private var dragGesture: some Gesture {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
188
|
+
// `.global` (not the default `.local`) so the gesture tracks against the
|
|
189
|
+
// window rather than the handle's own view, which is itself repositioned
|
|
190
|
+
// every frame by this same gesture's output (`sheet.offset(y: effectiveOffset)`
|
|
191
|
+
// in `body`) — the local coordinate space chasing its own moving reference
|
|
192
|
+
// frame is a plausible source of a continuous drag-time jitter.
|
|
193
|
+
DragGesture(coordinateSpace: .global)
|
|
194
|
+
.onChanged { _ in
|
|
195
|
+
// Touching the handle should immediately settle `sheetOffset` to its
|
|
196
|
+
// resting value (0) with no animation, so a drag that starts while the
|
|
197
|
+
// open animation — or the keyboard-driven resize, which shifts this
|
|
198
|
+
// same bottom-anchored top edge — is still in flight doesn't fight a
|
|
199
|
+
// moving target. Without this, the live drag value chases an animated
|
|
200
|
+
// one, producing a couple of corrective jumps before it settles.
|
|
201
|
+
if sheetOffset != 0 {
|
|
202
|
+
sheetOffset = 0
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
.updating($dragTranslation) { value, state, _ in
|
|
206
|
+
state = max(value.translation.height, 0)
|
|
150
207
|
}
|
|
151
|
-
.onEnded {
|
|
152
|
-
|
|
208
|
+
.onEnded { value in
|
|
209
|
+
let finalOffset = max(value.translation.height, 0)
|
|
210
|
+
// Capture into persisted state BEFORE @GestureState resets to 0,
|
|
211
|
+
// avoiding a one-frame snap back to rest before close()'s own
|
|
212
|
+
// slide-out animation starts (mirrors PopupView's `lastDragPosition`).
|
|
213
|
+
sheetOffset = finalOffset
|
|
214
|
+
if finalOffset > closeThreshold {
|
|
153
215
|
close()
|
|
154
216
|
} else {
|
|
155
217
|
withAnimation(.spring(response: 0.3, dampingFraction: 0.9)) {
|
|
@@ -163,17 +225,23 @@ struct BottomSheetView<Content: View>: View {
|
|
|
163
225
|
|
|
164
226
|
private func open() {
|
|
165
227
|
DispatchQueue.main.async {
|
|
166
|
-
withAnimation(.
|
|
228
|
+
withAnimation(.timingCurve(0.05, 0.7, 0.1, 1, duration: 0.1)) { backgroundAlpha = maxDynamicAlpha }
|
|
167
229
|
withAnimation(.timingCurve(0.05, 0.7, 0.1, 1, duration: 0.35)) { sheetOffset = 0 }
|
|
168
230
|
}
|
|
169
231
|
}
|
|
170
232
|
|
|
233
|
+
/// Mirrors Compose's `closeEvent`: slide out over 200ms, then fade the scrim
|
|
234
|
+
/// out over 100ms only after the slide finishes, then dismiss.
|
|
171
235
|
private func close() {
|
|
172
236
|
withAnimation(.timingCurve(0.3, 0, 0.8, 0.15, duration: 0.2)) {
|
|
173
237
|
sheetOffset = max(sheetHeight, screenHeight)
|
|
174
|
-
scrimAlpha = 0
|
|
175
238
|
}
|
|
176
239
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
|
240
|
+
withAnimation(.timingCurve(0.3, 0, 0.8, 0.15, duration: 0.1)) {
|
|
241
|
+
backgroundAlpha = 0
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
|
177
245
|
navigator.finishOverplayDismiss()
|
|
178
246
|
}
|
|
179
247
|
}
|
|
@@ -15,6 +15,7 @@ struct ModalScreen<Content: View>: View {
|
|
|
15
15
|
let content: () -> Content
|
|
16
16
|
|
|
17
17
|
@EnvironmentObject private var navigator: Navigator
|
|
18
|
+
@Environment(\.applicationEnvironment) private var appEnv
|
|
18
19
|
|
|
19
20
|
@State private var scrimOpacity: CGFloat = 0
|
|
20
21
|
@State private var contentScale: CGFloat = 0.8
|
|
@@ -38,6 +39,11 @@ struct ModalScreen<Content: View>: View {
|
|
|
38
39
|
.onAppear {
|
|
39
40
|
guard !didAppear else { return }
|
|
40
41
|
didAppear = true
|
|
42
|
+
ScreenTracker.trackPopupDisplayed(
|
|
43
|
+
maxApi: appEnv.maxApi,
|
|
44
|
+
context: appEnv.applicationContext,
|
|
45
|
+
parentScreenName: ScreenTracker.getLastScreenName()
|
|
46
|
+
)
|
|
41
47
|
navigator.registerOverplayDismiss { close() }
|
|
42
48
|
open()
|
|
43
49
|
}
|
|
@@ -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
|
+
}
|
|
@@ -237,7 +237,7 @@ struct Footer: View {
|
|
|
237
237
|
}
|
|
238
238
|
.padding(.vertical, Spacing.S)
|
|
239
239
|
.padding(.horizontal, Spacing.M)
|
|
240
|
-
.padding(.bottom, keyboardSize
|
|
240
|
+
.padding(.bottom, keyboardSize - bottomInset)
|
|
241
241
|
.background(Colors.black01)
|
|
242
242
|
.overlay(
|
|
243
243
|
Rectangle()
|