@momo-kits/native-kits 0.300.3-debug → 0.300.4-debug
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +51 -7
- package/README.md +2 -2
- package/compose/build.gradle.kts +10 -2
- package/compose/build.gradle.kts.backup +8 -0
- package/compose/compose.podspec +1 -3
- package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +4 -3
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +351 -111
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +20 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +4 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +9 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +11 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +10 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +6 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +55 -22
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +7 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +13 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +13 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +3 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +14 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +19 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +5 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +7 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +20 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +10 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +6 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +6 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +197 -60
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +132 -83
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +54 -62
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +122 -24
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +34 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +3 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +113 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +37 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +13 -0
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +44 -0
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +15 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +3 -1
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
- package/gradle/libs.versions.toml +3 -14
- package/gradle/wrapper/gradle-wrapper.properties +0 -2
- package/gradle.properties +3 -5
- package/ios/Application/ApplicationEnvironment.swift +24 -6
- package/ios/Application/DesignSystemKits.swift +100 -0
- package/ios/Application/DesignSystemKitsPayload.swift +68 -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 +8 -2
- 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 +171 -148
- package/ios/Input/InputPhoneNumber.swift +10 -9
- package/ios/Input/InputSearch.swift +107 -78
- package/ios/Input/InputTextArea.swift +133 -148
- package/ios/InputDropDown/InputDropDown.swift +10 -7
- package/ios/InputMoney/InputMoney.swift +32 -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 +415 -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 +138 -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 +7 -3
- 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 +109 -55
- package/ios/Title/Title.swift +6 -17
- package/ios/Tooltip/Tooltip.swift +29 -14
- package/ios/Typography/FontScale.swift +17 -14
- package/ios/Typography/Text.swift +156 -20
- package/ios/Typography/Typography.swift +2 -2
- package/ios/Uploader/Uploader.swift +3 -1
- package/ios/native-kits.podspec +2 -3
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -3
- 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/publish.sh +19 -2
- package/scripts/gen-ios-icons.mjs +36 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
- 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/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/useHeaderSearchAnimation.kt +0 -69
- 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/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/Typography/FontScaleStore.swift +0 -58
- package/ios/Typography/KitDesignSystem.swift +0 -17
- /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Components.swift
|
|
3
|
-
// Pods
|
|
4
|
-
//
|
|
5
|
-
// Created by sophia on 24/1/25.
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
import SwiftUI
|
|
9
|
-
|
|
10
|
-
// MARK: - Enums
|
|
11
|
-
|
|
12
|
-
public enum TitlePosition {
|
|
13
|
-
case left, center
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
public enum HeaderType {
|
|
17
|
-
case `default`, extended, none
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
public enum AnimatedHeaderRatio {
|
|
21
|
-
case ratio16_9
|
|
22
|
-
case ratio1_1
|
|
23
|
-
case ratio3_2
|
|
24
|
-
|
|
25
|
-
var value: CGFloat {
|
|
26
|
-
switch self {
|
|
27
|
-
case .ratio16_9: return 16.0 / 9.0
|
|
28
|
-
case .ratio1_1: return 1.0
|
|
29
|
-
case .ratio3_2: return 3.0 / 2.0
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// MARK: - Animated Header Config
|
|
36
|
-
|
|
37
|
-
public struct AnimatedHeader {
|
|
38
|
-
public var aspectRatio: AnimatedHeaderRatio = .ratio16_9
|
|
39
|
-
public var isSurface: Bool = true
|
|
40
|
-
public var composable: (_ scrollState: CGFloat) -> AnyView = { _ in AnyView(EmptyView()) }
|
|
41
|
-
|
|
42
|
-
public init(
|
|
43
|
-
aspectRatio: AnimatedHeaderRatio = .ratio16_9,
|
|
44
|
-
isSurface: Bool = true,
|
|
45
|
-
composable: @escaping (_ scrollState: CGFloat) -> AnyView = { _ in AnyView(EmptyView()) }
|
|
46
|
-
) {
|
|
47
|
-
self.aspectRatio = aspectRatio
|
|
48
|
-
self.isSurface = isSurface
|
|
49
|
-
self.composable = composable
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// MARK: - Utils
|
|
54
|
-
|
|
55
|
-
func safeAreaTopInset() -> CGFloat {
|
|
56
|
-
if #available(iOS 15.0, *) {
|
|
57
|
-
return UIApplication.shared.connectedScenes
|
|
58
|
-
.compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.top }
|
|
59
|
-
.first ?? 0
|
|
60
|
-
} else {
|
|
61
|
-
return UIApplication.shared.connectedScenes
|
|
62
|
-
.compactMap { ($0 as? UIWindowScene)?.windows.first?.safeAreaInsets.top }
|
|
63
|
-
.first ?? 0
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// MARK: - Header Background
|
|
68
|
-
|
|
69
|
-
public struct HeaderBackground: View {
|
|
70
|
-
var headerType: HeaderType = .default
|
|
71
|
-
var scrollState: CGFloat
|
|
72
|
-
var headerTransparent: Bool = false
|
|
73
|
-
var fullScreenContent: Bool = false
|
|
74
|
-
|
|
75
|
-
public init(
|
|
76
|
-
headerType: HeaderType = .default,
|
|
77
|
-
scrollState: CGFloat = 0,
|
|
78
|
-
headerTransparent: Bool = false,
|
|
79
|
-
fullScreenContent: Bool = false
|
|
80
|
-
) {
|
|
81
|
-
self.headerType = headerType
|
|
82
|
-
self.scrollState = scrollState
|
|
83
|
-
self.headerTransparent = headerTransparent
|
|
84
|
-
self.fullScreenContent = fullScreenContent
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
public var body: some View {
|
|
88
|
-
let statusBarHeight = safeAreaTopInset()
|
|
89
|
-
let opacity = max(0, 1 - scrollState / 200)
|
|
90
|
-
let height = fullScreenContent ? 0 : statusBarHeight + 52
|
|
91
|
-
let backgroundColor = headerTransparent ? Color.clear : Colors.black01
|
|
92
|
-
|
|
93
|
-
Group {
|
|
94
|
-
switch headerType {
|
|
95
|
-
case .default:
|
|
96
|
-
ZStack(alignment: .bottom) {
|
|
97
|
-
Rectangle()
|
|
98
|
-
.fill(backgroundColor)
|
|
99
|
-
.opacity(headerTransparent ? 0 : opacity)
|
|
100
|
-
.frame(height: height)
|
|
101
|
-
.shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
|
|
102
|
-
.background(backgroundColor)
|
|
103
|
-
.overlay(
|
|
104
|
-
headerTransparent ? AnyView(EmptyView()) :
|
|
105
|
-
AnyView(
|
|
106
|
-
Rectangle()
|
|
107
|
-
.fill(LinearGradient(
|
|
108
|
-
gradient: Gradient(colors: [
|
|
109
|
-
Color(red: 1, green: 0.8, blue: 0.87),
|
|
110
|
-
Color(red: 1, green: 0.8, blue: 0.87).opacity(0.5)
|
|
111
|
-
]),
|
|
112
|
-
startPoint: .top,
|
|
113
|
-
endPoint: .bottom))
|
|
114
|
-
.opacity(opacity)
|
|
115
|
-
.frame(height: height))
|
|
116
|
-
)
|
|
117
|
-
if !headerTransparent {
|
|
118
|
-
Rectangle()
|
|
119
|
-
.fill(Color.black.opacity(0.1))
|
|
120
|
-
.frame(height: 1)
|
|
121
|
-
.frame(maxWidth: .infinity)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
case .extended:
|
|
126
|
-
ZStack {
|
|
127
|
-
if !headerTransparent {
|
|
128
|
-
Colors.black01
|
|
129
|
-
LinearGradient(
|
|
130
|
-
gradient: Gradient(colors: [
|
|
131
|
-
Color(red: 1, green: 0.8, blue: 0.87),
|
|
132
|
-
Color(red: 1, green: 0.8, blue: 0.87).opacity(0)
|
|
133
|
-
]),
|
|
134
|
-
startPoint: .top,
|
|
135
|
-
endPoint: .bottom
|
|
136
|
-
)
|
|
137
|
-
.opacity(opacity)
|
|
138
|
-
.frame(height: 154)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
case .none:
|
|
143
|
-
EmptyView()
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// MARK: - Bottom-tab root preference
|
|
150
|
-
|
|
151
|
-
/// Set by `BottomTab` on appear so its enclosing `StackScreen` can detect that
|
|
152
|
-
/// it's hosting a self-contained bottom-tab surface and skip its own bottom
|
|
153
|
-
/// safe-area reservation — mirrors Compose's `Navigation.markAsBottomTabRoot()`
|
|
154
|
-
/// (`navigation.isBottomTabRoot`, read by `StackScreen.kt`'s `MainContent`).
|
|
155
|
-
/// Without this, `BottomTab`'s own internal safe-area handling gets an extra,
|
|
156
|
-
/// unwanted inset stacked on top of it by the outer `StackScreen`, pushing the
|
|
157
|
-
/// tab bar up higher than the true bottom inset.
|
|
158
|
-
public struct IsBottomTabRootKey: PreferenceKey {
|
|
159
|
-
public static var defaultValue: Bool = false
|
|
160
|
-
public static func reduce(value: inout Bool, nextValue: () -> Bool) {
|
|
161
|
-
value = value || nextValue()
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// MARK: - Header View
|
|
166
|
-
|
|
167
|
-
public struct Header: View {
|
|
168
|
-
var headerType: HeaderType = .default
|
|
169
|
-
var titlePosition: TitlePosition
|
|
170
|
-
var title: String
|
|
171
|
-
/// Optional rich title (mirrors Compose's `HeaderTitle`); `.user` renders an
|
|
172
|
-
/// avatar group instead of `title`. Defaults to nil → plain `title` text.
|
|
173
|
-
var headerTitle: HeaderTitle?
|
|
174
|
-
var headerRight: (()->any View)? = {HeaderRight()}
|
|
175
|
-
var goBack: (() -> Void)?
|
|
176
|
-
var opacity: CGFloat = 1
|
|
177
|
-
var animatedHeader: AnimatedHeader?
|
|
178
|
-
var scrollState: CGFloat = 0
|
|
179
|
-
var inputSearchProps: InputSearchProps?
|
|
180
|
-
var tintColor: Color?
|
|
181
|
-
/// Title opacity; used by the animated search header to fade the title out on scroll.
|
|
182
|
-
var titleOpacity: CGFloat = 1
|
|
183
|
-
|
|
184
|
-
public init(
|
|
185
|
-
headerType: HeaderType = .default,
|
|
186
|
-
titlePosition: TitlePosition,
|
|
187
|
-
title: String,
|
|
188
|
-
headerTitle: HeaderTitle? = nil,
|
|
189
|
-
headerRight: (()->any View)? = {HeaderRight()},
|
|
190
|
-
goBack: (() -> Void)? = nil,
|
|
191
|
-
opacity: CGFloat = 1,
|
|
192
|
-
animatedHeader: AnimatedHeader? = nil,
|
|
193
|
-
scrollState: CGFloat = 0,
|
|
194
|
-
inputSearchProps: InputSearchProps? = nil,
|
|
195
|
-
tintColor: Color? = nil,
|
|
196
|
-
titleOpacity: CGFloat = 1
|
|
197
|
-
) {
|
|
198
|
-
self.headerType = headerType
|
|
199
|
-
self.titlePosition = titlePosition
|
|
200
|
-
self.title = title
|
|
201
|
-
self.headerTitle = headerTitle
|
|
202
|
-
self.headerRight = headerRight
|
|
203
|
-
self.goBack = goBack
|
|
204
|
-
self.opacity = opacity
|
|
205
|
-
self.animatedHeader = animatedHeader
|
|
206
|
-
self.scrollState = scrollState
|
|
207
|
-
self.inputSearchProps = inputSearchProps
|
|
208
|
-
self.tintColor = tintColor
|
|
209
|
-
self.titleOpacity = titleOpacity
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
public var body: some View {
|
|
213
|
-
|
|
214
|
-
let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
|
|
215
|
-
let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
|
|
216
|
-
|
|
217
|
-
if headerType != .none {
|
|
218
|
-
VStack(spacing: 0) {
|
|
219
|
-
ZStack {
|
|
220
|
-
// MARK: - Left and Right Controls
|
|
221
|
-
HStack {
|
|
222
|
-
if let goBack = goBack {
|
|
223
|
-
SwiftUI.Button(action: goBack) {
|
|
224
|
-
Circle()
|
|
225
|
-
.stroke(borderColor, lineWidth: 0.2)
|
|
226
|
-
.background(Circle().fill(backgroundButtonColor))
|
|
227
|
-
.frame(width: 28, height: 28)
|
|
228
|
-
.overlay(
|
|
229
|
-
Icon(source: "arrow-back", size: 20, color: tintColor ?? Colors.black17)
|
|
230
|
-
)
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
Spacer()
|
|
235
|
-
|
|
236
|
-
if let headerRight = headerRight {
|
|
237
|
-
AnyView(headerRight())
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
.padding(.horizontal, 12)
|
|
241
|
-
|
|
242
|
-
// MARK: - Title or Search
|
|
243
|
-
HStack {
|
|
244
|
-
if titlePosition == .left && goBack != nil {
|
|
245
|
-
Spacer().frame(width: 38)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if let inputProps = inputSearchProps {
|
|
249
|
-
InputSearch(props: inputProps)
|
|
250
|
-
} else if case let .user(userData)? = headerTitle {
|
|
251
|
-
if #available(iOS 16.0, *) {
|
|
252
|
-
HeaderUserView(userData)
|
|
253
|
-
}
|
|
254
|
-
} else {
|
|
255
|
-
MomoText(headerTitle?.defaultTitle ?? title, typography: .headerDefaultBold, color: tintColor ?? Colors.black17, maxLines: 1)
|
|
256
|
-
.opacity(titleOpacity)
|
|
257
|
-
.frame(maxWidth: titlePosition == .center ? UIScreen.main.bounds.width - 252 : nil)
|
|
258
|
-
.frame(maxWidth: titlePosition == .center ? .infinity : UIScreen.main.bounds.width - 172, alignment: titlePosition == .center ? .center : .leading)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
Spacer()
|
|
262
|
-
}
|
|
263
|
-
.padding(.horizontal)
|
|
264
|
-
}
|
|
265
|
-
.background(Color.clear)
|
|
266
|
-
.frame(height: 52)
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
|
-
public protocol ComposeApi {
|
|
4
|
-
func request(funcName: String, params: Any, _ completion: ((String) -> Void)?)
|
|
5
|
-
func request(funcName: String, params: Any) -> String
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// Default implementation
|
|
9
|
-
public class ComposeApiImpl: ComposeApi {
|
|
10
|
-
public static let shared = ComposeApiImpl()
|
|
11
|
-
|
|
12
|
-
private init() {}
|
|
13
|
-
|
|
14
|
-
public func request(funcName: String, params: Any, _ completion: ((String) -> Void)?) {
|
|
15
|
-
// Implement your API request logic here
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
public func request(funcName: String, params: Any) -> String {
|
|
19
|
-
// Implement your API request logic here
|
|
20
|
-
return ""
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,306 +0,0 @@
|
|
|
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
|
-
// onToolAction needs the app context (appId/code) so the host knows which app to (un)favorite;
|
|
109
|
-
// without it the favorite is a no-op. Mirrors onPressMore below and the Compose HeaderRight.
|
|
110
|
-
let context = environment.applicationContext
|
|
111
|
-
environment.composeApi?.request(
|
|
112
|
-
funcName: "onToolAction",
|
|
113
|
-
params: [
|
|
114
|
-
"item": ["key": "onFavorite"],
|
|
115
|
-
"context": [
|
|
116
|
-
"appId": context?.appId,
|
|
117
|
-
"code": context?.appCode,
|
|
118
|
-
"name": context?.appName,
|
|
119
|
-
"icon": context?.appIcon,
|
|
120
|
-
"description": context?.description,
|
|
121
|
-
"support": context?.support,
|
|
122
|
-
"toolkitConfig": context?.toolkitConfig,
|
|
123
|
-
"providerId": context?.providerId,
|
|
124
|
-
"permissions": context?.permissions
|
|
125
|
-
]
|
|
126
|
-
]
|
|
127
|
-
) { _ in
|
|
128
|
-
isFavorite.toggle()
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private func onPressHelpCenter() {
|
|
133
|
-
let context = environment.applicationContext
|
|
134
|
-
let paramMap: [String: Any] = [
|
|
135
|
-
"appId": context?.appId,
|
|
136
|
-
"code": context?.appCode,
|
|
137
|
-
"name": context?.appName,
|
|
138
|
-
"icon": context?.appIcon,
|
|
139
|
-
"description": context?.description
|
|
140
|
-
]
|
|
141
|
-
environment.composeApi?.request(
|
|
142
|
-
funcName: "showHelpCenter",
|
|
143
|
-
params: paramMap
|
|
144
|
-
) { _ in }
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
private func onPressClose() {
|
|
148
|
-
environment.composeApi?.request(
|
|
149
|
-
funcName: "dismissAll",
|
|
150
|
-
params: ""
|
|
151
|
-
) { _ in }
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
private func onPressMore() {
|
|
155
|
-
let context = environment.applicationContext
|
|
156
|
-
let params: [String: Any] = [
|
|
157
|
-
"useSystemTools": headerRight?.useSystemTools,
|
|
158
|
-
"tools": headerRight?.tools.map { $0.toMap() } ?? [],
|
|
159
|
-
"context": [
|
|
160
|
-
"appId": context?.appId,
|
|
161
|
-
"code": context?.appCode,
|
|
162
|
-
"name": context?.appName,
|
|
163
|
-
"icon": context?.appIcon,
|
|
164
|
-
"description": context?.description,
|
|
165
|
-
"support": context?.support,
|
|
166
|
-
"toolkitConfig": context?.toolkitConfig,
|
|
167
|
-
"providerId": context?.providerId,
|
|
168
|
-
"permissions": context?.permissions
|
|
169
|
-
]
|
|
170
|
-
]
|
|
171
|
-
environment.composeApi?.request(
|
|
172
|
-
funcName: "showTools",
|
|
173
|
-
params: params
|
|
174
|
-
) { response in
|
|
175
|
-
do {
|
|
176
|
-
if let jsonData = response.data(using: .utf8),
|
|
177
|
-
let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
|
|
178
|
-
let toolResponse = json["response"] as? String {
|
|
179
|
-
headerRight?.toolCallback?(toolResponse)
|
|
180
|
-
}
|
|
181
|
-
} catch {
|
|
182
|
-
print("Error parsing response:", error)
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Load the current favorite state on appear so the star reflects reality; without this the button
|
|
188
|
-
// always renders "not favorited" until the user taps it. Mirrors the Compose LaunchedEffect.
|
|
189
|
-
private func loadFavoriteState() {
|
|
190
|
-
let context = environment.applicationContext
|
|
191
|
-
environment.composeApi?.request(
|
|
192
|
-
funcName: "isFavoriteApp",
|
|
193
|
-
params: ["code": context?.appCode]
|
|
194
|
-
) { response in
|
|
195
|
-
guard let data = response.data(using: .utf8),
|
|
196
|
-
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
197
|
-
return
|
|
198
|
-
}
|
|
199
|
-
isFavorite = (json["response"] as? Bool) ?? false
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
private func getNavigationButtonConfig() -> NavigationButtonConfig {
|
|
204
|
-
let totalTools = headerRight?.tools.reduce(0) { $0 + $1.items.count } ?? 0
|
|
205
|
-
var icon = isFavorite ? "pin_star_checked" : "pin_star"
|
|
206
|
-
var onClickHandler: () -> Void = onPressShortcut
|
|
207
|
-
|
|
208
|
-
if totalTools > 1 || headerRight?.useMore == true {
|
|
209
|
-
icon = "navigation_more_icon"
|
|
210
|
-
onClickHandler = onPressMore
|
|
211
|
-
} else if totalTools == 1, let singleTool = headerRight?.tools.first?.items.first {
|
|
212
|
-
icon = singleTool.icon
|
|
213
|
-
onClickHandler = {
|
|
214
|
-
headerRight?.toolCallback?(singleTool.key)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return NavigationButtonConfig(icon: icon, onPress: onClickHandler)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
var body: some View {
|
|
222
|
-
let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
|
|
223
|
-
let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
|
|
224
|
-
let navButtonConfig = getNavigationButtonConfig()
|
|
225
|
-
let showBadge = headerRight?.tools.contains { group in
|
|
226
|
-
group.items.contains { $0.showBadge }
|
|
227
|
-
} ?? false
|
|
228
|
-
|
|
229
|
-
HStack(alignment: .center, spacing: 0) {
|
|
230
|
-
if headerRight?.useShortcut == true {
|
|
231
|
-
NavigationButton(
|
|
232
|
-
disabled: isLoading,
|
|
233
|
-
icon: navButtonConfig.icon,
|
|
234
|
-
showBadge: showBadge,
|
|
235
|
-
onClick: navButtonConfig.onPress,
|
|
236
|
-
tintColor: tintColor
|
|
237
|
-
)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
HStack(alignment: .center, spacing: 0) {
|
|
241
|
-
Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
|
|
242
|
-
.padding(4)
|
|
243
|
-
.onTapGesture {
|
|
244
|
-
onPressHelpCenter()
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
Rectangle()
|
|
248
|
-
.fill(tintColor ?? Colors.black20)
|
|
249
|
-
.frame(width: 0.5, height: 12)
|
|
250
|
-
|
|
251
|
-
Icon(source: "16_basic_home", size: 20, color: tintColor ?? Colors.black17)
|
|
252
|
-
.padding(4)
|
|
253
|
-
.onTapGesture {
|
|
254
|
-
onPressClose()
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
.frame(width: 65, height: 28)
|
|
258
|
-
.background(backgroundButtonColor)
|
|
259
|
-
.cornerRadius(14)
|
|
260
|
-
.overlay(
|
|
261
|
-
RoundedRectangle(cornerRadius: 14)
|
|
262
|
-
.stroke(borderColor, lineWidth: 0.2)
|
|
263
|
-
)
|
|
264
|
-
.padding(.leading, Spacing.S)
|
|
265
|
-
}
|
|
266
|
-
.onAppear {
|
|
267
|
-
loadFavoriteState()
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
struct NavigationButton: View {
|
|
273
|
-
var disabled: Bool
|
|
274
|
-
var icon: String
|
|
275
|
-
var showBadge: Bool
|
|
276
|
-
var onClick: () -> Void
|
|
277
|
-
var tintColor: Color?
|
|
278
|
-
|
|
279
|
-
var body: some View {
|
|
280
|
-
let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
|
|
281
|
-
let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
|
|
282
|
-
|
|
283
|
-
ZStack {
|
|
284
|
-
Circle()
|
|
285
|
-
.fill(backgroundButtonColor)
|
|
286
|
-
.overlay(
|
|
287
|
-
Circle()
|
|
288
|
-
.stroke(borderColor, lineWidth: 0.2)
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
Icon(source: icon, size: 16, color: tintColor)
|
|
292
|
-
|
|
293
|
-
if showBadge {
|
|
294
|
-
BadgeDot(size: .small)
|
|
295
|
-
.offset(x: -2, y: -2)
|
|
296
|
-
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
.frame(width: 28, height: 28)
|
|
300
|
-
.onTapGesture {
|
|
301
|
-
if !disabled {
|
|
302
|
-
onClick()
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
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
|
-
.preference(key: IsBottomTabRootKey.self, value: true)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
@ViewBuilder
|
|
63
|
-
private func tabScreen(for index: Int) -> some View {
|
|
64
|
-
let item = items[index]
|
|
65
|
-
let options = item.options ?? NavigationOptions()
|
|
66
|
-
StackScreen(
|
|
67
|
-
backgroundColor: options.backgroundColor,
|
|
68
|
-
headerTransparent: options.headerTransparent,
|
|
69
|
-
fullScreenContent: options.fullScreenContent,
|
|
70
|
-
tintColor: options.tintColor ?? Colors.black17,
|
|
71
|
-
isBack: !options.hiddenBack,
|
|
72
|
-
headerType: options.headerType,
|
|
73
|
-
useAnimated: options.useAnimated,
|
|
74
|
-
title: options.title,
|
|
75
|
-
headerTitle: options.headerTitle,
|
|
76
|
-
titlePosition: options.titlePosition,
|
|
77
|
-
// Tab back: non-first tab returns to the first tab, first tab
|
|
78
|
-
// delegates to the navigator (mirrors Compose's onBackHandler).
|
|
79
|
-
goBack: options.hiddenBack ? nil : {
|
|
80
|
-
if index != 0 { selectedIndex = 0 } else { navigator.onBackSafe() }
|
|
81
|
-
},
|
|
82
|
-
scrollable: options.scrollable,
|
|
83
|
-
scrollToTopCallback: options.scrollToTopCallback,
|
|
84
|
-
useAvoidKeyboard: options.useAvoidKeyboard,
|
|
85
|
-
keyboardShouldPersistTaps: options.keyboardShouldPersistTaps,
|
|
86
|
-
footer: options.footer,
|
|
87
|
-
headerRight: options.resolvedHeaderRight,
|
|
88
|
-
animatedHeader: options.animatedHeader,
|
|
89
|
-
layoutOffset: options.layoutOffset,
|
|
90
|
-
inputSearchProps: options.inputSearchProps,
|
|
91
|
-
fabProps: options.fabProps,
|
|
92
|
-
content: { item.screen() }
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/// Bottom safe-area inset (home indicator).
|
|
98
|
-
@available(iOS 16.0, *)
|
|
99
|
-
private func safeAreaBottomInset() -> CGFloat {
|
|
100
|
-
let bottomInset = UIApplication.shared.connectedScenes
|
|
101
|
-
.compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
|
|
102
|
-
.first ?? 0
|
|
103
|
-
return min(bottomInset, 21)
|
|
104
|
-
}
|