@momo-kits/native-kits 0.152.4-beta.2 → 0.152.4-beta.3

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.
Files changed (155) hide show
  1. package/CODE_OF_CONDUCT.md +133 -0
  2. package/CONTRIBUTING.md +114 -0
  3. package/LICENSE +20 -0
  4. package/README.md +7 -0
  5. package/build.gradle.kts +32 -0
  6. package/compose/MoMoComposeKits.podspec +54 -0
  7. package/compose/build.gradle.kts +149 -0
  8. package/compose/src/androidMain/AndroidManifest.xml +2 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +105 -0
  10. package/compose/src/commonMain/composeResources/files/lottie_circle_loader.json +1 -0
  11. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  12. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  13. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  14. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  20. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  21. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  22. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  23. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  24. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +57 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +201 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +222 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +48 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +306 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +715 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +214 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +236 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +77 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +27 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +334 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +345 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +90 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +131 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +69 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +143 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +179 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +111 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +384 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +160 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +234 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +223 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +232 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +236 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +228 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +364 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +50 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +34 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +85 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +33 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +338 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +95 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +64 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +89 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +91 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +84 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +208 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +172 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +199 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +237 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +191 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +13 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +191 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +258 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +59 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +68 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +49 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +232 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +111 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +94 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +159 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +232 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScaleSizeScope.kt +17 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +459 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +169 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +216 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +180 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +251 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +80 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +306 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +31 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +385 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +38 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +62 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +88 -0
  108. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +144 -0
  109. package/gradle.properties +19 -0
  110. package/gradlew +240 -0
  111. package/gradlew.bat +91 -0
  112. package/ios/Application/ApplicationEnvironment.swift +50 -0
  113. package/ios/Application/Components.swift +263 -0
  114. package/ios/Application/ComposeApi.swift +22 -0
  115. package/ios/Application/FloatingButton.swift +172 -0
  116. package/ios/Application/HeaderRight.swift +271 -0
  117. package/ios/Application/Screen.swift +249 -0
  118. package/ios/Badge/BadgeDot.swift +31 -0
  119. package/ios/Button/Button.swift +211 -0
  120. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +126 -0
  121. package/ios/Checkbox/Checkbox.swift +81 -0
  122. package/ios/Chip/Chip.swift +96 -0
  123. package/ios/Colors+Radius+Spacing/Colors.swift +172 -0
  124. package/ios/Colors+Radius+Spacing/Radius.swift +22 -0
  125. package/ios/Colors+Radius+Spacing/Spacing.swift +12 -0
  126. package/ios/Extensions/Color++.swift +25 -0
  127. package/ios/Icon/Icon.swift +51 -0
  128. package/ios/Image/Image.swift +70 -0
  129. package/ios/Input/Input.swift +207 -0
  130. package/ios/Input/InputPhoneNumber.swift +176 -0
  131. package/ios/Input/InputSearch.swift +238 -0
  132. package/ios/Input/InputTextArea.swift +242 -0
  133. package/ios/Lottie/LottieView.swift +86 -0
  134. package/ios/OTPKeyboard/KeyboardButton.swift +41 -0
  135. package/ios/OTPKeyboard/OTPKeyboard.swift +145 -0
  136. package/ios/Popup/PopupDisplay.swift +284 -0
  137. package/ios/Popup/PopupInput.swift +96 -0
  138. package/ios/Popup/PopupPromotion.swift +73 -0
  139. package/ios/PopupView/FullscreenPopup.swift +251 -0
  140. package/ios/PopupView/Modifiers.swift +158 -0
  141. package/ios/PopupView/PopupView.swift +289 -0
  142. package/ios/PopupView/Utils++.swift +281 -0
  143. package/ios/ScrollIndicator/ScrollIndicator.swift +110 -0
  144. package/ios/Swipeable/SwipeCell.swift +278 -0
  145. package/ios/Swipeable/SwipeCellModel.swift +86 -0
  146. package/ios/Switch/Switch.swift +44 -0
  147. package/ios/Template/Logo/Logo.swift +75 -0
  148. package/ios/Template/TrustBanner/TrustBanner.swift +120 -0
  149. package/ios/Theme.md +18 -0
  150. package/ios/Typography/Text.swift +140 -0
  151. package/ios/Typography/Typography.swift +95 -0
  152. package/ios/native-kits.podspec +18 -0
  153. package/package.json +6 -7
  154. package/settings.gradle.kts +25 -0
  155. package/shared/build.gradle.kts +0 -74
