@momo-kits/native-kits 0.167.1-duo.1-debug → 0.167.1-duo.10-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 +32 -0
- package/compose/build.gradle.kts +15 -1
- package/compose/build.gradle.kts.backup +14 -0
- package/compose/compose.podspec +1 -1
- 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/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/Divider.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 +53 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -2
- 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/layout/Card.kt +13 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +21 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +232 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +11 -9
- 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/NavigationContainer.kt +4 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +143 -31
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +100 -66
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +22 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +98 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +312 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +66 -18
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +23 -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 +81 -24
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +185 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +13 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +4 -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 +86 -0
- package/gradle.properties +1 -1
- package/ios/Application/ApplicationEnvironment.swift +17 -0
- package/ios/Application/ComposeApi.swift +2 -1
- package/ios/Application/{Components.swift → LegacyHeader.swift} +7 -65
- package/ios/Application/Screen.swift +3 -2
- package/ios/Avatar/Avatar.swift +4 -3
- package/ios/Badge/Badge.swift +6 -4
- package/ios/Badge/BadgeDot.swift +1 -1
- package/ios/Badge/BadgeRibbon.swift +0 -1
- package/ios/Button/Button.swift +15 -12
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
- package/ios/Carousel/Carousel.swift +29 -12
- package/ios/Checkbox/Checkbox.swift +7 -3
- package/ios/Chip/Chip.swift +5 -4
- package/ios/Collapse/Collapse.swift +70 -19
- package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
- package/ios/DateTimePicker/DateTimePicker.swift +54 -20
- package/ios/DateTimePicker/WheelPicker.swift +103 -105
- package/ios/Divider/Divider.swift +37 -4
- package/ios/Extensions/Color++.swift +38 -9
- package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
- package/ios/Extensions/SkeletonUICompat.swift +11 -0
- package/ios/Icon/Icon.swift +21 -23
- package/ios/Icon/Icons.swift +1329 -0
- package/ios/IconButton/IconButton.swift +15 -7
- package/ios/Image/Image.swift +7 -23
- package/ios/Information/Information.swift +27 -9
- package/ios/Input/ErrorView.swift +2 -2
- package/ios/Input/Input.swift +160 -147
- package/ios/Input/InputPhoneNumber.swift +12 -9
- package/ios/Input/InputSearch.swift +102 -75
- package/ios/Input/InputTextArea.swift +128 -145
- package/ios/InputDropDown/InputDropDown.swift +10 -7
- package/ios/InputMoney/InputMoney.swift +33 -11
- package/ios/InputOTP/InputOTP.swift +22 -12
- package/ios/Layout/Card.swift +50 -0
- package/ios/Layout/GridContext.swift +123 -3
- package/ios/Layout/Item.swift +5 -4
- package/ios/Layout/ScreenLayout.swift +312 -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 +97 -8
- package/ios/Navigation/BottomTab/BottomTab.swift +187 -0
- package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +15 -7
- package/ios/Navigation/BottomTab/DuoBarCompression.swift +105 -0
- package/ios/Navigation/BottomTab/DuoTabBar.swift +304 -0
- package/ios/{Application → Navigation/Component}/FloatingButton.swift +14 -3
- package/ios/Navigation/Component/Header.swift +493 -0
- package/ios/Navigation/Component/HeaderBackground.swift +134 -0
- package/ios/Navigation/Component/HeaderJourney.swift +73 -0
- package/ios/Navigation/Component/HeaderLocation.swift +48 -0
- package/ios/Navigation/Component/HeaderRight.swift +386 -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 +176 -0
- package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +5 -5
- package/ios/Navigation/HostBackGestureBridge.swift +57 -0
- package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +5 -1
- package/ios/Navigation/Navigation.swift +190 -0
- package/ios/{Application/Navigation → Navigation}/NavigationContainer.swift +92 -81
- package/ios/{Application/Navigation → Navigation}/Navigator.swift +185 -36
- package/ios/Navigation/StackScreen.swift +782 -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 -136
- package/ios/Popup/PopupInput.swift +10 -13
- package/ios/Popup/PopupLayout.swift +140 -0
- package/ios/Popup/PopupNotify.swift +56 -73
- package/ios/Popup/PopupPromotion.swift +31 -47
- package/ios/PopupView/FullscreenPopup.swift +13 -19
- package/ios/PopupView/PopupView.swift +1 -1
- package/ios/PopupView/Utils++.swift +0 -8
- package/ios/ProgressInfo/ProgressInfo.swift +4 -2
- package/ios/Radio/Radio.swift +2 -1
- package/ios/Rating/Rating.swift +9 -1
- package/ios/Resources/dot_loading.json +558 -0
- package/ios/Resources/lottie_circle_loader.json +1 -0
- package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
- package/ios/Skeleton/Skeleton.swift +22 -27
- package/ios/Slider/Slider.swift +16 -18
- package/ios/Stepper/Stepper.swift +6 -1
- package/ios/Steps/Steps.swift +8 -6
- package/ios/SuggestAction/SuggestAction.swift +5 -0
- package/ios/Swipeable/SwipeCell.swift +10 -6
- package/ios/Switch/Switch.swift +22 -20
- package/ios/TabView/TabView.swift +102 -21
- package/ios/Tag/Tag.swift +15 -4
- package/ios/Template/Logo/Logo.swift +2 -1
- package/ios/Template/TrustBanner/TrustBanner.swift +106 -54
- package/ios/Title/Title.swift +6 -17
- package/ios/Tooltip/Tooltip.swift +29 -14
- package/ios/Typography/Text.swift +61 -19
- package/ios/Uploader/Uploader.swift +3 -1
- package/ios/native-kits.podspec +2 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
- package/package.json +1 -1
- package/scripts/gen-ios-icons.mjs +36 -0
- package/.claude/settings.local.json +0 -185
- 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/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/StackScreen.swift +0 -492
- package/local.properties +0 -8
- /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HeaderLocation.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/component/HeaderLocation.kt`: a description above a
|
|
6
|
+
// tappable location with a chevron.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
public struct HeaderLocationView: View {
|
|
12
|
+
private let data: HeaderLocationData
|
|
13
|
+
private let tintColor: Color
|
|
14
|
+
|
|
15
|
+
public init(_ data: HeaderLocationData, tintColor: Color) {
|
|
16
|
+
self.data = data
|
|
17
|
+
self.tintColor = tintColor
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Environment(\.screenLayout) private var layout
|
|
21
|
+
|
|
22
|
+
public var body: some View {
|
|
23
|
+
if data.isLoading {
|
|
24
|
+
VStack(alignment: .leading, spacing: Spacing.XXS) {
|
|
25
|
+
titleSkeleton(height: 12)
|
|
26
|
+
titleSkeleton(height: 18)
|
|
27
|
+
}
|
|
28
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
29
|
+
} else {
|
|
30
|
+
let tint = parseHeaderTint(data.tintColor) ?? tintColor
|
|
31
|
+
SwiftUI.Button(action: { data.onPress?() }) {
|
|
32
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
33
|
+
if let description = data.description, !description.isEmpty {
|
|
34
|
+
MomoText(description, typography: .descriptionXsRegular, color: tint, maxLines: 1)
|
|
35
|
+
}
|
|
36
|
+
HStack(spacing: 0) {
|
|
37
|
+
MomoText(data.location, typography: .actionXsBold, color: tint, maxLines: 1)
|
|
38
|
+
Spacer().frame(width: Spacing.XS)
|
|
39
|
+
Icon(source: "16_arrow_chevron_down_small", size: 16, color: tint)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
.frame(maxWidth: headerTitleMaxWidth(layout), alignment: .leading)
|
|
43
|
+
}
|
|
44
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
45
|
+
.disabled(data.onPress == nil)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HeaderRight.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/component/HeaderRight.kt`: the mini-app toolkit
|
|
6
|
+
// (shortcut / more / single tool, help center and close) in the bar or the iPhone Duo side bar.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
/// Mirrors Compose's `HeaderRight.Toolkit`.
|
|
12
|
+
public struct HeaderRightData {
|
|
13
|
+
var useShortcut: Bool
|
|
14
|
+
var useMore: Bool
|
|
15
|
+
var tools: [ToolGroup]
|
|
16
|
+
var toolCallback: ((String) -> Void)?
|
|
17
|
+
var useSystemTools: Bool
|
|
18
|
+
/// Close dismisses only this mini app instead of returning home.
|
|
19
|
+
var useCloseIcon: Bool
|
|
20
|
+
|
|
21
|
+
public init(
|
|
22
|
+
useShortcut: Bool = false,
|
|
23
|
+
useMore: Bool = false,
|
|
24
|
+
tools: [ToolGroup] = [],
|
|
25
|
+
toolCallback: ((String) -> Void)? = nil,
|
|
26
|
+
useSystemTools: Bool = true,
|
|
27
|
+
useCloseIcon: Bool = false
|
|
28
|
+
) {
|
|
29
|
+
self.useShortcut = useShortcut
|
|
30
|
+
self.useMore = useMore
|
|
31
|
+
self.tools = tools
|
|
32
|
+
self.toolCallback = toolCallback
|
|
33
|
+
self.useSystemTools = useSystemTools
|
|
34
|
+
self.useCloseIcon = useCloseIcon
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
var closeIcon: String { useCloseIcon ? "16_navigation_close_circle" : "16_basic_home" }
|
|
38
|
+
|
|
39
|
+
var showBadge: Bool { tools.contains { group in group.items.contains { $0.showBadge } } }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public struct Tool {
|
|
43
|
+
var key: String
|
|
44
|
+
var icon: String
|
|
45
|
+
var showBadge: Bool
|
|
46
|
+
var name: [String: String]
|
|
47
|
+
var showRightIcon: Bool
|
|
48
|
+
|
|
49
|
+
public init(
|
|
50
|
+
key: String,
|
|
51
|
+
icon: String,
|
|
52
|
+
name: [String: String] = [:],
|
|
53
|
+
showBadge: Bool = false,
|
|
54
|
+
showRightIcon: Bool = true
|
|
55
|
+
) {
|
|
56
|
+
self.key = key
|
|
57
|
+
self.icon = icon
|
|
58
|
+
self.showBadge = showBadge
|
|
59
|
+
self.name = name
|
|
60
|
+
self.showRightIcon = showRightIcon
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func toMap() -> [String: Any] {
|
|
64
|
+
[
|
|
65
|
+
"key": key,
|
|
66
|
+
"icon": icon,
|
|
67
|
+
"showBadge": showBadge,
|
|
68
|
+
"name": name,
|
|
69
|
+
"showRightIcon": showRightIcon
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public struct ToolGroup {
|
|
75
|
+
var title: [String: String]
|
|
76
|
+
var items: [Tool]
|
|
77
|
+
|
|
78
|
+
public init(title: [String: String] = [:], items: [Tool]) {
|
|
79
|
+
self.title = title
|
|
80
|
+
self.items = items
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func toMap() -> [String: Any] {
|
|
84
|
+
[
|
|
85
|
+
"title": title,
|
|
86
|
+
"items": items.map { $0.toMap() }
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private let helpCenterAppId = "vn.momo.helpcenter"
|
|
92
|
+
|
|
93
|
+
/// The Help Center mini app hides its own help button; without a context there is nothing to open.
|
|
94
|
+
private func usesHelpCenter(_ context: MiniAppContext?) -> Bool {
|
|
95
|
+
guard let context else { return false }
|
|
96
|
+
return context.appId != helpCenterAppId
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private func showsShortcut(_ data: HeaderRightData, _ context: MiniAppContext?) -> Bool {
|
|
100
|
+
data.useShortcut && !(data.useMore && context == nil)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Height of the vertical toolkit: the optional shortcut, then the help/home capsule.
|
|
104
|
+
func toolkitColumnHeight(_ data: HeaderRightData, context: MiniAppContext?) -> CGFloat {
|
|
105
|
+
let shortcut = showsShortcut(data, context) ? DuoMetrics.controlSize + Spacing.M : 0
|
|
106
|
+
return shortcut + (usesHelpCenter(context) ? 2 * DuoMetrics.itemPitch : DuoMetrics.controlSize)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// A header right that renders the kit toolkit, so the Duo side bar can take it over.
|
|
110
|
+
protocol ToolkitHeaderRightView {
|
|
111
|
+
var headerRight: HeaderRightData? { get }
|
|
112
|
+
var tintColor: Color? { get }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Toolkit data when `headerRight` renders the kit toolkit; nil for a custom header right.
|
|
116
|
+
func toolkitHeaderRight(_ headerRight: (() -> any View)?) -> (data: HeaderRightData?, tintColor: Color?)? {
|
|
117
|
+
guard let toolkit = headerRight?() as? ToolkitHeaderRightView else { return nil }
|
|
118
|
+
return (toolkit.headerRight, toolkit.tintColor)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// MARK: - Public views
|
|
122
|
+
|
|
123
|
+
/// The kit toolkit. Works under `NavigationContainer` (keyed environment) and under hosts that
|
|
124
|
+
/// inject `ApplicationEnvironment` with `.environmentObject`.
|
|
125
|
+
public struct HeaderRight: View, ToolkitHeaderRightView {
|
|
126
|
+
var headerRight: HeaderRightData?
|
|
127
|
+
var tintColor: Color?
|
|
128
|
+
|
|
129
|
+
public init(headerRight: HeaderRightData? = nil, tintColor: Color? = nil) {
|
|
130
|
+
self.headerRight = headerRight
|
|
131
|
+
self.tintColor = tintColor
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public var body: some View {
|
|
135
|
+
Toolkit(headerRight: headerRight ?? HeaderRightData(), tintColor: tintColor)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@available(*, deprecated, renamed: "HeaderRight", message: "HeaderRight now reads both environment styles.")
|
|
140
|
+
public struct NavigationHeaderRight: View, ToolkitHeaderRightView {
|
|
141
|
+
var headerRight: HeaderRightData?
|
|
142
|
+
var tintColor: Color?
|
|
143
|
+
|
|
144
|
+
public init(headerRight: HeaderRightData? = nil, tintColor: Color? = nil) {
|
|
145
|
+
self.headerRight = headerRight
|
|
146
|
+
self.tintColor = tintColor
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public var body: some View {
|
|
150
|
+
Toolkit(headerRight: headerRight ?? HeaderRightData(), tintColor: tintColor)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// MARK: - Toolkit
|
|
155
|
+
|
|
156
|
+
private struct NavigationButtonConfig {
|
|
157
|
+
let icon: String
|
|
158
|
+
let onPress: () -> Void
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
struct Toolkit: View {
|
|
162
|
+
let headerRight: HeaderRightData
|
|
163
|
+
var tintColor: Color?
|
|
164
|
+
/// iPhone Duo side bar column instead of the bar row.
|
|
165
|
+
var vertical: Bool = false
|
|
166
|
+
|
|
167
|
+
@State private var isFavorite = false
|
|
168
|
+
@State private var isLoading = false
|
|
169
|
+
@OptionalEnvironmentObject private var injectedEnvironment: ApplicationEnvironment?
|
|
170
|
+
@Environment(\.applicationEnvironment) private var keyedEnvironment
|
|
171
|
+
@Environment(\.headerColor) private var barColor
|
|
172
|
+
|
|
173
|
+
private var environment: ApplicationEnvironment { injectedEnvironment ?? keyedEnvironment }
|
|
174
|
+
private var context: MiniAppContext? { environment.applicationContext }
|
|
175
|
+
|
|
176
|
+
/// The bar's colors when inside a header, otherwise derived from the tint like a plain header.
|
|
177
|
+
private var headerColor: HeaderColor {
|
|
178
|
+
barColor ?? getHeaderColor(isAnimated: false, isSurface: true, opacity: 1, tintColor: tintColor)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private func apiValue(_ value: Any?) -> Any { value ?? NSNull() }
|
|
182
|
+
|
|
183
|
+
private var contextMap: [String: Any] {
|
|
184
|
+
[
|
|
185
|
+
"appId": apiValue(context?.appId),
|
|
186
|
+
"code": apiValue(context?.appCode),
|
|
187
|
+
"name": apiValue(context?.appName),
|
|
188
|
+
"icon": apiValue(context?.appIcon),
|
|
189
|
+
"description": apiValue(context?.description),
|
|
190
|
+
"support": apiValue(context?.support),
|
|
191
|
+
"toolkitConfig": apiValue(context?.toolkitConfig),
|
|
192
|
+
"providerId": apiValue(context?.providerId),
|
|
193
|
+
"permissions": apiValue(context?.permissions)
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private func parseResponse(_ response: String) -> Any? {
|
|
198
|
+
guard let data = response.data(using: .utf8),
|
|
199
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
200
|
+
return nil
|
|
201
|
+
}
|
|
202
|
+
return json["response"]
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// MARK: Actions
|
|
206
|
+
|
|
207
|
+
private func loadFavoriteState() {
|
|
208
|
+
_ = environment.composeApi?.request(
|
|
209
|
+
funcName: "isFavoriteApp",
|
|
210
|
+
params: ["code": apiValue(context?.appCode)]
|
|
211
|
+
) { response in
|
|
212
|
+
isFavorite = parseResponse(response) as? Bool == true
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private func onPressShortcut() {
|
|
217
|
+
_ = environment.composeApi?.request(
|
|
218
|
+
funcName: "onToolAction",
|
|
219
|
+
params: ["item": ["key": "onFavorite"], "context": contextMap]
|
|
220
|
+
) { response in
|
|
221
|
+
if let result = parseResponse(response) as? [String: Any], result["success"] as? Bool == true {
|
|
222
|
+
isFavorite.toggle()
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private func onPressMore() {
|
|
228
|
+
_ = environment.composeApi?.request(
|
|
229
|
+
funcName: "showTools",
|
|
230
|
+
params: [
|
|
231
|
+
"useSystemTools": headerRight.useSystemTools,
|
|
232
|
+
"tools": headerRight.tools.map { $0.toMap() },
|
|
233
|
+
"context": contextMap
|
|
234
|
+
]
|
|
235
|
+
) { response in
|
|
236
|
+
if let tool = parseResponse(response) as? String {
|
|
237
|
+
headerRight.toolCallback?(tool)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private func onPressHelpCenter() {
|
|
243
|
+
guard context != nil else { return }
|
|
244
|
+
_ = environment.composeApi?.request(
|
|
245
|
+
funcName: "showHelpCenter",
|
|
246
|
+
params: [
|
|
247
|
+
"appId": apiValue(context?.appId),
|
|
248
|
+
"code": apiValue(context?.appCode),
|
|
249
|
+
"name": apiValue(context?.appName),
|
|
250
|
+
"icon": apiValue(context?.appIcon),
|
|
251
|
+
"description": apiValue(context?.description)
|
|
252
|
+
]
|
|
253
|
+
) { _ in }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private func onPressClose() {
|
|
257
|
+
if headerRight.useCloseIcon {
|
|
258
|
+
environment.requestDismiss()
|
|
259
|
+
} else {
|
|
260
|
+
_ = environment.composeApi?.request(funcName: "dismissAll", params: nil) { _ in }
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private var navigationButtonConfig: NavigationButtonConfig {
|
|
265
|
+
let totalTools = headerRight.tools.reduce(0) { $0 + $1.items.count }
|
|
266
|
+
if totalTools > 1 || headerRight.useMore {
|
|
267
|
+
return NavigationButtonConfig(icon: "navigation_more_icon", onPress: onPressMore)
|
|
268
|
+
}
|
|
269
|
+
if totalTools == 1, let tool = headerRight.tools.first?.items.first {
|
|
270
|
+
return NavigationButtonConfig(icon: tool.icon) { headerRight.toolCallback?(tool.key) }
|
|
271
|
+
}
|
|
272
|
+
return NavigationButtonConfig(icon: isFavorite ? "pin_star_checked" : "pin_star", onPress: onPressShortcut)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// MARK: Body
|
|
276
|
+
|
|
277
|
+
var body: some View {
|
|
278
|
+
Group {
|
|
279
|
+
if vertical {
|
|
280
|
+
verticalBody
|
|
281
|
+
} else {
|
|
282
|
+
horizontalBody
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
.onAppear(perform: loadFavoriteState)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private var verticalBody: some View {
|
|
289
|
+
let config = navigationButtonConfig
|
|
290
|
+
let useHelpCenter = usesHelpCenter(context)
|
|
291
|
+
return VStack(spacing: Spacing.M) {
|
|
292
|
+
if showsShortcut(headerRight, context) {
|
|
293
|
+
SideBarButton(
|
|
294
|
+
icon: config.icon,
|
|
295
|
+
headerColor: headerColor,
|
|
296
|
+
showBadge: headerRight.showBadge,
|
|
297
|
+
disabled: isLoading,
|
|
298
|
+
action: config.onPress
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
SideBarGroup(headerColor: headerColor, itemCount: useHelpCenter ? 2 : 1) {
|
|
302
|
+
if useHelpCenter {
|
|
303
|
+
SideBarGroupItem(
|
|
304
|
+
icon: "help_center",
|
|
305
|
+
headerColor: headerColor,
|
|
306
|
+
accessibilityId: "btn_navigation_help_center",
|
|
307
|
+
action: onPressHelpCenter
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
SideBarGroupItem(
|
|
311
|
+
icon: headerRight.closeIcon,
|
|
312
|
+
headerColor: headerColor,
|
|
313
|
+
accessibilityId: "btn_navigation_close",
|
|
314
|
+
action: onPressClose
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private var horizontalBody: some View {
|
|
321
|
+
let config = navigationButtonConfig
|
|
322
|
+
return HStack(alignment: .center, spacing: 0) {
|
|
323
|
+
if showsShortcut(headerRight, context) {
|
|
324
|
+
NavigationButton(
|
|
325
|
+
disabled: isLoading,
|
|
326
|
+
icon: config.icon,
|
|
327
|
+
showBadge: headerRight.showBadge,
|
|
328
|
+
headerColor: headerColor,
|
|
329
|
+
onClick: config.onPress
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
HStack(alignment: .center, spacing: 0) {
|
|
334
|
+
if usesHelpCenter(context) {
|
|
335
|
+
toolButton(icon: "help_center", accessibilityId: "btn_navigation_help_center", action: onPressHelpCenter)
|
|
336
|
+
Rectangle()
|
|
337
|
+
.fill(tintColor ?? Colors.black20)
|
|
338
|
+
.frame(width: 0.5, height: 12)
|
|
339
|
+
}
|
|
340
|
+
toolButton(icon: headerRight.closeIcon, accessibilityId: "btn_navigation_close", action: onPressClose)
|
|
341
|
+
}
|
|
342
|
+
.frame(width: 65, height: 28)
|
|
343
|
+
.background(headerColor.backgroundButton)
|
|
344
|
+
.cornerRadius(14)
|
|
345
|
+
.overlay(RoundedRectangle(cornerRadius: 14).stroke(headerColor.borderColor, lineWidth: 0.2))
|
|
346
|
+
.padding(.leading, Spacing.S)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private func toolButton(icon: String, accessibilityId: String, action: @escaping () -> Void) -> some View {
|
|
351
|
+
SwiftUI.Button(action: action) {
|
|
352
|
+
Icon(source: icon, size: 20, color: headerColor.tintIconColor)
|
|
353
|
+
.padding(4)
|
|
354
|
+
}
|
|
355
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
356
|
+
.accessibilityIdentifier(accessibilityId)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/// Mirrors Compose's `NavigationButton`: the 28pt circular shortcut / more button.
|
|
361
|
+
struct NavigationButton: View {
|
|
362
|
+
var disabled: Bool
|
|
363
|
+
var icon: String
|
|
364
|
+
var showBadge: Bool
|
|
365
|
+
var headerColor: HeaderColor
|
|
366
|
+
var onClick: () -> Void
|
|
367
|
+
|
|
368
|
+
var body: some View {
|
|
369
|
+
SwiftUI.Button(action: onClick) {
|
|
370
|
+
ZStack {
|
|
371
|
+
Circle()
|
|
372
|
+
.fill(headerColor.backgroundButton)
|
|
373
|
+
.overlay(Circle().stroke(headerColor.borderColor, lineWidth: 0.2))
|
|
374
|
+
Icon(source: icon, size: 20, color: headerColor.tintIconColor)
|
|
375
|
+
}
|
|
376
|
+
.frame(width: 28, height: 28)
|
|
377
|
+
.overlay(alignment: .topTrailing) {
|
|
378
|
+
if showBadge {
|
|
379
|
+
BadgeDot(size: .small).offset(x: -Spacing.XXS, y: -Spacing.XXS)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
384
|
+
.disabled(disabled)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HeaderTitle.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/component/HeaderTitle.kt`: the plain header title.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import SwiftUI
|
|
9
|
+
|
|
10
|
+
/// `actionSBold` at 15/22 in the header family, single line.
|
|
11
|
+
struct HeaderTitleText: View {
|
|
12
|
+
let title: String
|
|
13
|
+
let color: Color
|
|
14
|
+
var centered: Bool = false
|
|
15
|
+
|
|
16
|
+
init(_ title: String, color: Color, centered: Bool = false) {
|
|
17
|
+
self.title = title
|
|
18
|
+
self.color = color
|
|
19
|
+
self.centered = centered
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
var body: some View {
|
|
23
|
+
MomoText(
|
|
24
|
+
title,
|
|
25
|
+
typography: .actionSBold,
|
|
26
|
+
color: color,
|
|
27
|
+
textAlign: centered ? .center : .leading,
|
|
28
|
+
maxLines: 1,
|
|
29
|
+
truncationMode: .tail,
|
|
30
|
+
accessibilityId: "title_navigation_header",
|
|
31
|
+
fontFamily: headerFontFamily
|
|
32
|
+
)
|
|
33
|
+
.fontSize(15, lineHeight: 22)
|
|
34
|
+
.frame(maxWidth: centered ? .infinity : nil, alignment: centered ? .center : .leading)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// HeaderUser.swift
|
|
3
3
|
// MoMoUIKits
|
|
4
4
|
//
|
|
5
|
-
// SwiftUI mirror of Compose's `
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/component/HeaderUser.kt`.
|
|
6
6
|
// Renders a `HeaderTitle.User`: an avatar group (1..n avatars with overflow),
|
|
7
7
|
// a title with optional inline icons, and an optional subtitle. Shows a
|
|
8
8
|
// skeleton shimmer placeholder while `isLoading`.
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
import SwiftUI
|
|
12
12
|
|
|
13
|
-
@available(iOS 16.0, *)
|
|
14
13
|
public struct HeaderUserView: View {
|
|
15
14
|
private let data: HeaderUserData
|
|
16
15
|
|
|
@@ -20,9 +19,7 @@ public struct HeaderUserView: View {
|
|
|
20
19
|
|
|
21
20
|
private let avatarSize: CGFloat = Spacing.XXL // 32
|
|
22
21
|
|
|
23
|
-
private var
|
|
24
|
-
UIScreen.main.bounds.width - 172
|
|
25
|
-
}
|
|
22
|
+
@Environment(\.screenLayout) private var layout
|
|
26
23
|
|
|
27
24
|
public var body: some View {
|
|
28
25
|
if data.isLoading {
|
|
@@ -33,6 +30,14 @@ public struct HeaderUserView: View {
|
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
private var content: some View {
|
|
33
|
+
SwiftUI.Button(action: { data.onPress?() }) {
|
|
34
|
+
row
|
|
35
|
+
}
|
|
36
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
37
|
+
.disabled(data.onPress == nil)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private var row: some View {
|
|
36
41
|
HStack(spacing: 0) {
|
|
37
42
|
HStack(spacing: 0) {
|
|
38
43
|
ZStack(alignment: .bottomTrailing) {
|
|
@@ -49,8 +54,7 @@ public struct HeaderUserView: View {
|
|
|
49
54
|
|
|
50
55
|
VStack(alignment: .leading, spacing: 0) {
|
|
51
56
|
HStack(spacing: 0) {
|
|
52
|
-
MomoText(data.title, typography: .actionXsBold, color: data.tintColor
|
|
53
|
-
.truncationMode(.tail)
|
|
57
|
+
MomoText(data.title, typography: .actionXsBold, color: data.tintColor, maxLines: 1, fontFamily: headerFontFamily)
|
|
54
58
|
|
|
55
59
|
if !data.icons.isEmpty {
|
|
56
60
|
Spacer().frame(width: 6)
|
|
@@ -66,34 +70,27 @@ public struct HeaderUserView: View {
|
|
|
66
70
|
|
|
67
71
|
if let subTitle = data.subTitle, !subTitle.isEmpty {
|
|
68
72
|
Spacer().frame(height: 2)
|
|
69
|
-
MomoText(subTitle, typography: .descriptionXsRegular, color: data.tintColor
|
|
70
|
-
.truncationMode(.tail)
|
|
73
|
+
MomoText(subTitle, typography: .descriptionXsRegular, color: data.tintColor, maxLines: 1)
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
|
-
.frame(maxWidth:
|
|
77
|
+
.frame(maxWidth: headerTitleMaxWidth(layout), alignment: .leading)
|
|
75
78
|
|
|
76
79
|
Spacer(minLength: 0)
|
|
77
80
|
}
|
|
78
81
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
79
82
|
.padding(.vertical, Spacing.XS)
|
|
80
83
|
.contentShape(Rectangle())
|
|
81
|
-
.onTapGesture { data.onPress?() }
|
|
82
|
-
.allowsHitTesting(data.onPress != nil)
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
private var shimmer: some View {
|
|
86
87
|
HStack(spacing: Spacing.M / 2) {
|
|
87
|
-
|
|
88
|
-
.fill(Colors.black05)
|
|
88
|
+
Skeleton()
|
|
89
89
|
.frame(width: avatarSize, height: avatarSize)
|
|
90
|
+
.clipShape(Circle())
|
|
90
91
|
VStack(alignment: .leading, spacing: Spacing.XXS) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.frame(width: 120, height: 18)
|
|
94
|
-
RoundedRectangle(cornerRadius: 8)
|
|
95
|
-
.fill(Colors.black05)
|
|
96
|
-
.frame(width: 120, height: 12)
|
|
92
|
+
titleSkeleton(height: 18)
|
|
93
|
+
titleSkeleton(height: 12)
|
|
97
94
|
}
|
|
98
95
|
}
|
|
99
96
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
@@ -105,8 +102,8 @@ public struct HeaderUserView: View {
|
|
|
105
102
|
|
|
106
103
|
/// Mirrors Compose's `AvatarGroup`: lays out 1..4 avatars in a fixed collage,
|
|
107
104
|
/// falls back to initials when empty, and shows a "+n" overflow tile beyond 4.
|
|
108
|
-
@available(iOS 16.0, *)
|
|
109
105
|
private struct AvatarGroup: View {
|
|
106
|
+
@Environment(\.appTheme) private var theme
|
|
110
107
|
let urls: [String]
|
|
111
108
|
let title: String
|
|
112
109
|
let size: CGFloat
|
|
@@ -176,7 +173,7 @@ private struct AvatarGroup: View {
|
|
|
176
173
|
ImageView(url)
|
|
177
174
|
.aspectRatio(contentMode: .fill)
|
|
178
175
|
.clipShape(Circle())
|
|
179
|
-
.overlay(Circle().stroke(
|
|
176
|
+
.overlay(Circle().stroke(theme.colors.border.default, lineWidth: 0.5))
|
|
180
177
|
}
|
|
181
178
|
|
|
182
179
|
private func overflowTile(more: Int) -> some View {
|
|
@@ -187,8 +184,8 @@ private struct AvatarGroup: View {
|
|
|
187
184
|
}
|
|
188
185
|
}
|
|
189
186
|
|
|
190
|
-
@available(iOS 16.0, *)
|
|
191
187
|
private struct InitialsAvatar: View {
|
|
188
|
+
@Environment(\.appTheme) private var theme
|
|
192
189
|
let name: String
|
|
193
190
|
let size: CGFloat
|
|
194
191
|
|
|
@@ -204,6 +201,6 @@ private struct InitialsAvatar: View {
|
|
|
204
201
|
.frame(width: size, height: size)
|
|
205
202
|
.background(Colors.pink09)
|
|
206
203
|
.clipShape(Circle())
|
|
207
|
-
.overlay(Circle().stroke(
|
|
204
|
+
.overlay(Circle().stroke(theme.colors.border.default, lineWidth: 0.5))
|
|
208
205
|
}
|
|
209
206
|
}
|