@momo-kits/native-kits 0.162.44 → 0.162.45
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 +45 -3
- package/README.md +2 -2
- package/ios/Application/ApplicationEnvironment.swift +17 -0
- package/ios/Application/MaxApi.swift +30 -3
- package/ios/Avatar/Avatar.swift +4 -3
- package/ios/Badge/Badge.swift +6 -4
- package/ios/Badge/BadgeDot.swift +1 -1
- package/ios/Badge/BadgeRibbon.swift +0 -1
- package/ios/Button/Button.swift +15 -12
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
- package/ios/Carousel/Carousel.swift +29 -12
- package/ios/Checkbox/Checkbox.swift +7 -3
- package/ios/Chip/Chip.swift +5 -4
- package/ios/Collapse/Collapse.swift +70 -19
- package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
- package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
- package/ios/DateTimePicker/DateTimePicker.swift +54 -20
- package/ios/DateTimePicker/WheelPicker.swift +103 -105
- package/ios/Divider/Divider.swift +37 -4
- package/ios/Extensions/Color++.swift +38 -9
- package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
- package/ios/Extensions/SkeletonUICompat.swift +11 -0
- package/ios/Icon/Icon.swift +21 -23
- package/ios/Icon/Icons.swift +1329 -0
- package/ios/IconButton/IconButton.swift +15 -7
- package/ios/Image/Image.swift +7 -23
- package/ios/Information/Information.swift +27 -9
- package/ios/Input/ErrorView.swift +2 -2
- package/ios/Input/Input.swift +160 -147
- package/ios/Input/InputPhoneNumber.swift +12 -9
- package/ios/Input/InputSearch.swift +107 -77
- package/ios/Input/InputTextArea.swift +128 -145
- package/ios/InputDropDown/InputDropDown.swift +10 -7
- package/ios/InputMoney/InputMoney.swift +33 -11
- package/ios/InputOTP/InputOTP.swift +22 -12
- package/ios/Layout/Card.swift +50 -0
- package/ios/Layout/GridContext.swift +187 -3
- package/ios/Layout/Item.swift +5 -4
- package/ios/Layout/ScreenLayout.swift +385 -0
- package/ios/Layout/Section.swift +4 -100
- package/ios/Loader/Loader.swift +5 -9
- package/ios/Lottie/LottieView.swift +14 -2
- package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
- package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
- package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
- package/ios/Navigation/BottomTab/DuoBarCompression.swift +104 -0
- package/ios/Navigation/BottomTab/DuoTabBar.swift +329 -0
- package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
- package/ios/Navigation/Component/Header.swift +545 -0
- package/ios/Navigation/Component/HeaderBackground.swift +154 -0
- package/ios/Navigation/Component/HeaderJourney.swift +73 -0
- package/ios/Navigation/Component/HeaderLocation.swift +48 -0
- package/ios/Navigation/Component/HeaderRight.swift +425 -0
- package/ios/Navigation/Component/HeaderTitle.swift +36 -0
- package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
- package/ios/Navigation/Component/SideBar.swift +183 -0
- package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
- package/ios/Navigation/HostBackGestureBridge.swift +78 -0
- package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
- package/ios/Navigation/Navigation.swift +204 -0
- package/ios/Navigation/NavigationContainer.swift +195 -0
- package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
- package/ios/Navigation/StackScreen.swift +930 -0
- package/ios/Navigation/StackTransition.swift +400 -0
- package/ios/Navigation/SwipeBackHaptic.swift +33 -0
- package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
- package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
- package/ios/Pagination/PaginationScroll.swift +2 -2
- package/ios/Popup/PopupDisplay.swift +72 -135
- package/ios/Popup/PopupInput.swift +10 -13
- package/ios/Popup/PopupLayout.swift +140 -0
- package/ios/Popup/PopupNotify.swift +59 -69
- package/ios/Popup/PopupPromotion.swift +31 -47
- package/ios/PopupView/FullscreenPopup.swift +13 -19
- package/ios/PopupView/PopupView.swift +1 -1
- package/ios/PopupView/Utils++.swift +0 -8
- package/ios/ProgressInfo/ProgressInfo.swift +4 -2
- package/ios/Radio/Radio.swift +2 -1
- package/ios/Rating/Rating.swift +9 -1
- package/ios/Resources/dot_loading.json +558 -0
- package/ios/Resources/lottie_circle_loader.json +1 -0
- package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
- package/ios/Skeleton/Skeleton.swift +22 -27
- package/ios/Slider/Slider.swift +16 -18
- package/ios/Stepper/Stepper.swift +6 -1
- package/ios/Steps/Steps.swift +8 -6
- package/ios/SuggestAction/SuggestAction.swift +5 -0
- package/ios/Swipeable/SwipeCell.swift +10 -6
- package/ios/Switch/Switch.swift +22 -20
- package/ios/TabView/TabView.swift +102 -21
- package/ios/Tag/Tag.swift +15 -4
- package/ios/Template/Logo/Logo.swift +2 -1
- package/ios/Template/TrustBanner/TrustBanner.swift +106 -54
- package/ios/Title/Title.swift +6 -17
- package/ios/Tooltip/Tooltip.swift +29 -14
- package/ios/Typography/FontScale.swift +3 -3
- package/ios/Typography/Text.swift +81 -19
- package/ios/Uploader/Uploader.swift +3 -1
- package/ios/native-kits.podspec +2 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
- package/package.json +1 -1
- package/scripts/gen-ios-icons.mjs +36 -0
- package/ios/Application/Components.swift +0 -270
- package/ios/Application/ComposeApi.swift +0 -22
- package/ios/Application/HeaderRight.swift +0 -306
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
- package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
- package/ios/Application/Navigation/HeaderTitle.swift +0 -57
- package/ios/Application/Navigation/NavigationContainer.swift +0 -213
- package/ios/Application/Navigation/NavigationOptions.swift +0 -109
- package/ios/Application/Navigation/NavigatorManager.swift +0 -22
- package/ios/Application/Navigation/component/Header.swift +0 -178
- package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
- package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
- package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
- package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
- package/ios/Application/Screen.swift +0 -250
- package/ios/Application/StackScreen.swift +0 -492
- /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// NavigationFooter.swift
|
|
3
|
-
// MoMoUIKits
|
|
4
|
-
//
|
|
5
|
-
// SwiftUI mirror of Compose's `Footer` (navigation/StackScreen.kt): the band
|
|
6
|
-
// pinned under the screen content — surface background, S/M padding, the
|
|
7
|
-
// navigation-bar reserve, and the 6pt gradient shadow cast up onto the content.
|
|
8
|
-
//
|
|
9
|
-
// Named `NavigationFooter` to coexist with the legacy `Footer` in `Screen.swift`
|
|
10
|
-
// (kept as-is for the old `Screen` path). Used by `StackScreen`.
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
import SwiftUI
|
|
14
|
-
|
|
15
|
-
public struct NavigationFooter: View {
|
|
16
|
-
/// Compose's `footerComponent`: nothing renders when nil.
|
|
17
|
-
var footerComponent: (() -> AnyView)?
|
|
18
|
-
/// Current keyboard height, measured from the bottom of the screen.
|
|
19
|
-
var keyboardSize: CGFloat
|
|
20
|
-
|
|
21
|
-
public init(
|
|
22
|
-
footerComponent: (() -> AnyView)?,
|
|
23
|
-
keyboardSize: CGFloat = 0
|
|
24
|
-
) {
|
|
25
|
-
self.footerComponent = footerComponent
|
|
26
|
-
self.keyboardSize = keyboardSize
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@Environment(\.appTheme) private var theme
|
|
30
|
-
|
|
31
|
-
/// Read from the window, not from an enclosing `GeometryProxy`: the proxy's bottom
|
|
32
|
-
/// inset grows to the keyboard height while the keyboard is up.
|
|
33
|
-
static var safeAreaBottom: CGFloat {
|
|
34
|
-
UIApplication.shared.connectedScenes
|
|
35
|
-
.compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
|
|
36
|
-
.first ?? 0
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/// Compose's `AppNavigationBar.current` (`getNavigationBarHeight` clamps iOS to 21pt).
|
|
40
|
-
static var navigationBarInset: CGFloat { min(safeAreaBottom, 21) }
|
|
41
|
-
|
|
42
|
-
/// Compose's `bottomPadding + Spacing.S` with the keyboard down. The footer is laid
|
|
43
|
-
/// out inside the safe area, which already covers part of that gap.
|
|
44
|
-
static var bottomPadding: CGFloat {
|
|
45
|
-
max(navigationBarInset + Spacing.S - safeAreaBottom, 0)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/// Deliberately an offset and not padding: growing the footer feeds back into SwiftUI's
|
|
49
|
-
/// keyboard avoidance, so a padding-based lift never settles at the right place.
|
|
50
|
-
/// Negative lifts the band over the keyboard; positive shaves the few points by which
|
|
51
|
-
/// the safe area over-reserves compared to Compose. `FloatingContent` rides the same
|
|
52
|
-
/// amount so the FAB keeps its distance above the band.
|
|
53
|
-
///
|
|
54
|
-
/// Natural position is `safeAreaBottom + bottomPadding` above the bottom of the screen;
|
|
55
|
-
/// Compose puts it at `(AppNavigationBar - keyboard).coerceAtLeast(0) + Spacing.S` on
|
|
56
|
-
/// top of the `imePadding()` lift — i.e. `max(navigationBar, keyboard) + Spacing.S`.
|
|
57
|
-
static func verticalOffset(keyboardSize: CGFloat) -> CGFloat {
|
|
58
|
-
(safeAreaBottom + bottomPadding) - (max(navigationBarInset, keyboardSize) + Spacing.S)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
public var body: some View {
|
|
62
|
-
if let footerComponent = footerComponent {
|
|
63
|
-
VStack(alignment: .leading, spacing: 0) {
|
|
64
|
-
footerComponent()
|
|
65
|
-
}
|
|
66
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
67
|
-
.padding(.top, Spacing.S)
|
|
68
|
-
.padding(.horizontal, Spacing.M)
|
|
69
|
-
.padding(.bottom, Self.bottomPadding)
|
|
70
|
-
// Compose paints the surface all the way to the bottom of the screen; the
|
|
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))
|
|
73
|
-
.overlay(alignment: .top) { shadow }
|
|
74
|
-
.offset(y: Self.verticalOffset(keyboardSize: keyboardSize))
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/// Compose offsets the shadow box by -6dp, so it lands on the content above the
|
|
79
|
-
/// footer rather than on the footer surface itself.
|
|
80
|
-
private var shadow: some View {
|
|
81
|
-
LinearGradient(
|
|
82
|
-
colors: [Color.clear, Color.black.opacity(0.05)],
|
|
83
|
-
startPoint: .top,
|
|
84
|
-
endPoint: .bottom
|
|
85
|
-
)
|
|
86
|
-
.frame(height: 6)
|
|
87
|
-
.offset(y: -6)
|
|
88
|
-
.allowsHitTesting(false)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import SwiftUI
|
|
2
|
-
import Foundation
|
|
3
|
-
|
|
4
|
-
/// Keyed (`@Environment(\.applicationEnvironment)`) variant of `HeaderRight`, for hosts
|
|
5
|
-
/// wired through `NavigationContainer`/`KitContainer` (the injection style introduced by
|
|
6
|
-
/// MR 227 / DS-637). Reuses the shared data types (`HeaderRightData`, `Tool`, `ToolGroup`)
|
|
7
|
-
/// and `NavigationButton` from `HeaderRight.swift`. The legacy `HeaderRight` there stays on
|
|
8
|
-
/// `@EnvironmentObject` for hosts that render `Screen` directly with
|
|
9
|
-
/// `.environmentObject(ApplicationEnvironment(...))`. Opt in per screen with
|
|
10
|
-
/// `headerRight: { NavigationHeaderRight() }`.
|
|
11
|
-
public struct NavigationHeaderRight: View {
|
|
12
|
-
public init(headerRight: HeaderRightData? = nil, tintColor: Color? = nil) {
|
|
13
|
-
self.headerRight = headerRight
|
|
14
|
-
self.tintColor = tintColor
|
|
15
|
-
}
|
|
16
|
-
var headerRight: HeaderRightData?
|
|
17
|
-
var tintColor: Color? = nil
|
|
18
|
-
|
|
19
|
-
public var body: some View {
|
|
20
|
-
HStack(alignment: .center) {
|
|
21
|
-
ToolkitNavigationHeaderRight(headerRight: headerRight, tintColor: tintColor)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
struct ToolkitNavigationHeaderRight: View {
|
|
27
|
-
var headerRight: HeaderRightData?
|
|
28
|
-
var tintColor: Color?
|
|
29
|
-
@State private var isFavorite: Bool = false
|
|
30
|
-
@State private var isLoading: Bool = false
|
|
31
|
-
@Environment(\.applicationEnvironment) private var environment
|
|
32
|
-
|
|
33
|
-
private func apiValue(_ value: Any?) -> Any {
|
|
34
|
-
value ?? NSNull()
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
private var contextMap: [String: Any] {
|
|
38
|
-
let context = environment.applicationContext
|
|
39
|
-
return [
|
|
40
|
-
"appId": apiValue(context?.appId),
|
|
41
|
-
"code": apiValue(context?.appCode),
|
|
42
|
-
"name": apiValue(context?.appName),
|
|
43
|
-
"icon": apiValue(context?.appIcon),
|
|
44
|
-
"description": apiValue(context?.description),
|
|
45
|
-
"support": apiValue(context?.support),
|
|
46
|
-
"toolkitConfig": apiValue(context?.toolkitConfig),
|
|
47
|
-
"providerId": apiValue(context?.providerId),
|
|
48
|
-
"permissions": apiValue(context?.permissions)
|
|
49
|
-
]
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private var shouldShowShortcut: Bool {
|
|
53
|
-
headerRight?.useShortcut == true && !(headerRight?.useMore == true && environment.applicationContext == nil)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
private func parseResponse(_ response: String) -> Any? {
|
|
57
|
-
guard let jsonData = response.data(using: .utf8),
|
|
58
|
-
let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
|
|
59
|
-
return nil
|
|
60
|
-
}
|
|
61
|
-
return json["response"]
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// MARK: - Actions
|
|
65
|
-
private func loadFavoriteState() {
|
|
66
|
-
environment.composeApi?.request(
|
|
67
|
-
funcName: "isFavoriteApp",
|
|
68
|
-
params: ["code": apiValue(environment.applicationContext?.appCode)]
|
|
69
|
-
) { response in
|
|
70
|
-
if let isFavorite = parseResponse(response) as? Bool {
|
|
71
|
-
self.isFavorite = isFavorite
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private func onPressShortcut() {
|
|
77
|
-
environment.composeApi?.request(
|
|
78
|
-
funcName: "onToolAction",
|
|
79
|
-
params: [
|
|
80
|
-
"item": ["key": "onFavorite"],
|
|
81
|
-
"context": contextMap
|
|
82
|
-
]
|
|
83
|
-
) { response in
|
|
84
|
-
if let result = parseResponse(response) as? [String: Any],
|
|
85
|
-
result["success"] as? Bool == true {
|
|
86
|
-
isFavorite.toggle()
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private func onPressHelpCenter() {
|
|
92
|
-
environment.composeApi?.request(
|
|
93
|
-
funcName: "showHelpCenter",
|
|
94
|
-
params: [
|
|
95
|
-
"appId": apiValue(environment.applicationContext?.appId),
|
|
96
|
-
"code": apiValue(environment.applicationContext?.appCode),
|
|
97
|
-
"name": apiValue(environment.applicationContext?.appName),
|
|
98
|
-
"icon": apiValue(environment.applicationContext?.appIcon),
|
|
99
|
-
"description": apiValue(environment.applicationContext?.description)
|
|
100
|
-
]
|
|
101
|
-
) { _ in }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private func onPressClose() {
|
|
105
|
-
environment.composeApi?.request(
|
|
106
|
-
funcName: "dismissAll",
|
|
107
|
-
params: nil
|
|
108
|
-
) { _ in }
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private func onPressMore() {
|
|
112
|
-
let params: [String: Any] = [
|
|
113
|
-
"useSystemTools": headerRight?.useSystemTools ?? true,
|
|
114
|
-
"tools": headerRight?.tools.map { $0.toMap() } ?? [],
|
|
115
|
-
"context": contextMap
|
|
116
|
-
]
|
|
117
|
-
environment.composeApi?.request(
|
|
118
|
-
funcName: "showTools",
|
|
119
|
-
params: params
|
|
120
|
-
) { response in
|
|
121
|
-
if let toolResponse = parseResponse(response) as? String {
|
|
122
|
-
headerRight?.toolCallback?(toolResponse)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
private func getNavigationButtonConfig() -> NavigationButtonConfig {
|
|
128
|
-
let totalTools = headerRight?.tools.reduce(0) { $0 + $1.items.count } ?? 0
|
|
129
|
-
var icon = isFavorite ? "pin_star_checked" : "pin_star"
|
|
130
|
-
var onClickHandler: () -> Void = onPressShortcut
|
|
131
|
-
|
|
132
|
-
if totalTools > 1 || headerRight?.useMore == true {
|
|
133
|
-
icon = "navigation_more_icon"
|
|
134
|
-
onClickHandler = onPressMore
|
|
135
|
-
} else if totalTools == 1, let singleTool = headerRight?.tools.first?.items.first {
|
|
136
|
-
icon = singleTool.icon
|
|
137
|
-
onClickHandler = {
|
|
138
|
-
headerRight?.toolCallback?(singleTool.key)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return NavigationButtonConfig(icon: icon, onPress: onClickHandler)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
var body: some View {
|
|
146
|
-
let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
|
|
147
|
-
let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
|
|
148
|
-
let navButtonConfig = getNavigationButtonConfig()
|
|
149
|
-
let showBadge = headerRight?.tools.contains { group in
|
|
150
|
-
group.items.contains { $0.showBadge }
|
|
151
|
-
} ?? false
|
|
152
|
-
|
|
153
|
-
HStack(alignment: .center, spacing: 0) {
|
|
154
|
-
if shouldShowShortcut {
|
|
155
|
-
NavigationButton(
|
|
156
|
-
disabled: isLoading,
|
|
157
|
-
icon: navButtonConfig.icon,
|
|
158
|
-
showBadge: showBadge,
|
|
159
|
-
onClick: navButtonConfig.onPress,
|
|
160
|
-
tintColor: tintColor
|
|
161
|
-
)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
HStack(alignment: .center, spacing: 0) {
|
|
165
|
-
if environment.applicationContext != nil {
|
|
166
|
-
Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
|
|
167
|
-
.padding(4)
|
|
168
|
-
.onTapGesture {
|
|
169
|
-
onPressHelpCenter()
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
Rectangle()
|
|
173
|
-
.fill(tintColor ?? Colors.black20)
|
|
174
|
-
.frame(width: 0.5, height: 12)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
Icon(source: "16_basic_home", size: 20, color: tintColor ?? Colors.black17)
|
|
178
|
-
.padding(4)
|
|
179
|
-
.onTapGesture {
|
|
180
|
-
onPressClose()
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
.frame(width: 65, height: 28)
|
|
184
|
-
.background(backgroundButtonColor)
|
|
185
|
-
.cornerRadius(14)
|
|
186
|
-
.overlay(
|
|
187
|
-
RoundedRectangle(cornerRadius: 14)
|
|
188
|
-
.stroke(borderColor, lineWidth: 0.2)
|
|
189
|
-
)
|
|
190
|
-
.padding(.leading, Spacing.S)
|
|
191
|
-
}
|
|
192
|
-
.onAppear {
|
|
193
|
-
loadFavoriteState()
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Screen.swift
|
|
3
|
-
// Pods
|
|
4
|
-
//
|
|
5
|
-
// Created by sophia on 5/2/25.
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
import SwiftUI
|
|
9
|
-
|
|
10
|
-
public struct Screen<Content: View>: View {
|
|
11
|
-
// MARK: - Properties
|
|
12
|
-
|
|
13
|
-
var backgroundColor: Color?
|
|
14
|
-
var headerTransparent: Bool = false
|
|
15
|
-
var fullScreenContent: Bool = false
|
|
16
|
-
var tintColor: Color? = Colors.black17
|
|
17
|
-
var isBack: Bool
|
|
18
|
-
var headerType: HeaderType
|
|
19
|
-
var verticalAlignment: VerticalAlignment
|
|
20
|
-
var horizontalAlignment: HorizontalAlignment
|
|
21
|
-
var title: String
|
|
22
|
-
var titlePosition: TitlePosition
|
|
23
|
-
var goBack: (() -> Void)?
|
|
24
|
-
var scrollable: Bool
|
|
25
|
-
var onContentLayout: ((CGRect) -> Void)?
|
|
26
|
-
var useAvoidKeyboard: Bool
|
|
27
|
-
var footer: (() -> AnyView)?
|
|
28
|
-
var headerRight: (()->any View)? = {HeaderRight()}
|
|
29
|
-
var animatedHeader: AnimatedHeader? = nil
|
|
30
|
-
var layoutOffset: CGFloat
|
|
31
|
-
var inputSearchProps: InputSearchProps?
|
|
32
|
-
var fabProps: FabProps?
|
|
33
|
-
var content: () -> Content
|
|
34
|
-
|
|
35
|
-
public init(
|
|
36
|
-
backgroundColor: Color? = nil,
|
|
37
|
-
headerTransparent: Bool = false,
|
|
38
|
-
fullScreenContent: Bool = false,
|
|
39
|
-
tintColor: Color = Colors.black17,
|
|
40
|
-
isBack: Bool = true,
|
|
41
|
-
headerType: HeaderType = .default,
|
|
42
|
-
verticalAlignment: VerticalAlignment = .top,
|
|
43
|
-
horizontalAlignment: HorizontalAlignment = .leading,
|
|
44
|
-
title: String = "Stack",
|
|
45
|
-
titlePosition: TitlePosition = .left,
|
|
46
|
-
goBack: (() -> Void)? = nil,
|
|
47
|
-
scrollable: Bool = true,
|
|
48
|
-
onContentLayout: ((CGRect) -> Void)? = nil,
|
|
49
|
-
useAvoidKeyboard: Bool = true,
|
|
50
|
-
footer: (() -> AnyView)? = nil,
|
|
51
|
-
headerRight: (()->any View)? = {HeaderRight()},
|
|
52
|
-
animatedHeader: AnimatedHeader? = nil,
|
|
53
|
-
layoutOffset: CGFloat = 56,
|
|
54
|
-
inputSearchProps: InputSearchProps? = nil,
|
|
55
|
-
fabProps: FabProps? = nil,
|
|
56
|
-
@ViewBuilder content: @escaping () -> Content
|
|
57
|
-
) {
|
|
58
|
-
self.backgroundColor = backgroundColor
|
|
59
|
-
self.headerTransparent = headerTransparent
|
|
60
|
-
self.fullScreenContent = fullScreenContent
|
|
61
|
-
self.tintColor = tintColor
|
|
62
|
-
self.isBack = isBack
|
|
63
|
-
self.headerType = headerType
|
|
64
|
-
self.verticalAlignment = verticalAlignment
|
|
65
|
-
self.horizontalAlignment = horizontalAlignment
|
|
66
|
-
self.title = title
|
|
67
|
-
self.titlePosition = titlePosition
|
|
68
|
-
self.goBack = goBack
|
|
69
|
-
self.scrollable = scrollable
|
|
70
|
-
self.onContentLayout = onContentLayout
|
|
71
|
-
self.useAvoidKeyboard = useAvoidKeyboard
|
|
72
|
-
self.footer = footer
|
|
73
|
-
self.headerRight = headerRight
|
|
74
|
-
self.animatedHeader = animatedHeader
|
|
75
|
-
self.layoutOffset = layoutOffset
|
|
76
|
-
self.inputSearchProps = inputSearchProps
|
|
77
|
-
self.fabProps = fabProps
|
|
78
|
-
self.content = content
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
@State private var scrollOffset: CGFloat = 0
|
|
82
|
-
@State private var keyboardHeight: CGFloat = 0
|
|
83
|
-
|
|
84
|
-
// MARK: - Body
|
|
85
|
-
|
|
86
|
-
public var body: some View {
|
|
87
|
-
GeometryReader { geometry in
|
|
88
|
-
let topInset = geometry.safeAreaInsets.top
|
|
89
|
-
let keyboardSize: CGFloat = useAvoidKeyboard ? max(keyboardHeight, 0) : 0
|
|
90
|
-
let height = geometry.size.height
|
|
91
|
-
let bottomInset = geometry.safeAreaInsets.bottom
|
|
92
|
-
let screenHeight = height + topInset + bottomInset
|
|
93
|
-
|
|
94
|
-
ZStack(alignment: .top) {
|
|
95
|
-
// Background color
|
|
96
|
-
(backgroundColor ?? Color.white)
|
|
97
|
-
.edgesIgnoringSafeArea(.all)
|
|
98
|
-
|
|
99
|
-
VStack(spacing: 0) {
|
|
100
|
-
ZStack(alignment: fabProps?.position == .center ? .bottom : .bottomTrailing) {
|
|
101
|
-
VStack(alignment: horizontalAlignment, spacing: 0) {
|
|
102
|
-
if animatedHeader != nil || (headerTransparent && fullScreenContent) {
|
|
103
|
-
ZStack(alignment: .top) {
|
|
104
|
-
// MARK: - Header
|
|
105
|
-
Header(
|
|
106
|
-
headerType: headerType,
|
|
107
|
-
titlePosition: titlePosition,
|
|
108
|
-
title: title,
|
|
109
|
-
headerRight: headerRight,
|
|
110
|
-
goBack: goBack,
|
|
111
|
-
opacity: min(scrollOffset / 114, 1),
|
|
112
|
-
animatedHeader: animatedHeader,
|
|
113
|
-
scrollState: scrollOffset,
|
|
114
|
-
inputSearchProps: inputSearchProps,
|
|
115
|
-
tintColor: tintColor
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
.background(
|
|
119
|
-
HeaderBackground(
|
|
120
|
-
headerType: headerType,
|
|
121
|
-
scrollState: scrollOffset,
|
|
122
|
-
headerTransparent: headerTransparent,
|
|
123
|
-
fullScreenContent: fullScreenContent
|
|
124
|
-
|
|
125
|
-
)
|
|
126
|
-
.overlay(animatedHeader?.composable(scrollOffset)
|
|
127
|
-
.frame(maxWidth: .infinity)
|
|
128
|
-
.aspectRatio(animatedHeader?.aspectRatio.value, contentMode: .fill)
|
|
129
|
-
)
|
|
130
|
-
).zIndex(100)
|
|
131
|
-
|
|
132
|
-
VStack {
|
|
133
|
-
if scrollable {
|
|
134
|
-
ScrollView(.vertical, showsIndicators: false) {
|
|
135
|
-
VStack {
|
|
136
|
-
content()
|
|
137
|
-
}
|
|
138
|
-
.background(GeometryReader { geo -> Color in
|
|
139
|
-
DispatchQueue.main.async {
|
|
140
|
-
self.scrollOffset = -geo.frame(in: .global).origin.y
|
|
141
|
-
}
|
|
142
|
-
return Color.clear
|
|
143
|
-
})
|
|
144
|
-
}
|
|
145
|
-
} else {
|
|
146
|
-
content()
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
.edgesIgnoringSafeArea(.all)
|
|
150
|
-
}
|
|
151
|
-
} else {
|
|
152
|
-
// MARK: - Header
|
|
153
|
-
Header(
|
|
154
|
-
headerType: headerType,
|
|
155
|
-
titlePosition: titlePosition,
|
|
156
|
-
title: title,
|
|
157
|
-
headerRight: headerRight,
|
|
158
|
-
goBack: goBack,
|
|
159
|
-
opacity: min(scrollOffset / 114, 1),
|
|
160
|
-
animatedHeader: animatedHeader,
|
|
161
|
-
scrollState: scrollOffset,
|
|
162
|
-
inputSearchProps: inputSearchProps,
|
|
163
|
-
tintColor: tintColor
|
|
164
|
-
)
|
|
165
|
-
.background(
|
|
166
|
-
HeaderBackground(
|
|
167
|
-
headerType: headerType,
|
|
168
|
-
scrollState: scrollOffset,
|
|
169
|
-
headerTransparent: headerTransparent,
|
|
170
|
-
fullScreenContent: fullScreenContent
|
|
171
|
-
)
|
|
172
|
-
.edgesIgnoringSafeArea(.top)
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
if scrollable {
|
|
176
|
-
ScrollView(.vertical, showsIndicators: false) {
|
|
177
|
-
VStack {
|
|
178
|
-
content()
|
|
179
|
-
}
|
|
180
|
-
.background(GeometryReader { geo -> Color in
|
|
181
|
-
DispatchQueue.main.async {
|
|
182
|
-
self.scrollOffset = -geo.frame(in: .global).origin.y
|
|
183
|
-
}
|
|
184
|
-
return Color.clear
|
|
185
|
-
})
|
|
186
|
-
}
|
|
187
|
-
} else {
|
|
188
|
-
content()
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if let fabProps = fabProps {
|
|
194
|
-
FloatingButton(
|
|
195
|
-
props: fabProps,
|
|
196
|
-
keyboardOffset: useAvoidKeyboard ? keyboardSize : 0,
|
|
197
|
-
scrollOffset: scrollOffset
|
|
198
|
-
)
|
|
199
|
-
.padding(.horizontal, Spacing.M)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// MARK: - Footer
|
|
205
|
-
if let footerView = footer?() {
|
|
206
|
-
Footer(footerView: footerView, keyboardSize: keyboardSize)
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// MARK: - Keyboard Handling
|
|
214
|
-
|
|
215
|
-
private func observeKeyboard() {
|
|
216
|
-
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main) { notification in
|
|
217
|
-
if useAvoidKeyboard,
|
|
218
|
-
let endFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
|
|
219
|
-
withAnimation(.easeOut(duration: 0.25)) {
|
|
220
|
-
keyboardHeight = UIScreen.main.bounds.height - endFrame.origin.y
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// MARK: - Footer
|
|
228
|
-
|
|
229
|
-
struct Footer: View {
|
|
230
|
-
var footerView: AnyView
|
|
231
|
-
var keyboardSize: CGFloat
|
|
232
|
-
var bottomInset: CGFloat = 0
|
|
233
|
-
|
|
234
|
-
var body: some View {
|
|
235
|
-
VStack {
|
|
236
|
-
footerView
|
|
237
|
-
}
|
|
238
|
-
.padding(.vertical, Spacing.S)
|
|
239
|
-
.padding(.horizontal, Spacing.M)
|
|
240
|
-
.padding(.bottom, keyboardSize - bottomInset)
|
|
241
|
-
.background(Colors.black01)
|
|
242
|
-
.overlay(
|
|
243
|
-
Rectangle()
|
|
244
|
-
.fill(Colors.black20.opacity(0.1))
|
|
245
|
-
.frame(height: 4)
|
|
246
|
-
.shadow(color: Colors.black20.opacity(0.2), radius: 10, y: -2),
|
|
247
|
-
alignment: .top
|
|
248
|
-
)
|
|
249
|
-
}
|
|
250
|
-
}
|