@momo-kits/native-kits 0.167.1-duo.5-debug → 0.167.1-duo.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +8 -3
- package/ios/Application/HeaderRight.swift +25 -12
- package/ios/Application/Layout/ScreenLayout.swift +196 -0
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +30 -9
- package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +5 -2
- package/ios/Application/Navigation/BottomTab/DuoTabBar.swift +275 -0
- package/ios/Application/Navigation/HeaderUser.swift +4 -1
- package/ios/Application/Navigation/NavigationContainer.swift +3 -0
- package/ios/Application/Navigation/Overplay/BottomSheet.swift +41 -4
- package/ios/Application/Navigation/Overplay/ModalScreen.swift +5 -1
- package/ios/Application/Navigation/component/DuoScrollGradient.swift +55 -0
- package/ios/Application/Navigation/component/Header.swift +9 -3
- package/ios/Application/Navigation/component/HeaderBackground.swift +3 -2
- package/ios/Application/Navigation/component/NavigationFooter.swift +1 -1
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +61 -9
- package/ios/Application/Navigation/component/SideBar.swift +155 -0
- package/ios/Application/StackScreen.swift +135 -18
- package/ios/Popup/PopupDisplay.swift +84 -68
- package/ios/Popup/PopupLayout.swift +45 -0
- package/ios/Popup/PopupNotify.swift +43 -32
- package/ios/Popup/PopupPromotion.swift +17 -5
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +76 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +170 -0
- package/package.json +1 -1
- package/build.gradle.kts +0 -11
- package/compose/build.gradle.kts +0 -190
- package/compose/build.gradle.kts.backup +0 -190
- package/compose/compose.podspec +0 -47
- package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -19
- package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -151
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -124
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -831
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -56
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -82
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -356
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -113
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -475
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -266
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -240
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -264
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -334
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -98
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -310
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -323
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -199
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -91
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -552
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -88
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -498
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -234
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -188
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -188
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -297
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -54
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -50
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +0 -157
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -41
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -299
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +0 -58
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -129
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -106
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -205
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -393
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -622
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -35
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -175
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -232
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -381
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -83
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -105
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -74
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -347
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -37
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -363
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +0 -169
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -132
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -127
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -71
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -118
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -40
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -32
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -213
- package/gradle/libs.versions.toml +0 -69
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +0 -8
- package/gradle.properties +0 -26
- package/gradlew +0 -252
- package/gradlew.bat +0 -94
- package/ios-demo/README.md +0 -120
- package/settings.gradle.kts +0 -64
|
@@ -123,6 +123,7 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
123
123
|
private func overplayLayer(presentedId: Int?) -> some View {
|
|
124
124
|
if let item = navigator.overplay, item.presentedId == presentedId {
|
|
125
125
|
OverplayHost(item: item)
|
|
126
|
+
.momoScreenLayout()
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
|
|
@@ -170,6 +171,8 @@ private struct ScreenHost: View {
|
|
|
170
171
|
screenName: screen.name,
|
|
171
172
|
content: { screen.content() }
|
|
172
173
|
)
|
|
174
|
+
// Per screen, not around the NavigationStack: a state-driven reader there stops path pushes rendering.
|
|
175
|
+
.momoScreenLayout()
|
|
173
176
|
}
|
|
174
177
|
}
|
|
175
178
|
|
|
@@ -45,6 +45,7 @@ struct BottomSheetView<Content: View>: View {
|
|
|
45
45
|
@EnvironmentObject private var navigator: Navigator
|
|
46
46
|
@Environment(\.appTheme) private var theme
|
|
47
47
|
@Environment(\.applicationEnvironment) private var appEnv
|
|
48
|
+
@Environment(\.screenLayout) private var layout
|
|
48
49
|
|
|
49
50
|
/// Vertical translation of the sheet; starts off-screen and animates to 0.
|
|
50
51
|
@State private var sheetOffset: CGFloat = UIScreen.main.bounds.height
|
|
@@ -66,7 +67,36 @@ struct BottomSheetView<Content: View>: View {
|
|
|
66
67
|
/// steady-state (fully open) scrim peaks at 0.6, matching ModalScreen's peak.
|
|
67
68
|
private let maxDynamicAlpha: CGFloat = 0.3
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
// UIScreen stays on the outer display on iPhone Duo, even when unfolded
|
|
71
|
+
private var screenHeight: CGFloat { isDuo ? layout.height : UIScreen.main.bounds.height }
|
|
72
|
+
|
|
73
|
+
private var isDuo: Bool { layout.isDuoDevice }
|
|
74
|
+
// iPhone Duo inner display: center the sheet instead of spanning the width
|
|
75
|
+
private var isDuoCentered: Bool { isDuo && layout.width >= 600 }
|
|
76
|
+
|
|
77
|
+
/// Keeps the portrait width in landscape, like system sheets.
|
|
78
|
+
private var duoCenteredWidth: CGFloat {
|
|
79
|
+
min(
|
|
80
|
+
min(layout.width, layout.height) - Spacing.L * 2,
|
|
81
|
+
layout.width - max(layout.insets.leading, layout.insets.trailing, Spacing.L) * 2
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Landscape side buttons span the full height, so the content is inset; in portrait the
|
|
86
|
+
/// side status bar only covers the top corner.
|
|
87
|
+
private var sideInsetLeading: CGFloat {
|
|
88
|
+
isDuo && !isDuoCentered && layout.width > layout.height ? layout.insets.leading : 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private var sideInsetTrailing: CGFloat {
|
|
92
|
+
isDuo && !isDuoCentered && layout.width > layout.height ? layout.insets.trailing : 0
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// The keyboard filler is part of the sheet, so capping the sheet shrinks the content by it.
|
|
96
|
+
private var duoMaxHeight: CGFloat? {
|
|
97
|
+
guard isDuo else { return nil }
|
|
98
|
+
return layout.height - max(layout.duoTopInset, Spacing.XL)
|
|
99
|
+
}
|
|
70
100
|
private var bottomInset: CGFloat { safeAreaBottomInset() }
|
|
71
101
|
/// While the keyboard is up, it already covers the home-indicator area, so it
|
|
72
102
|
/// replaces (rather than adds to) the usual bottom-inset filler.
|
|
@@ -110,7 +140,7 @@ struct BottomSheetView<Content: View>: View {
|
|
|
110
140
|
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)) { notification in
|
|
111
141
|
guard let endFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
|
|
112
142
|
withAnimation(.easeOut(duration: 0.25)) {
|
|
113
|
-
keyboardHeight =
|
|
143
|
+
keyboardHeight = screenHeight - endFrame.origin.y
|
|
114
144
|
}
|
|
115
145
|
}
|
|
116
146
|
.onAppear {
|
|
@@ -138,6 +168,8 @@ struct BottomSheetView<Content: View>: View {
|
|
|
138
168
|
headerView
|
|
139
169
|
.frame(height: 56)
|
|
140
170
|
.padding(.horizontal, Spacing.M)
|
|
171
|
+
.padding(.leading, sideInsetLeading)
|
|
172
|
+
.padding(.trailing, sideInsetTrailing)
|
|
141
173
|
}
|
|
142
174
|
.frame(maxWidth: .infinity)
|
|
143
175
|
.frame(height: 72)
|
|
@@ -149,12 +181,17 @@ struct BottomSheetView<Content: View>: View {
|
|
|
149
181
|
.frame(height: 1)
|
|
150
182
|
|
|
151
183
|
content()
|
|
184
|
+
.padding(.leading, sideInsetLeading)
|
|
185
|
+
.padding(.trailing, sideInsetTrailing)
|
|
186
|
+
// a tall sheet on iPhone Duo keeps its header and gives the content what is left
|
|
187
|
+
.layoutPriority(-1)
|
|
152
188
|
|
|
153
189
|
backgroundColor
|
|
154
190
|
.frame(height: effectiveBottomInset)
|
|
155
191
|
}
|
|
156
|
-
.frame(maxWidth: .infinity)
|
|
157
|
-
.frame(alignment: .bottom)
|
|
192
|
+
.frame(maxWidth: isDuoCentered ? duoCenteredWidth : .infinity)
|
|
193
|
+
.frame(maxHeight: duoMaxHeight, alignment: .bottom)
|
|
194
|
+
.clipped()
|
|
158
195
|
.background(
|
|
159
196
|
GeometryReader { geo in
|
|
160
197
|
Color.clear.onAppear { sheetHeight = geo.size.height }
|
|
@@ -16,6 +16,7 @@ struct ModalScreen<Content: View>: View {
|
|
|
16
16
|
|
|
17
17
|
@EnvironmentObject private var navigator: Navigator
|
|
18
18
|
@Environment(\.applicationEnvironment) private var appEnv
|
|
19
|
+
@Environment(\.screenLayout) private var layout
|
|
19
20
|
|
|
20
21
|
@State private var scrimOpacity: CGFloat = 0
|
|
21
22
|
@State private var contentScale: CGFloat = 0.8
|
|
@@ -35,8 +36,11 @@ struct ModalScreen<Content: View>: View {
|
|
|
35
36
|
content()
|
|
36
37
|
.scaleEffect(contentScale)
|
|
37
38
|
.opacity(contentOpacity)
|
|
39
|
+
// iPhone Duo: center below the side status bar and above the home indicator
|
|
40
|
+
.padding(.top, layout.isDuoDevice ? layout.duoTopInset : 0)
|
|
41
|
+
.padding(.bottom, layout.isDuoDevice ? layout.insets.bottom : 0)
|
|
38
42
|
}
|
|
39
|
-
//
|
|
43
|
+
// Full screen like RN's Modal, so asymmetric landscape insets don't shift the content sideways.
|
|
40
44
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
41
45
|
.ignoresSafeArea(.container)
|
|
42
46
|
.onAppear {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DuoScrollGradient.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// Port of RN `DuoScrollGradient` (Components/DuoGlassBackground.tsx): with the iPhone Duo
|
|
6
|
+
// glass header the content scrolls under the bar, so the header gradient is drawn under it.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
// extended headers run their gradient this far below the header, as off Duo
|
|
12
|
+
private let extendedGradientExtra: CGFloat = 52
|
|
13
|
+
// scroll distance over which the extended gradient collapses to the header
|
|
14
|
+
private let extendedCollapseDistance: CGFloat = 100
|
|
15
|
+
|
|
16
|
+
struct DuoScrollGradient: View {
|
|
17
|
+
let scrollOffset: CGFloat
|
|
18
|
+
let headerHeight: CGFloat
|
|
19
|
+
let extended: Bool
|
|
20
|
+
|
|
21
|
+
@Environment(\.appTheme) private var theme
|
|
22
|
+
@Environment(\.screenLayout) private var layout
|
|
23
|
+
|
|
24
|
+
private var scroll: CGFloat { max(scrollOffset, 0) }
|
|
25
|
+
|
|
26
|
+
private var gradient: LinearGradient {
|
|
27
|
+
LinearGradient(
|
|
28
|
+
colors: [theme.colors.gradient, theme.colors.gradient.opacity(0)],
|
|
29
|
+
startPoint: .top,
|
|
30
|
+
endPoint: .bottom
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var body: some View {
|
|
35
|
+
Group {
|
|
36
|
+
if extended {
|
|
37
|
+
// extended squeezes the whole fade into its box and fades out
|
|
38
|
+
let progress = min(scroll / extendedCollapseDistance, 1)
|
|
39
|
+
gradient
|
|
40
|
+
.frame(height: headerHeight + extendedGradientExtra * (1 - progress))
|
|
41
|
+
.opacity(1 - min(scroll / extendedGradientExtra, 1))
|
|
42
|
+
} else {
|
|
43
|
+
// default keeps the off-Duo 375:154 shape, the header clips it, and scrolls away
|
|
44
|
+
gradient
|
|
45
|
+
.frame(height: layout.width * 154 / 375)
|
|
46
|
+
.frame(height: headerHeight, alignment: .top)
|
|
47
|
+
.clipped()
|
|
48
|
+
.offset(y: -scroll)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
|
52
|
+
.ignoresSafeArea()
|
|
53
|
+
.allowsHitTesting(false)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -60,7 +60,11 @@ public struct NavigationHeader: View {
|
|
|
60
60
|
self.titleOpacity = titleOpacity
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
@Environment(\.screenLayout) private var layout
|
|
64
|
+
|
|
63
65
|
private var isAnimated: Bool { animatedHeader != nil }
|
|
66
|
+
/// iPhone Duo side bar holds back and the toolkit; a custom header right stays in the bar.
|
|
67
|
+
private var useSideBar: Bool { layout.sideBar != nil }
|
|
64
68
|
private var isSurface: Bool { animatedHeader?.isSurface ?? true }
|
|
65
69
|
|
|
66
70
|
private var headerColor: HeaderColor {
|
|
@@ -79,7 +83,7 @@ public struct NavigationHeader: View {
|
|
|
79
83
|
if headerType != .none {
|
|
80
84
|
ZStack {
|
|
81
85
|
HStack(spacing: 0) {
|
|
82
|
-
if let goBack {
|
|
86
|
+
if let goBack, !useSideBar {
|
|
83
87
|
BackButton(color: headerColor, onBack: goBack)
|
|
84
88
|
Spacer().frame(width: Spacing.S)
|
|
85
89
|
}
|
|
@@ -91,7 +95,7 @@ public struct NavigationHeader: View {
|
|
|
91
95
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
if let headerRight {
|
|
98
|
+
if let headerRight, !(useSideBar && toolkitHeaderRight(headerRight) != nil) {
|
|
95
99
|
AnyView(headerRight())
|
|
96
100
|
.background(
|
|
97
101
|
GeometryReader { geo in
|
|
@@ -107,8 +111,10 @@ public struct NavigationHeader: View {
|
|
|
107
111
|
.padding(.horizontal, Spacing.M)
|
|
108
112
|
}
|
|
109
113
|
}
|
|
110
|
-
.frame(height:
|
|
114
|
+
.frame(height: layout.headerBarHeight)
|
|
111
115
|
.overlay(alignment: .bottom) { VerticalShadow(opacity: opacity) }
|
|
116
|
+
// status bar off the top edge: the row spans the whole header from the window top
|
|
117
|
+
.padding(.top, min(layout.headerStatusBarHeight - layout.insets.top, 0))
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
|
|
@@ -43,10 +43,11 @@ public struct NavigationHeaderBackground: View {
|
|
|
43
43
|
)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
@Environment(\.screenLayout) private var layout
|
|
47
|
+
|
|
46
48
|
public var body: some View {
|
|
47
|
-
let statusBarHeight = safeAreaTopInset()
|
|
48
49
|
let opacity = max(0, 1 - scrollState / CGFloat(HEADER_HEIGHT))
|
|
49
|
-
let minHeight = fullScreenContent ? 0 :
|
|
50
|
+
let minHeight = fullScreenContent ? 0 : layout.headerHeight
|
|
50
51
|
let maxHeight: CGFloat = 154
|
|
51
52
|
let backgroundColor = headerTransparent ? Color.clear : Colors.black01
|
|
52
53
|
let topColor = opacity == 0 ? backgroundColor : Color(hex: 0xFDCADE)
|
|
@@ -69,7 +69,7 @@ public struct NavigationFooter: View {
|
|
|
69
69
|
.padding(.bottom, Self.bottomPadding)
|
|
70
70
|
// Compose paints the surface all the way to the bottom of the screen; the
|
|
71
71
|
// footer's own frame stops at the safe area, so only the fill bleeds down.
|
|
72
|
-
.background(theme.colors.background.surface.ignoresSafeArea(.container, edges: .bottom))
|
|
72
|
+
.background(theme.colors.background.surface.ignoresSafeArea(.container, edges: [.bottom, .horizontal]))
|
|
73
73
|
.overlay(alignment: .top) { shadow }
|
|
74
74
|
.offset(y: Self.verticalOffset(keyboardSize: keyboardSize))
|
|
75
75
|
}
|
|
@@ -26,6 +26,8 @@ public struct NavigationHeaderRight: View {
|
|
|
26
26
|
struct ToolkitNavigationHeaderRight: View {
|
|
27
27
|
var headerRight: HeaderRightData?
|
|
28
28
|
var tintColor: Color?
|
|
29
|
+
/// iPhone Duo side bar column instead of the bar row.
|
|
30
|
+
var vertical: Bool = false
|
|
29
31
|
@State private var isFavorite: Bool = false
|
|
30
32
|
@State private var isLoading: Bool = false
|
|
31
33
|
@Environment(\.applicationEnvironment) private var environment
|
|
@@ -142,15 +144,68 @@ struct ToolkitNavigationHeaderRight: View {
|
|
|
142
144
|
return NavigationButtonConfig(icon: icon, onPress: onClickHandler)
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
private var showHelpCenter: Bool {
|
|
148
|
+
environment.applicationContext != nil && showsHelpCenter(environment.applicationContext)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private var closeIcon: String { headerRight?.closeIcon ?? "16_basic_home" }
|
|
152
|
+
|
|
145
153
|
var body: some View {
|
|
154
|
+
Group {
|
|
155
|
+
if vertical {
|
|
156
|
+
verticalBody
|
|
157
|
+
} else {
|
|
158
|
+
horizontalBody
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
.onAppear {
|
|
162
|
+
loadFavoriteState()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private var showBadge: Bool {
|
|
167
|
+
headerRight?.tools.contains { group in
|
|
168
|
+
group.items.contains { $0.showBadge }
|
|
169
|
+
} ?? false
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private var verticalBody: some View {
|
|
173
|
+
let navButtonConfig = getNavigationButtonConfig()
|
|
174
|
+
return VStack(spacing: Spacing.M) {
|
|
175
|
+
if shouldShowShortcut {
|
|
176
|
+
SideBarButton(
|
|
177
|
+
icon: navButtonConfig.icon,
|
|
178
|
+
tintColor: tintColor,
|
|
179
|
+
showBadge: showBadge,
|
|
180
|
+
disabled: isLoading,
|
|
181
|
+
action: navButtonConfig.onPress
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
SideBarGroup(tintColor: tintColor, itemCount: showHelpCenter ? 2 : 1) {
|
|
185
|
+
if showHelpCenter {
|
|
186
|
+
SideBarGroupItem(
|
|
187
|
+
icon: "help_center",
|
|
188
|
+
tintColor: tintColor,
|
|
189
|
+
accessibilityId: "btn_navigation_help_center",
|
|
190
|
+
action: onPressHelpCenter
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
SideBarGroupItem(
|
|
194
|
+
icon: closeIcon,
|
|
195
|
+
tintColor: tintColor,
|
|
196
|
+
accessibilityId: "btn_navigation_close",
|
|
197
|
+
action: onPressClose
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private var horizontalBody: some View {
|
|
146
204
|
let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
|
|
147
205
|
let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
|
|
148
206
|
let navButtonConfig = getNavigationButtonConfig()
|
|
149
|
-
let showBadge = headerRight?.tools.contains { group in
|
|
150
|
-
group.items.contains { $0.showBadge }
|
|
151
|
-
} ?? false
|
|
152
207
|
|
|
153
|
-
HStack(alignment: .center, spacing: 0) {
|
|
208
|
+
return HStack(alignment: .center, spacing: 0) {
|
|
154
209
|
if shouldShowShortcut {
|
|
155
210
|
NavigationButton(
|
|
156
211
|
disabled: isLoading,
|
|
@@ -162,7 +217,7 @@ struct ToolkitNavigationHeaderRight: View {
|
|
|
162
217
|
}
|
|
163
218
|
|
|
164
219
|
HStack(alignment: .center, spacing: 0) {
|
|
165
|
-
if
|
|
220
|
+
if showHelpCenter {
|
|
166
221
|
Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
|
|
167
222
|
.padding(4)
|
|
168
223
|
.onTapGesture {
|
|
@@ -174,7 +229,7 @@ struct ToolkitNavigationHeaderRight: View {
|
|
|
174
229
|
.frame(width: 0.5, height: 12)
|
|
175
230
|
}
|
|
176
231
|
|
|
177
|
-
Icon(source:
|
|
232
|
+
Icon(source: closeIcon, size: 20, color: tintColor ?? Colors.black17)
|
|
178
233
|
.padding(4)
|
|
179
234
|
.onTapGesture {
|
|
180
235
|
onPressClose()
|
|
@@ -189,8 +244,5 @@ struct ToolkitNavigationHeaderRight: View {
|
|
|
189
244
|
)
|
|
190
245
|
.padding(.leading, Spacing.S)
|
|
191
246
|
}
|
|
192
|
-
.onAppear {
|
|
193
|
-
loadFavoriteState()
|
|
194
|
-
}
|
|
195
247
|
}
|
|
196
248
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SideBar.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// iPhone Duo: back and toolkit leave the header bar for a column in the side safe area,
|
|
6
|
+
// as the system bars do. Port of RN `SideBarButton.tsx` + `useSideBarItemStyle`.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
private let sideBarIconSize: CGFloat = 24
|
|
12
|
+
|
|
13
|
+
/// Toolkit data when `headerRight` renders the kit toolkit; nil for a custom header right.
|
|
14
|
+
func toolkitHeaderRight(_ headerRight: (() -> any View)?) -> (data: HeaderRightData?, tintColor: Color?)? {
|
|
15
|
+
guard let view = headerRight?() else { return nil }
|
|
16
|
+
if let toolkit = view as? HeaderRight { return (toolkit.headerRight, toolkit.tintColor) }
|
|
17
|
+
if let toolkit = view as? NavigationHeaderRight { return (toolkit.headerRight, toolkit.tintColor) }
|
|
18
|
+
return nil
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// The back button and toolkit column, placed in window coordinates over the side strip.
|
|
22
|
+
struct HeaderSideBar: View {
|
|
23
|
+
let sideBar: SideBarLayout
|
|
24
|
+
let goBack: (() -> Void)?
|
|
25
|
+
let toolkit: (data: HeaderRightData?, tintColor: Color?)?
|
|
26
|
+
let headerColor: HeaderColor
|
|
27
|
+
/// What footer / tab bar / home indicator take from the bottom; the column stays above it.
|
|
28
|
+
let bottomInset: CGFloat
|
|
29
|
+
|
|
30
|
+
@Environment(\.screenLayout) private var layout
|
|
31
|
+
@State private var columnHeight: CGFloat = 0
|
|
32
|
+
|
|
33
|
+
private var isRight: Bool { sideBar.edge == .right }
|
|
34
|
+
|
|
35
|
+
private var top: CGFloat {
|
|
36
|
+
let maxY = layout.height - bottomInset - Spacing.XL - columnHeight
|
|
37
|
+
return max(min(sideBar.top, maxY), 0)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
var body: some View {
|
|
41
|
+
VStack(alignment: isRight ? .leading : .trailing, spacing: Spacing.M) {
|
|
42
|
+
if let goBack {
|
|
43
|
+
SideBarButton(
|
|
44
|
+
icon: "arrow-back",
|
|
45
|
+
tintColor: headerColor.tintIconColor,
|
|
46
|
+
accessibilityId: "btn_navigation_back",
|
|
47
|
+
action: goBack
|
|
48
|
+
)
|
|
49
|
+
} else {
|
|
50
|
+
// keep the slot so the toolkit does not jump between root and pushed screens
|
|
51
|
+
Color.clear.frame(width: DuoMetrics.controlSize, height: DuoMetrics.controlSize)
|
|
52
|
+
}
|
|
53
|
+
if let toolkit {
|
|
54
|
+
ToolkitNavigationHeaderRight(
|
|
55
|
+
headerRight: toolkit.data,
|
|
56
|
+
tintColor: toolkit.tintColor ?? headerColor.tintIconColor,
|
|
57
|
+
vertical: true
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
.padding(isRight ? .leading : .trailing, Spacing.M)
|
|
62
|
+
.frame(width: sideBar.width, alignment: isRight ? .leading : .trailing)
|
|
63
|
+
.background(
|
|
64
|
+
GeometryReader { proxy in
|
|
65
|
+
Color.clear
|
|
66
|
+
.onAppear { columnHeight = proxy.size.height }
|
|
67
|
+
.onChange(of: proxy.size.height) { columnHeight = $0 }
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
.padding(.top, top)
|
|
71
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: isRight ? .topTrailing : .topLeading)
|
|
72
|
+
.ignoresSafeArea()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Dark glass under light icons (banner headers), light glass otherwise.
|
|
77
|
+
private struct SideBarGlass<S: Shape>: ViewModifier {
|
|
78
|
+
let tintColor: Color?
|
|
79
|
+
let shape: S
|
|
80
|
+
|
|
81
|
+
private var isDark: Bool { tintColor == Colors.black01 || tintColor == .white }
|
|
82
|
+
|
|
83
|
+
func body(content: Content) -> some View {
|
|
84
|
+
if isDark {
|
|
85
|
+
content
|
|
86
|
+
.background(shape.fill(Colors.black20.opacity(0.5)))
|
|
87
|
+
.overlay(shape.stroke(Colors.black01.opacity(0.2), lineWidth: 0.5))
|
|
88
|
+
} else {
|
|
89
|
+
content
|
|
90
|
+
.background(
|
|
91
|
+
shape.fill(Colors.black01.opacity(0.85))
|
|
92
|
+
.shadow(color: Color.black.opacity(0.12), radius: 4, x: 0, y: 2)
|
|
93
|
+
)
|
|
94
|
+
.overlay(shape.stroke(Colors.black20.opacity(0.08), lineWidth: 0.5))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// 48pt glass circle for a single side bar control.
|
|
100
|
+
struct SideBarButton: View {
|
|
101
|
+
let icon: String
|
|
102
|
+
var tintColor: Color?
|
|
103
|
+
var showBadge: Bool = false
|
|
104
|
+
var disabled: Bool = false
|
|
105
|
+
var accessibilityId: String?
|
|
106
|
+
let action: () -> Void
|
|
107
|
+
|
|
108
|
+
var body: some View {
|
|
109
|
+
SwiftUI.Button(action: { if !disabled { action() } }) {
|
|
110
|
+
Icon(source: icon, size: sideBarIconSize, color: tintColor ?? Colors.black17)
|
|
111
|
+
.frame(width: DuoMetrics.controlSize, height: DuoMetrics.controlSize)
|
|
112
|
+
.modifier(SideBarGlass(tintColor: tintColor, shape: Circle()))
|
|
113
|
+
.overlay(alignment: .topTrailing) {
|
|
114
|
+
if showBadge {
|
|
115
|
+
BadgeDot(size: .small).offset(x: -Spacing.XXS, y: Spacing.XXS)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
.buttonStyle(.plain)
|
|
120
|
+
.accessibilityIdentifier(accessibilityId ?? "")
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// 48pt wide glass capsule grouping related controls, one item per 50pt.
|
|
125
|
+
struct SideBarGroup<Content: View>: View {
|
|
126
|
+
var tintColor: Color?
|
|
127
|
+
let itemCount: Int
|
|
128
|
+
@ViewBuilder let content: () -> Content
|
|
129
|
+
|
|
130
|
+
var body: some View {
|
|
131
|
+
VStack(spacing: 0) { content() }
|
|
132
|
+
.frame(
|
|
133
|
+
width: DuoMetrics.controlSize,
|
|
134
|
+
height: itemCount > 1 ? DuoMetrics.itemPitch * CGFloat(itemCount) : DuoMetrics.controlSize
|
|
135
|
+
)
|
|
136
|
+
.modifier(SideBarGlass(tintColor: tintColor, shape: RoundedRectangle(cornerRadius: Radius.XL)))
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
struct SideBarGroupItem: View {
|
|
141
|
+
let icon: String
|
|
142
|
+
var tintColor: Color?
|
|
143
|
+
let accessibilityId: String
|
|
144
|
+
let action: () -> Void
|
|
145
|
+
|
|
146
|
+
var body: some View {
|
|
147
|
+
SwiftUI.Button(action: action) {
|
|
148
|
+
Icon(source: icon, size: sideBarIconSize, color: tintColor ?? Colors.black17)
|
|
149
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
150
|
+
.contentShape(Rectangle())
|
|
151
|
+
}
|
|
152
|
+
.buttonStyle(.plain)
|
|
153
|
+
.accessibilityIdentifier(accessibilityId)
|
|
154
|
+
}
|
|
155
|
+
}
|