@@ -0,0 +1,271 @@
1
+ import SwiftUI
2
+ import Foundation
3
+
4
+ public struct HeaderRightData {
5
+ var useShortcut: Bool
6
+ var useMore: Bool
7
+ var tools: [ToolGroup]
8
+ var toolCallback: ((String) -> Void)?
9
+ var useSystemTools: Bool
10
+
11
+ public init(
12
+ useShortcut: Bool = false,
13
+ useMore: Bool = false,
14
+ tools: [ToolGroup] = [],
15
+ toolCallback: ((String) -> Void)? = nil,
16
+ useSystemTools: Bool = true
17
+ ) {
18
+ self.useShortcut = useShortcut
19
+ self.useMore = useMore
20
+ self.tools = tools
21
+ self.toolCallback = toolCallback
22
+ self.useSystemTools = useSystemTools
23
+ }
24
+ }
25
+
26
+ public struct Tool {
27
+ var key: String
28
+ var icon: String
29
+ var showBadge: Bool
30
+ var name: [String: String]
31
+ var showRightIcon: Bool
32
+
33
+ public init(
34
+ key: String,
35
+ icon: String,
36
+ name: [String: String] = [:],
37
+ showBadge: Bool = false,
38
+ showRightIcon: Bool = true
39
+ ) {
40
+ self.key = key
41
+ self.icon = icon
42
+ self.showBadge = showBadge
43
+ self.name = name
44
+ self.showRightIcon = showRightIcon
45
+ }
46
+
47
+ func toMap() -> [String: Any] {
48
+ return [
49
+ "key": key,
50
+ "icon": icon,
51
+ "showBadge": showBadge,
52
+ "name": name,
53
+ "showRightIcon": showRightIcon
54
+ ]
55
+ }
56
+ }
57
+
58
+ public struct ToolGroup {
59
+ var title: [String: String]
60
+ var items: [Tool]
61
+
62
+ public init(
63
+ title: [String: String] = [:],
64
+ items: [Tool]
65
+ ) {
66
+ self.title = title
67
+ self.items = items
68
+ }
69
+
70
+ func toMap() -> [String: Any] {
71
+ return [
72
+ "title": title,
73
+ "items": items.map { $0.toMap() }
74
+ ]
75
+ }
76
+ }
77
+
78
+ struct NavigationButtonConfig {
79
+ let icon: String
80
+ let onPress: () -> Void
81
+ }
82
+
83
+ // MARK: - Components
84
+ public struct HeaderRight: View {
85
+ public init(headerRight: HeaderRightData? = nil, tintColor: Color? = nil) {
86
+ self.headerRight = headerRight
87
+ self.tintColor = tintColor
88
+ }
89
+ var headerRight: HeaderRightData?
90
+ var tintColor: Color? = nil
91
+
92
+ public var body: some View {
93
+ HStack(alignment: .center) {
94
+ ToolkitHeaderRight(headerRight: headerRight, tintColor: tintColor)
95
+ }
96
+ }
97
+ }
98
+
99
+ struct ToolkitHeaderRight: View {
100
+ var headerRight: HeaderRightData?
101
+ var tintColor: Color?
102
+ @State private var isFavorite: Bool = false
103
+ @State private var isLoading: Bool = false
104
+ @EnvironmentObject private var environment: ApplicationEnvironment
105
+
106
+ // MARK: - Actions
107
+ private func onPressShortcut() {
108
+ environment.composeApi?.request(
109
+ funcName: "onToolAction",
110
+ params: ["item": ["key": "onFavorite"]]
111
+ ) { _ in
112
+ isFavorite.toggle()
113
+ }
114
+ }
115
+
116
+ private func onPressHelpCenter() {
117
+ let context = environment.applicationContext
118
+ let paramMap: [String: Any] = [
119
+ "appId": context?.appId,
120
+ "code": context?.appCode,
121
+ "name": context?.appName,
122
+ "icon": context?.appIcon,
123
+ "description": context?.description
124
+ ]
125
+ environment.composeApi?.request(
126
+ funcName: "showHelpCenter",
127
+ params: paramMap
128
+ ) { _ in }
129
+ }
130
+
131
+ private func onPressClose() {
132
+ environment.composeApi?.request(
133
+ funcName: "dismissAll",
134
+ params: ""
135
+ ) { _ in }
136
+ }
137
+
138
+ private func onPressMore() {
139
+ let context = environment.applicationContext
140
+ let params: [String: Any] = [
141
+ "useSystemTools": headerRight?.useSystemTools,
142
+ "tools": headerRight?.tools.map { $0.toMap() } ?? [],
143
+ "context": [
144
+ "appId": context?.appId,
145
+ "code": context?.appCode,
146
+ "name": context?.appName,
147
+ "icon": context?.appIcon,
148
+ "description": context?.description,
149
+ "support": context?.support,
150
+ "toolkitConfig": context?.toolkitConfig,
151
+ "providerId": context?.providerId,
152
+ "permissions": context?.permissions
153
+ ]
154
+ ]
155
+ environment.composeApi?.request(
156
+ funcName: "showTools",
157
+ params: params
158
+ ) { response in
159
+ do {
160
+ if let jsonData = response.data(using: .utf8),
161
+ let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
162
+ let toolResponse = json["response"] as? String {
163
+ headerRight?.toolCallback?(toolResponse)
164
+ }
165
+ } catch {
166
+ print("Error parsing response:", error)
167
+ }
168
+ }
169
+ }
170
+
171
+ private func getNavigationButtonConfig() -> NavigationButtonConfig {
172
+ let totalTools = headerRight?.tools.reduce(0) { $0 + $1.items.count } ?? 0
173
+ var icon = isFavorite ? "pin_star_checked" : "pin_star"
174
+ var onClickHandler: () -> Void = onPressShortcut
175
+
176
+ if totalTools > 1 || headerRight?.useMore == true {
177
+ icon = "navigation_more_icon"
178
+ onClickHandler = onPressMore
179
+ } else if totalTools == 1, let singleTool = headerRight?.tools.first?.items.first {
180
+ icon = singleTool.icon
181
+ onClickHandler = {
182
+ headerRight?.toolCallback?(singleTool.key)
183
+ }
184
+ }
185
+
186
+ return NavigationButtonConfig(icon: icon, onPress: onClickHandler)
187
+ }
188
+
189
+ var body: some View {
190
+ let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
191
+ let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
192
+ let navButtonConfig = getNavigationButtonConfig()
193
+ let showBadge = headerRight?.tools.contains { group in
194
+ group.items.contains { $0.showBadge }
195
+ } ?? false
196
+
197
+ HStack(alignment: .center, spacing: 0) {
198
+ if headerRight?.useShortcut == true {
199
+ NavigationButton(
200
+ disabled: isLoading,
201
+ icon: navButtonConfig.icon,
202
+ showBadge: showBadge,
203
+ onClick: navButtonConfig.onPress,
204
+ tintColor: tintColor
205
+ )
206
+ }
207
+
208
+ HStack(alignment: .center, spacing: 0) {
209
+ Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
210
+ .padding(4)
211
+ .onTapGesture {
212
+ onPressHelpCenter()
213
+ }
214
+
215
+ Rectangle()
216
+ .fill(tintColor ?? Colors.black20)
217
+ .frame(width: 0.5, height: 12)
218
+
219
+ Icon(source: "16_basic_home", size: 20, color: tintColor ?? Colors.black17)
220
+ .padding(4)
221
+ .onTapGesture {
222
+ onPressClose()
223
+ }
224
+ }
225
+ .frame(width: 65, height: 28)
226
+ .background(backgroundButtonColor)
227
+ .cornerRadius(14)
228
+ .overlay(
229
+ RoundedRectangle(cornerRadius: 14)
230
+ .stroke(borderColor, lineWidth: 0.2)
231
+ )
232
+ .padding(.leading, Spacing.S)
233
+ }
234
+ }
235
+ }
236
+
237
+ struct NavigationButton: View {
238
+ var disabled: Bool
239
+ var icon: String
240
+ var showBadge: Bool
241
+ var onClick: () -> Void
242
+ var tintColor: Color?
243
+
244
+ var body: some View {
245
+ let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
246
+ let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
247
+
248
+ ZStack {
249
+ Circle()
250
+ .fill(backgroundButtonColor)
251
+ .overlay(
252
+ Circle()
253
+ .stroke(borderColor, lineWidth: 0.2)
254
+ )
255
+
256
+ Icon(source: icon, size: 16, color: tintColor)
257
+
258
+ if showBadge {
259
+ BadgeDot(size: .small)
260
+ .offset(x: -2, y: -2)
261
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
262
+ }
263
+ }
264
+ .frame(width: 28, height: 28)
265
+ .onTapGesture {
266
+ if !disabled {
267
+ onClick()
268
+ }
269
+ }
270
+ }
271
+ }
@@ -0,0 +1,249 @@
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
+
233
+ var body: some View {
234
+ VStack {
235
+ footerView
236
+ }
237
+ .padding(.vertical, Spacing.S)
238
+ .padding(.horizontal, Spacing.M)
239
+ .padding(.bottom, keyboardSize)
240
+ .background(Colors.black01)
241
+ .overlay(
242
+ Rectangle()
243
+ .fill(Colors.black20.opacity(0.1))
244
+ .frame(height: 4)
245
+ .shadow(color: Colors.black20.opacity(0.2), radius: 10, y: -2),
246
+ alignment: .top
247
+ )
248
+ }
249
+ }
@@ -0,0 +1,31 @@
1
+ import SwiftUI
2
+
3
+ public enum DotSize {
4
+ case small
5
+ case large
6
+
7
+ var size: CGFloat {
8
+ switch self {
9
+ case .small: return 10
10
+ case .large: return 16
11
+ }
12
+ }
13
+ }
14
+
15
+ public struct BadgeDot: View {
16
+ var size: DotSize
17
+
18
+ public init(size: DotSize = .large) {
19
+ self.size = size
20
+ }
21
+
22
+ public var body: some View {
23
+ Circle()
24
+ .fill(Colors.red03)
25
+ .frame(width: size.size, height: size.size)
26
+ .overlay(
27
+ Circle()
28
+ .stroke(Colors.black01, lineWidth: 1)
29
+ )
30
+ }
31
+ }