@momo-kits/native-kits 0.163.1-beta.3 → 0.163.1-beta.5
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 +1 -1
- package/ios/Application/ApplicationEnvironment.swift +11 -1
- package/ios/Application/Components.swift +34 -44
- package/ios/Application/FloatingButton.swift +10 -9
- package/ios/Application/HeaderRight.swift +79 -45
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +103 -0
- package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +215 -0
- package/ios/Application/Navigation/BottomTab/CurvedContainer.swift +65 -0
- package/ios/Application/Navigation/HeaderBackProps.swift +66 -0
- package/ios/Application/Navigation/HeaderTitle.swift +57 -0
- package/ios/Application/Navigation/HeaderUser.swift +209 -0
- package/ios/Application/Navigation/NavigationContainer.swift +176 -0
- package/ios/Application/Navigation/NavigationOptions.swift +109 -0
- package/ios/Application/Navigation/Navigator.swift +364 -0
- package/ios/Application/Navigation/Overplay/BottomSheet.swift +188 -0
- package/ios/Application/Navigation/Overplay/ModalScreen.swift +68 -0
- package/ios/Application/Navigation/Overplay/SnackBar.swift +144 -0
- package/ios/Application/Navigation/component/Header.swift +178 -0
- package/ios/Application/Navigation/component/HeaderBackground.swift +83 -0
- package/ios/Application/Navigation/component/HeaderColor.swift +65 -0
- package/ios/Application/Screen.swift +2 -1
- package/ios/Application/StackScreen.swift +453 -0
- package/ios/Avatar/Avatar.swift +193 -0
- package/ios/Badge/Badge.swift +11 -14
- package/ios/Badge/BadgeRibbon.swift +12 -21
- package/ios/BaselineView/BaselineView.swift +163 -0
- package/ios/Button/Button.swift +61 -22
- package/ios/Carousel/Carousel.swift +181 -0
- package/ios/Checkbox/Checkbox.swift +14 -13
- package/ios/Chip/Chip.swift +23 -9
- package/ios/Collapse/Collapse.swift +182 -0
- package/ios/Colors+Radius+Spacing/Colors.swift +12 -12
- package/ios/Colors+Radius+Spacing/Theme.swift +295 -0
- package/ios/DateTimePicker/DateTimePicker.swift +210 -0
- package/ios/DateTimePicker/DateTimePickerTypes.swift +55 -0
- package/ios/DateTimePicker/DateTimePickerUtils.swift +167 -0
- package/ios/DateTimePicker/WheelPicker.swift +161 -0
- package/ios/Divider/Divider.swift +16 -0
- package/ios/Extensions/Color++.swift +32 -0
- package/ios/Icon/Icon.swift +6 -1
- package/ios/IconButton/IconButton.swift +112 -0
- package/ios/Input/Input.swift +41 -25
- package/ios/Input/InputPhoneNumber.swift +20 -19
- package/ios/Input/InputSearch.swift +100 -47
- package/ios/Input/InputTextArea.swift +66 -40
- package/ios/InputDropDown/InputDropDown.swift +198 -0
- package/ios/InputMoney/InputMoney.swift +335 -0
- package/ios/InputOTP/InputOTP.swift +210 -0
- package/ios/Loader/Loader.swift +113 -0
- package/ios/MoMoUIKits.podspec +5 -0
- package/ios/Pagination/PaginationDot.swift +61 -0
- package/ios/Pagination/PaginationNumber.swift +35 -0
- package/ios/Pagination/PaginationScroll.swift +101 -0
- package/ios/Pagination/PaginationWhiteDot.swift +37 -0
- package/ios/Popup/PopupDisplay.swift +8 -16
- package/ios/Popup/PopupInput.swift +2 -8
- package/ios/Popup/PopupNotify.swift +219 -0
- package/ios/ProgressInfo/ProgressInfo.swift +294 -0
- package/ios/Radio/Radio.swift +70 -0
- package/ios/Rating/Rating.swift +82 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Background.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Border.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Card.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Contents.json +6 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Error.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Primary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryDark.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryLight.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Secondary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Success.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Text.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/TextSecondary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/Contents.json +9 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-default.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-disable.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-pressed.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-selected.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-surface.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-tonal.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-default.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-disable.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/gradient.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/primary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/secondary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-container.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-primary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-secondary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-default.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-disable.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-hint.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-secondary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Warning.colorset/Contents.json +38 -0
- package/ios/Skeleton/Skeleton.swift +99 -0
- package/ios/Slider/Slider.swift +237 -0
- package/ios/Stepper/Stepper.swift +220 -0
- package/ios/Steps/Steps.swift +451 -0
- package/ios/SuggestAction/SuggestAction.swift +97 -0
- package/ios/Swipeable/SwipeCell.swift +185 -0
- package/ios/Swipeable/SwipeCellModel.swift +21 -0
- package/ios/TabView/TabView.swift +530 -0
- package/ios/Tag/Tag.swift +98 -0
- package/ios/Title/Title.swift +269 -0
- package/ios/Tooltip/Tooltip.swift +482 -0
- package/ios/Typography/FontScaleStore.swift +19 -0
- package/ios/Typography/Text.swift +86 -17
- package/ios/Typography/Typography.swift +12 -14
- package/ios/Uploader/Uploader.swift +183 -0
- package/ios/native-kits.podspec +61 -8
- package/ios-demo/MoMoUIKitsDemo.podspec +20 -0
- package/ios-demo/Resources/dot_loading.json +558 -0
- package/ios-demo/Resources/icon.json +4052 -0
- package/ios-demo/Resources/lottie_circle_loader.json +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/DemoScaffold.swift +48 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsBadgeDemoView.swift +25 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsButtonDemoView.swift +26 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsCheckboxDemoView.swift +19 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsChipDemoView.swift +33 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsInputDemoView.swift +48 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsPopupDemoView.swift +21 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsRadioDemoView.swift +17 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsTypographyDemoView.swift +28 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoContext.swift +31 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoFactory.swift +24 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +110 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +93 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +269 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BaselineView.swift +117 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BottomTabDemo.swift +89 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +100 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +126 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +63 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +20 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +93 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +107 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +124 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +14 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +49 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +278 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +58 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +78 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +138 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +111 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +136 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +107 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +73 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +100 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +32 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +57 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +70 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +227 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +72 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +175 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +121 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +76 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +89 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +32 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +56 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +32 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +150 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +138 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +103 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +94 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +79 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +152 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +33 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +190 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +34 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +182 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +109 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +187 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +87 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +131 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +51 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
- package/package.json +1 -1
- package/publish.sh +50 -0
- package/scripts/gen-ios-color.mjs +136 -0
- package/example/ios/Example.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -5
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImage.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SkeletonUI.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -46
- package/ios/Application/FontScaleStore.swift +0 -59
package/CLAUDE.md
CHANGED
|
@@ -40,7 +40,7 @@ Publishing is automated (`publish.sh` + GitLab CI on `[ci build]` / `main`). Do
|
|
|
40
40
|
## Architecture essentials
|
|
41
41
|
|
|
42
42
|
- **Entry point:** wrap content in `NavigationContainer(...)` from `navigation/`. It builds the `Navigator`, takes a host-supplied `Localize` (falls back to an empty one), and provides the CompositionLocals below. **Do not use** `ApplicationContainer` (`application/NavigationContainer.kt`) — deprecated and does **not** wire `LocalLocalize` / `LocalNavigator` / `LocalMaxApi`.
|
|
43
|
-
- **Configuration flows via CompositionLocals**, not params. Public ones: `AppTheme`, `AppStatusBar`, `AppNavigationBar` (`const/Theme.kt`); `ApplicationContext`, `AppConfig`, `AppLanguage`, `LocalLocalize
|
|
43
|
+
- **Configuration flows via CompositionLocals**, not params. Public ones: `AppTheme`, `AppStatusBar`, `AppNavigationBar` (`const/Theme.kt`); `ApplicationContext`, `AppConfig`, `AppLanguage`, `LocalLocalize` (`application/`); `LocalNavigator`, `LocalMaxApi` (`navigation/`). Read with `X.current`.
|
|
44
44
|
- **Platform code** goes through `expect`/`actual` in `platform/` — never reference Android/iOS APIs from `commonMain` directly.
|
|
45
45
|
- **Native bridge:** `IMaxApi` (`vn.momo.maxapi`, exposed as `LocalMaxApi`) is the host-app bridge (dismiss, tracking, device info, language).
|
|
46
46
|
- Navigation uses a runtime `DynamicScreenRegistry`; overlays (modal/bottom-sheet/snackbar) go through `Navigator` + `OverplayComponentRegistry`.
|
|
@@ -46,6 +46,16 @@ public class ApplicationEnvironment: ObservableObject {
|
|
|
46
46
|
self.applicationContext = applicationContext
|
|
47
47
|
self.composeApi = composeApi
|
|
48
48
|
self.config = config
|
|
49
|
-
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private struct ApplicationEnvironmentKey: EnvironmentKey {
|
|
53
|
+
static let defaultValue = ApplicationEnvironment()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public extension EnvironmentValues {
|
|
57
|
+
var applicationEnvironment: ApplicationEnvironment {
|
|
58
|
+
get { self[ApplicationEnvironmentKey.self] }
|
|
59
|
+
set { self[ApplicationEnvironmentKey.self] = newValue }
|
|
50
60
|
}
|
|
51
61
|
}
|
|
@@ -83,9 +83,8 @@ public struct HeaderBackground: View {
|
|
|
83
83
|
self.headerTransparent = headerTransparent
|
|
84
84
|
self.fullScreenContent = fullScreenContent
|
|
85
85
|
}
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
public var body: some View {
|
|
88
|
-
|
|
89
88
|
let statusBarHeight = safeAreaTopInset()
|
|
90
89
|
let opacity = max(0, 1 - scrollState / 200)
|
|
91
90
|
let height = fullScreenContent ? 0 : statusBarHeight + 52
|
|
@@ -101,21 +100,6 @@ public struct HeaderBackground: View {
|
|
|
101
100
|
.frame(height: height)
|
|
102
101
|
.shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
|
|
103
102
|
.background(backgroundColor)
|
|
104
|
-
.overlay(
|
|
105
|
-
headerTransparent ? AnyView(EmptyView()) :
|
|
106
|
-
AnyView(
|
|
107
|
-
Rectangle()
|
|
108
|
-
.fill(LinearGradient(
|
|
109
|
-
gradient: Gradient(colors: [
|
|
110
|
-
Color(red: 1, green: 0.8, blue: 0.87),
|
|
111
|
-
Color(red: 1, green: 0.8, blue: 0.87).opacity(0.5)
|
|
112
|
-
]),
|
|
113
|
-
startPoint: .top,
|
|
114
|
-
endPoint: .bottom))
|
|
115
|
-
.opacity(opacity)
|
|
116
|
-
.frame(height: height))
|
|
117
|
-
)
|
|
118
|
-
|
|
119
103
|
if !headerTransparent {
|
|
120
104
|
Rectangle()
|
|
121
105
|
.fill(Color.black.opacity(0.1))
|
|
@@ -148,12 +132,26 @@ public struct HeaderBackground: View {
|
|
|
148
132
|
}
|
|
149
133
|
}
|
|
150
134
|
|
|
135
|
+
// MARK: - Header right width preference
|
|
136
|
+
|
|
137
|
+
/// Reports the measured width of the header-right area so the animated search
|
|
138
|
+
/// header can compute its trailing inset (mirrors Compose's headerRightWidthPx).
|
|
139
|
+
public struct HeaderRightWidthKey: PreferenceKey {
|
|
140
|
+
public static var defaultValue: CGFloat = 0
|
|
141
|
+
public static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
|
142
|
+
value = max(value, nextValue())
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
151
146
|
// MARK: - Header View
|
|
152
147
|
|
|
153
148
|
public struct Header: View {
|
|
154
149
|
var headerType: HeaderType = .default
|
|
155
150
|
var titlePosition: TitlePosition
|
|
156
151
|
var title: String
|
|
152
|
+
/// Optional rich title (mirrors Compose's `HeaderTitle`); `.user` renders an
|
|
153
|
+
/// avatar group instead of `title`. Defaults to nil → plain `title` text.
|
|
154
|
+
var headerTitle: HeaderTitle?
|
|
157
155
|
var headerRight: (()->any View)? = {HeaderRight()}
|
|
158
156
|
var goBack: (() -> Void)?
|
|
159
157
|
var opacity: CGFloat = 1
|
|
@@ -161,22 +159,27 @@ public struct Header: View {
|
|
|
161
159
|
var scrollState: CGFloat = 0
|
|
162
160
|
var inputSearchProps: InputSearchProps?
|
|
163
161
|
var tintColor: Color?
|
|
162
|
+
/// Title opacity; used by the animated search header to fade the title out on scroll.
|
|
163
|
+
var titleOpacity: CGFloat = 1
|
|
164
164
|
|
|
165
165
|
public init(
|
|
166
166
|
headerType: HeaderType = .default,
|
|
167
167
|
titlePosition: TitlePosition,
|
|
168
168
|
title: String,
|
|
169
|
+
headerTitle: HeaderTitle? = nil,
|
|
169
170
|
headerRight: (()->any View)? = {HeaderRight()},
|
|
170
171
|
goBack: (() -> Void)? = nil,
|
|
171
172
|
opacity: CGFloat = 1,
|
|
172
173
|
animatedHeader: AnimatedHeader? = nil,
|
|
173
174
|
scrollState: CGFloat = 0,
|
|
174
175
|
inputSearchProps: InputSearchProps? = nil,
|
|
175
|
-
tintColor: Color? = nil
|
|
176
|
+
tintColor: Color? = nil,
|
|
177
|
+
titleOpacity: CGFloat = 1
|
|
176
178
|
) {
|
|
177
179
|
self.headerType = headerType
|
|
178
180
|
self.titlePosition = titlePosition
|
|
179
181
|
self.title = title
|
|
182
|
+
self.headerTitle = headerTitle
|
|
180
183
|
self.headerRight = headerRight
|
|
181
184
|
self.goBack = goBack
|
|
182
185
|
self.opacity = opacity
|
|
@@ -184,10 +187,11 @@ public struct Header: View {
|
|
|
184
187
|
self.scrollState = scrollState
|
|
185
188
|
self.inputSearchProps = inputSearchProps
|
|
186
189
|
self.tintColor = tintColor
|
|
190
|
+
self.titleOpacity = titleOpacity
|
|
187
191
|
}
|
|
188
|
-
|
|
192
|
+
|
|
189
193
|
public var body: some View {
|
|
190
|
-
|
|
194
|
+
|
|
191
195
|
let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
|
|
192
196
|
let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
|
|
193
197
|
|
|
@@ -212,6 +216,9 @@ public struct Header: View {
|
|
|
212
216
|
|
|
213
217
|
if let headerRight = headerRight {
|
|
214
218
|
AnyView(headerRight())
|
|
219
|
+
.background(GeometryReader { geo in
|
|
220
|
+
Color.clear.preference(key: HeaderRightWidthKey.self, value: geo.size.width)
|
|
221
|
+
})
|
|
215
222
|
}
|
|
216
223
|
}
|
|
217
224
|
.padding(.horizontal, 12)
|
|
@@ -223,33 +230,16 @@ public struct Header: View {
|
|
|
223
230
|
}
|
|
224
231
|
|
|
225
232
|
if let inputProps = inputSearchProps {
|
|
226
|
-
InputSearch(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
placeholder: inputProps.placeholder,
|
|
232
|
-
onChangeText: inputProps.onChangeText,
|
|
233
|
-
onPressButtonText: inputProps.onPressButtonText,
|
|
234
|
-
error: inputProps.error,
|
|
235
|
-
disabled: inputProps.disabled,
|
|
236
|
-
icon: inputProps.icon,
|
|
237
|
-
iconColor: inputProps.iconColor,
|
|
238
|
-
onRightIconPressed: inputProps.onRightIconPressed,
|
|
239
|
-
onFocus: inputProps.onFocus,
|
|
240
|
-
onBlur: inputProps.onBlur,
|
|
241
|
-
loading: inputProps.loading,
|
|
242
|
-
fontWeight: inputProps.fontWeight,
|
|
243
|
-
keyboardType: inputProps.keyboardType
|
|
244
|
-
)
|
|
233
|
+
InputSearch(props: inputProps)
|
|
234
|
+
} else if case let .user(userData)? = headerTitle {
|
|
235
|
+
if #available(iOS 16.0, *) {
|
|
236
|
+
HeaderUserView(userData)
|
|
237
|
+
}
|
|
245
238
|
} else {
|
|
246
|
-
|
|
247
|
-
.
|
|
248
|
-
.ignoreBoldTextSetting()
|
|
249
|
-
.foregroundColor(tintColor ?? Colors.black17)
|
|
239
|
+
MomoText(headerTitle?.defaultTitle ?? title, typography: .headerDefaultBold, color: tintColor ?? Colors.black17, maxLines: 1)
|
|
240
|
+
.opacity(titleOpacity)
|
|
250
241
|
.frame(maxWidth: titlePosition == .center ? UIScreen.main.bounds.width - 252 : nil)
|
|
251
242
|
.frame(maxWidth: titlePosition == .center ? .infinity : UIScreen.main.bounds.width - 172, alignment: titlePosition == .center ? .center : .leading)
|
|
252
|
-
.lineLimit(1)
|
|
253
243
|
}
|
|
254
244
|
|
|
255
245
|
Spacer()
|
|
@@ -3,11 +3,16 @@ import Combine
|
|
|
3
3
|
|
|
4
4
|
public enum FABSize {
|
|
5
5
|
case small
|
|
6
|
+
case `default`
|
|
7
|
+
/// Backward-compatible alias for older iOS call sites. Compose names this
|
|
8
|
+
/// size `DEFAULT`.
|
|
6
9
|
case large
|
|
7
|
-
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
public enum FABPosition {
|
|
13
|
+
case end
|
|
14
|
+
/// Backward-compatible alias for older iOS call sites. Compose names this
|
|
15
|
+
/// position `END`.
|
|
11
16
|
case right
|
|
12
17
|
case center
|
|
13
18
|
}
|
|
@@ -31,10 +36,10 @@ public struct FabProps {
|
|
|
31
36
|
label: String? = nil,
|
|
32
37
|
onClick: (() -> Void)? = nil,
|
|
33
38
|
containerColor: Color? = Colors.primary,
|
|
34
|
-
size: FABSize? = .
|
|
39
|
+
size: FABSize? = .default,
|
|
35
40
|
iconSize: CGFloat? = nil,
|
|
36
41
|
scrollOffset: CGFloat? = nil,
|
|
37
|
-
position: FABPosition? = .
|
|
42
|
+
position: FABPosition? = .end,
|
|
38
43
|
bottomPadding: CGFloat? = nil,
|
|
39
44
|
renderComponent: (() -> AnyView)? = nil
|
|
40
45
|
) {
|
|
@@ -65,7 +70,7 @@ public struct FloatingButton: View {
|
|
|
65
70
|
private var keyboardOffset: CGFloat
|
|
66
71
|
private var scrollOffset: CGFloat
|
|
67
72
|
private var bottomPadding: CGFloat { props.bottomPadding ?? 12 + keyboardOffset}
|
|
68
|
-
private var position: FABPosition? { props.position ?? .
|
|
73
|
+
private var position: FABPosition? { props.position ?? .end }
|
|
69
74
|
private var containerColor: Color? { props.containerColor ?? Colors.primary }
|
|
70
75
|
private var iconColor: Color? { props.iconColor ?? .white }
|
|
71
76
|
private var label: String? { props.label }
|
|
@@ -152,11 +157,7 @@ public struct FloatingButton: View {
|
|
|
152
157
|
if let label = label {
|
|
153
158
|
if showText {
|
|
154
159
|
Spacer().frame(width: 8)
|
|
155
|
-
|
|
156
|
-
.foregroundColor(.white)
|
|
157
|
-
.font(.system(size: scaleSize(16), weight: .bold))
|
|
158
|
-
.ignoreBoldTextSetting()
|
|
159
|
-
.lineLimit(1)
|
|
160
|
+
MomoText(label, typography: .actionDefaultBold, color: .white, maxLines: 1)
|
|
160
161
|
.background(
|
|
161
162
|
GeometryReader { geo in
|
|
162
163
|
Color.clear.onAppear {
|
|
@@ -101,69 +101,98 @@ struct ToolkitHeaderRight: View {
|
|
|
101
101
|
var tintColor: Color?
|
|
102
102
|
@State private var isFavorite: Bool = false
|
|
103
103
|
@State private var isLoading: Bool = false
|
|
104
|
-
@
|
|
104
|
+
@Environment(\.applicationEnvironment) private var environment
|
|
105
|
+
|
|
106
|
+
private func apiValue(_ value: Any?) -> Any {
|
|
107
|
+
value ?? NSNull()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private var contextMap: [String: Any] {
|
|
111
|
+
let context = environment.applicationContext
|
|
112
|
+
return [
|
|
113
|
+
"appId": apiValue(context?.appId),
|
|
114
|
+
"code": apiValue(context?.appCode),
|
|
115
|
+
"name": apiValue(context?.appName),
|
|
116
|
+
"icon": apiValue(context?.appIcon),
|
|
117
|
+
"description": apiValue(context?.description),
|
|
118
|
+
"support": apiValue(context?.support),
|
|
119
|
+
"toolkitConfig": apiValue(context?.toolkitConfig),
|
|
120
|
+
"providerId": apiValue(context?.providerId),
|
|
121
|
+
"permissions": apiValue(context?.permissions)
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private var shouldShowShortcut: Bool {
|
|
126
|
+
headerRight?.useShortcut == true && !(headerRight?.useMore == true && environment.applicationContext == nil)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private func parseResponse(_ response: String) -> Any? {
|
|
130
|
+
guard let jsonData = response.data(using: .utf8),
|
|
131
|
+
let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
|
|
132
|
+
return nil
|
|
133
|
+
}
|
|
134
|
+
return json["response"]
|
|
135
|
+
}
|
|
105
136
|
|
|
106
137
|
// MARK: - Actions
|
|
138
|
+
private func loadFavoriteState() {
|
|
139
|
+
environment.composeApi?.request(
|
|
140
|
+
funcName: "isFavoriteApp",
|
|
141
|
+
params: ["code": apiValue(environment.applicationContext?.appCode)]
|
|
142
|
+
) { response in
|
|
143
|
+
if let isFavorite = parseResponse(response) as? Bool {
|
|
144
|
+
self.isFavorite = isFavorite
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
107
149
|
private func onPressShortcut() {
|
|
108
150
|
environment.composeApi?.request(
|
|
109
151
|
funcName: "onToolAction",
|
|
110
|
-
params: [
|
|
111
|
-
|
|
112
|
-
|
|
152
|
+
params: [
|
|
153
|
+
"item": ["key": "onFavorite"],
|
|
154
|
+
"context": contextMap
|
|
155
|
+
]
|
|
156
|
+
) { response in
|
|
157
|
+
if let result = parseResponse(response) as? [String: Any],
|
|
158
|
+
result["success"] as? Bool == true {
|
|
159
|
+
isFavorite.toggle()
|
|
160
|
+
}
|
|
113
161
|
}
|
|
114
162
|
}
|
|
115
163
|
|
|
116
164
|
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
165
|
environment.composeApi?.request(
|
|
126
166
|
funcName: "showHelpCenter",
|
|
127
|
-
params:
|
|
167
|
+
params: [
|
|
168
|
+
"appId": apiValue(environment.applicationContext?.appId),
|
|
169
|
+
"code": apiValue(environment.applicationContext?.appCode),
|
|
170
|
+
"name": apiValue(environment.applicationContext?.appName),
|
|
171
|
+
"icon": apiValue(environment.applicationContext?.appIcon),
|
|
172
|
+
"description": apiValue(environment.applicationContext?.description)
|
|
173
|
+
]
|
|
128
174
|
) { _ in }
|
|
129
175
|
}
|
|
130
176
|
|
|
131
177
|
private func onPressClose() {
|
|
132
178
|
environment.composeApi?.request(
|
|
133
179
|
funcName: "dismissAll",
|
|
134
|
-
params:
|
|
180
|
+
params: nil
|
|
135
181
|
) { _ in }
|
|
136
182
|
}
|
|
137
183
|
|
|
138
184
|
private func onPressMore() {
|
|
139
|
-
let context = environment.applicationContext
|
|
140
185
|
let params: [String: Any] = [
|
|
141
|
-
"useSystemTools": headerRight?.useSystemTools,
|
|
186
|
+
"useSystemTools": headerRight?.useSystemTools ?? true,
|
|
142
187
|
"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
|
-
]
|
|
188
|
+
"context": contextMap
|
|
154
189
|
]
|
|
155
190
|
environment.composeApi?.request(
|
|
156
191
|
funcName: "showTools",
|
|
157
192
|
params: params
|
|
158
193
|
) { response in
|
|
159
|
-
|
|
160
|
-
|
|
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)
|
|
194
|
+
if let toolResponse = parseResponse(response) as? String {
|
|
195
|
+
headerRight?.toolCallback?(toolResponse)
|
|
167
196
|
}
|
|
168
197
|
}
|
|
169
198
|
}
|
|
@@ -195,7 +224,7 @@ struct ToolkitHeaderRight: View {
|
|
|
195
224
|
} ?? false
|
|
196
225
|
|
|
197
226
|
HStack(alignment: .center, spacing: 0) {
|
|
198
|
-
if
|
|
227
|
+
if shouldShowShortcut {
|
|
199
228
|
NavigationButton(
|
|
200
229
|
disabled: isLoading,
|
|
201
230
|
icon: navButtonConfig.icon,
|
|
@@ -206,15 +235,17 @@ struct ToolkitHeaderRight: View {
|
|
|
206
235
|
}
|
|
207
236
|
|
|
208
237
|
HStack(alignment: .center, spacing: 0) {
|
|
209
|
-
|
|
210
|
-
.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
238
|
+
if environment.applicationContext != nil {
|
|
239
|
+
Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
|
|
240
|
+
.padding(4)
|
|
241
|
+
.onTapGesture {
|
|
242
|
+
onPressHelpCenter()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
Rectangle()
|
|
246
|
+
.fill(tintColor ?? Colors.black20)
|
|
247
|
+
.frame(width: 0.5, height: 12)
|
|
248
|
+
}
|
|
218
249
|
|
|
219
250
|
Icon(source: "16_basic_home", size: 20, color: tintColor ?? Colors.black17)
|
|
220
251
|
.padding(4)
|
|
@@ -231,6 +262,9 @@ struct ToolkitHeaderRight: View {
|
|
|
231
262
|
)
|
|
232
263
|
.padding(.leading, Spacing.S)
|
|
233
264
|
}
|
|
265
|
+
.onAppear {
|
|
266
|
+
loadFavoriteState()
|
|
267
|
+
}
|
|
234
268
|
}
|
|
235
269
|
}
|
|
236
270
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//
|
|
2
|
+
// BottomTab.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `BottomTab` (navigation/bottomtab/BottomTab.kt).
|
|
6
|
+
// Hosts a set of tabbed screens, each wrapped in `StackScreen` chrome built
|
|
7
|
+
// from that tab's `NavigationOptions`, with a `BottomTabBar` pinned to the
|
|
8
|
+
// bottom. Switching tabs cross-fades + scales the content (parity with Compose).
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
import SwiftUI
|
|
12
|
+
|
|
13
|
+
@available(iOS 16.0, *)
|
|
14
|
+
public struct BottomTab: View {
|
|
15
|
+
private let items: [BottomTabItem]
|
|
16
|
+
private let floatingButton: BottomTabFloatingButton?
|
|
17
|
+
|
|
18
|
+
@EnvironmentObject private var navigator: Navigator
|
|
19
|
+
@State private var selectedIndex: Int = 0
|
|
20
|
+
|
|
21
|
+
public init(items: [BottomTabItem], floatingButton: BottomTabFloatingButton? = nil) {
|
|
22
|
+
self.items = items
|
|
23
|
+
self.floatingButton = floatingButton
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private var bottomInset: CGFloat { safeAreaBottomInset() }
|
|
27
|
+
private var reservedBottom: CGFloat { BOTTOM_TAB_BAR_HEIGHT + bottomInset + Spacing.S }
|
|
28
|
+
|
|
29
|
+
public var body: some View {
|
|
30
|
+
ZStack(alignment: .bottom) {
|
|
31
|
+
// Tab content area, inset above the bar.
|
|
32
|
+
ZStack {
|
|
33
|
+
ForEach(items.indices, id: \.self) { index in
|
|
34
|
+
if index == selectedIndex {
|
|
35
|
+
tabScreen(for: index)
|
|
36
|
+
.transition(.scale(scale: 0.97).combined(with: .opacity))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
.padding(.bottom, reservedBottom)
|
|
41
|
+
.animation(.easeInOut(duration: 0.2), value: selectedIndex)
|
|
42
|
+
|
|
43
|
+
// Tab bar + home-indicator filler.
|
|
44
|
+
VStack(spacing: 0) {
|
|
45
|
+
BottomTabBar(
|
|
46
|
+
items: items,
|
|
47
|
+
floatingButton: floatingButton,
|
|
48
|
+
selectedIndex: selectedIndex,
|
|
49
|
+
onTabSelected: { index in
|
|
50
|
+
if index != selectedIndex { selectedIndex = index }
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
Colors.black01
|
|
54
|
+
.frame(height: bottomInset + Spacing.S)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
58
|
+
.ignoresSafeArea(edges: .bottom)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@ViewBuilder
|
|
62
|
+
private func tabScreen(for index: Int) -> some View {
|
|
63
|
+
let item = items[index]
|
|
64
|
+
let options = item.options ?? NavigationOptions()
|
|
65
|
+
StackScreen(
|
|
66
|
+
backgroundColor: options.backgroundColor,
|
|
67
|
+
headerTransparent: options.headerTransparent,
|
|
68
|
+
fullScreenContent: options.fullScreenContent,
|
|
69
|
+
tintColor: options.tintColor ?? Colors.black17,
|
|
70
|
+
isBack: !options.hiddenBack,
|
|
71
|
+
headerType: options.headerType,
|
|
72
|
+
useAnimated: options.useAnimated,
|
|
73
|
+
title: options.title,
|
|
74
|
+
headerTitle: options.headerTitle,
|
|
75
|
+
titlePosition: options.titlePosition,
|
|
76
|
+
// Tab back: non-first tab returns to the first tab, first tab
|
|
77
|
+
// delegates to the navigator (mirrors Compose's onBackHandler).
|
|
78
|
+
goBack: options.hiddenBack ? nil : {
|
|
79
|
+
if index != 0 { selectedIndex = 0 } else { navigator.onBackSafe() }
|
|
80
|
+
},
|
|
81
|
+
scrollable: options.scrollable,
|
|
82
|
+
scrollToTopCallback: options.scrollToTopCallback,
|
|
83
|
+
useAvoidKeyboard: options.useAvoidKeyboard,
|
|
84
|
+
keyboardShouldPersistTaps: options.keyboardShouldPersistTaps,
|
|
85
|
+
footer: options.footer,
|
|
86
|
+
headerRight: options.resolvedHeaderRight,
|
|
87
|
+
animatedHeader: options.animatedHeader,
|
|
88
|
+
layoutOffset: options.layoutOffset,
|
|
89
|
+
inputSearchProps: options.inputSearchProps,
|
|
90
|
+
fabProps: options.fabProps,
|
|
91
|
+
content: { item.screen() }
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Bottom safe-area inset (home indicator).
|
|
97
|
+
@available(iOS 16.0, *)
|
|
98
|
+
private func safeAreaBottomInset() -> CGFloat {
|
|
99
|
+
let bottomInset = UIApplication.shared.connectedScenes
|
|
100
|
+
.compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
|
|
101
|
+
.first ?? 0
|
|
102
|
+
return min(bottomInset, 21)
|
|
103
|
+
}
|