@momo-kits/native-kits 0.167.1-duo.2-debug → 0.167.1-duo.3-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/scheduled_tasks.lock +1 -0
- package/.claude/settings.local.json +185 -0
- package/CLAUDE.md +8 -3
- 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/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +52 -25
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +45 -15
- 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/StackScreen.kt +34 -8
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +81 -62
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +281 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +31 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +27 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +17 -10
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
- package/gradle.properties +1 -1
- package/ios/Application/HeaderRight.swift +25 -12
- package/ios/Application/Layout/ScreenLayout.swift +196 -0
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +30 -9
- package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +5 -2
- package/ios/Application/Navigation/BottomTab/DuoTabBar.swift +275 -0
- package/ios/Application/Navigation/HeaderUser.swift +4 -1
- package/ios/Application/Navigation/NavigationContainer.swift +3 -0
- package/ios/Application/Navigation/Overplay/BottomSheet.swift +41 -4
- package/ios/Application/Navigation/Overplay/ModalScreen.swift +5 -1
- package/ios/Application/Navigation/component/DuoScrollGradient.swift +55 -0
- package/ios/Application/Navigation/component/Header.swift +9 -3
- package/ios/Application/Navigation/component/HeaderBackground.swift +3 -2
- package/ios/Application/Navigation/component/NavigationFooter.swift +1 -1
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +61 -9
- package/ios/Application/Navigation/component/SideBar.swift +155 -0
- package/ios/Application/StackScreen.swift +135 -18
- package/ios/Popup/PopupDisplay.swift +84 -68
- package/ios/Popup/PopupLayout.swift +45 -0
- package/ios/Popup/PopupNotify.swift +43 -32
- package/ios/Popup/PopupPromotion.swift +17 -5
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +76 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +170 -0
- package/local.properties +8 -0
- package/package.json +1 -1
|
@@ -55,6 +55,7 @@ extension View {
|
|
|
55
55
|
|
|
56
56
|
public struct PopupDisplay: View {
|
|
57
57
|
@Environment(\.localize) private var localize
|
|
58
|
+
@Environment(\.screenLayout) private var layout
|
|
58
59
|
|
|
59
60
|
// MARK: Lifecycle
|
|
60
61
|
|
|
@@ -91,6 +92,7 @@ public struct PopupDisplay: View {
|
|
|
91
92
|
|
|
92
93
|
@State private var textHeight: CGFloat = .zero
|
|
93
94
|
@State private var isScrollable = false
|
|
95
|
+
@State private var actionsHeight: CGFloat = 0
|
|
94
96
|
|
|
95
97
|
public var body: some View {
|
|
96
98
|
var shouldUseColumn: Bool {
|
|
@@ -114,89 +116,103 @@ public struct PopupDisplay: View {
|
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
VStack(spacing: 0) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
.
|
|
121
|
-
|
|
122
|
-
.
|
|
119
|
+
// scrolls when iPhone Duo landscape is too short; actions stay visible
|
|
120
|
+
PopupScrollBody(maxHeight: layout.popupMaxHeight.map { $0 - actionsHeight }) {
|
|
121
|
+
VStack(spacing: 0) {
|
|
122
|
+
if(!url.isEmpty) {
|
|
123
|
+
WebImage(url: URL(string: url), isAnimating: .constant(true))
|
|
124
|
+
.resizable()
|
|
125
|
+
.placeholder {
|
|
126
|
+
Rectangle()
|
|
127
|
+
.fill(Color.gray.opacity(0.3))
|
|
128
|
+
.aspectRatio(1.777, contentMode: .fit)
|
|
129
|
+
.frame(maxWidth: .infinity, maxHeight: 184)
|
|
130
|
+
}
|
|
123
131
|
.aspectRatio(1.777, contentMode: .fit)
|
|
124
|
-
.frame(maxWidth: .infinity,
|
|
132
|
+
.frame(maxWidth: .infinity, alignment: .center)
|
|
133
|
+
.clipShape(RoundedCorner(radius: 15, corners: [.topLeft, .topRight]))
|
|
134
|
+
.clipped()
|
|
125
135
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
MomoText(title, typography: .headerDefaultBold)
|
|
133
|
-
.padding(.top, 24)
|
|
134
|
-
.padding(.bottom, 8)
|
|
135
|
-
.lineLimit(2)
|
|
136
|
-
.accessibility(identifier: "title_popup_permission")
|
|
136
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
137
|
+
MomoText(title, typography: .headerDefaultBold)
|
|
138
|
+
.padding(.top, 24)
|
|
139
|
+
.padding(.bottom, 8)
|
|
140
|
+
.lineLimit(2)
|
|
141
|
+
.accessibility(identifier: "title_popup_permission")
|
|
137
142
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
// Cap the visible height to ~8.5 lines
|
|
152
|
-
.frame(height: min(maxHeight8_5, textHeight))
|
|
153
|
-
} else {
|
|
154
|
-
MomoText(description, typography: .bodyDefaultRegular)
|
|
155
|
-
.multilineTextAlignment(.leading)
|
|
156
|
-
.background(GeometryReader { geo in
|
|
157
|
-
Color.clear.onAppear {
|
|
158
|
-
textHeight = geo.size.height
|
|
159
|
-
// Trigger scroll at ~8.5 lines
|
|
160
|
-
isScrollable = textHeight > maxHeight8_5
|
|
143
|
+
Group {
|
|
144
|
+
if isScrollable {
|
|
145
|
+
ScrollView(showsIndicators: false) {
|
|
146
|
+
MomoText(description, typography: .bodyDefaultRegular)
|
|
147
|
+
.multilineTextAlignment(.leading)
|
|
148
|
+
.background(GeometryReader { geo in
|
|
149
|
+
Color.clear.onAppear { textHeight = geo.size.height }
|
|
150
|
+
})
|
|
151
|
+
.measureLineHeights(font: .body_default_regular,
|
|
152
|
+
oneLine: $oneLineH,
|
|
153
|
+
twoLines: $twoLineH)
|
|
154
|
+
.accessibility(identifier: "description_popup_permission")
|
|
161
155
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
156
|
+
// Cap the visible height to ~8.5 lines
|
|
157
|
+
.frame(height: min(maxHeight8_5, textHeight))
|
|
158
|
+
} else {
|
|
159
|
+
MomoText(description, typography: .bodyDefaultRegular)
|
|
160
|
+
.multilineTextAlignment(.leading)
|
|
161
|
+
.background(GeometryReader { geo in
|
|
162
|
+
Color.clear.onAppear {
|
|
163
|
+
textHeight = geo.size.height
|
|
164
|
+
// Trigger scroll at ~8.5 lines
|
|
165
|
+
isScrollable = textHeight > maxHeight8_5
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
.measureLineHeights(font: .body_default_regular,
|
|
169
|
+
oneLine: $oneLineH,
|
|
170
|
+
twoLines: $twoLineH)
|
|
171
|
+
.accessibility(identifier: "description_popup_permission")
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
174
|
.padding(.bottom, 8)
|
|
175
|
+
|
|
176
|
+
if(!errorCode.isEmpty) {
|
|
177
|
+
MomoText(localize.translate("errorCode") + errorCode, typography: .descriptionXsRegular, color: Colors.black12)
|
|
178
|
+
.lineLimit(1)
|
|
179
|
+
.padding(.bottom, 8)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
183
|
+
.padding(.horizontal, 24)
|
|
184
|
+
.padding(.bottom, 16)
|
|
175
185
|
}
|
|
176
186
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
ColumnButtons
|
|
186
|
-
}
|
|
187
|
-
} else {
|
|
188
|
-
if buttonType == .text {
|
|
189
|
-
RowTextButtons
|
|
187
|
+
|
|
188
|
+
Group {
|
|
189
|
+
if shouldUseColumn {
|
|
190
|
+
if buttonType == .text {
|
|
191
|
+
ColumnTextButtons
|
|
192
|
+
} else {
|
|
193
|
+
ColumnButtons
|
|
194
|
+
}
|
|
190
195
|
} else {
|
|
191
|
-
|
|
196
|
+
if buttonType == .text {
|
|
197
|
+
RowTextButtons
|
|
198
|
+
} else {
|
|
199
|
+
RowButtons
|
|
200
|
+
}
|
|
192
201
|
}
|
|
193
202
|
}
|
|
203
|
+
.background(
|
|
204
|
+
GeometryReader { proxy in
|
|
205
|
+
Color.clear
|
|
206
|
+
.onAppear { actionsHeight = proxy.size.height }
|
|
207
|
+
.onChange(of: proxy.size.height) { actionsHeight = $0 }
|
|
208
|
+
}
|
|
209
|
+
)
|
|
194
210
|
}
|
|
195
211
|
.fixedSize(horizontal: false, vertical: true)
|
|
196
212
|
}
|
|
197
213
|
}
|
|
198
214
|
.background(Color.white.cornerRadius(15))
|
|
199
|
-
.frame(maxWidth: popupMaxWidth
|
|
215
|
+
.frame(maxWidth: layout.popupMaxWidth)
|
|
200
216
|
.padding(.horizontal, 12)
|
|
201
217
|
.accessibilityElement(children: .ignore)
|
|
202
218
|
.accessibility(identifier: "popup_notify")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
// Width of a system alert on the iPhone Duo inner display, in both orientations (RN Popup/utils.ts).
|
|
4
|
+
private let duoPopupMaxWidth: CGFloat = 440
|
|
5
|
+
|
|
6
|
+
extension ScreenLayout {
|
|
7
|
+
var isDuoDevice: Bool { isIPhoneDuoDevice(windowSize: CGSize(width: width, height: height)) }
|
|
8
|
+
|
|
9
|
+
/// Card width cap; other devices keep the full window width minus the popup's own padding.
|
|
10
|
+
var popupMaxWidth: CGFloat {
|
|
11
|
+
isDuoDevice ? min(width - Spacing.XL * 2, duoPopupMaxWidth) : .infinity
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/// Room left for a popup on iPhone Duo, where landscape is too short for it; nil elsewhere.
|
|
15
|
+
var popupMaxHeight: CGFloat? {
|
|
16
|
+
guard isDuoDevice else { return nil }
|
|
17
|
+
return height - duoTopInset - insets.bottom - Spacing.XL * 2
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Scrolls `content` once it is taller than `maxHeight`; otherwise sizes to it.
|
|
22
|
+
struct PopupScrollBody<Content: View>: View {
|
|
23
|
+
let maxHeight: CGFloat?
|
|
24
|
+
@ViewBuilder let content: () -> Content
|
|
25
|
+
|
|
26
|
+
@State private var contentHeight: CGFloat = 0
|
|
27
|
+
|
|
28
|
+
var body: some View {
|
|
29
|
+
if let maxHeight {
|
|
30
|
+
ScrollView(.vertical, showsIndicators: false) {
|
|
31
|
+
content()
|
|
32
|
+
.background(
|
|
33
|
+
GeometryReader { proxy in
|
|
34
|
+
Color.clear
|
|
35
|
+
.onAppear { contentHeight = proxy.size.height }
|
|
36
|
+
.onChange(of: proxy.size.height) { contentHeight = $0 }
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
.frame(height: min(contentHeight, max(maxHeight, 0)))
|
|
41
|
+
} else {
|
|
42
|
+
content()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -3,12 +3,6 @@ import SwiftUI
|
|
|
3
3
|
|
|
4
4
|
public typealias PopupActionDirection = PopupButtonDirection
|
|
5
5
|
|
|
6
|
-
// Portrait width in landscape too, so aspect-ratio images don't push popups past the screen height.
|
|
7
|
-
var popupMaxWidth: CGFloat {
|
|
8
|
-
let size = UIScreen.main.bounds.size
|
|
9
|
-
return min(size.width, size.height)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
6
|
public struct PopupNotifyProps {
|
|
13
7
|
public var image: String
|
|
14
8
|
public var title: String
|
|
@@ -46,10 +40,12 @@ public struct PopupNotifyProps {
|
|
|
46
40
|
public struct PopupNotify: View {
|
|
47
41
|
@Environment(\.localize) private var localize
|
|
48
42
|
@Environment(\.momoFontScale) private var fontScale
|
|
43
|
+
@Environment(\.screenLayout) private var layout
|
|
49
44
|
|
|
50
45
|
public let props: PopupNotifyProps
|
|
51
46
|
|
|
52
47
|
@State private var descriptionHeight: CGFloat = 0
|
|
48
|
+
@State private var actionsHeight: CGFloat = 0
|
|
53
49
|
|
|
54
50
|
private var errorCodePrefix: String {
|
|
55
51
|
localize.translate("errorCode")
|
|
@@ -77,37 +73,26 @@ public struct PopupNotify: View {
|
|
|
77
73
|
public var body: some View {
|
|
78
74
|
ZStack(alignment: .topTrailing) {
|
|
79
75
|
VStack(spacing: 0) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
typography: .headerDefaultBold,
|
|
88
|
-
maxLines: 2,
|
|
89
|
-
accessibilityId: "title_popup_permission"
|
|
90
|
-
)
|
|
91
|
-
.padding(.bottom, Spacing.S)
|
|
92
|
-
|
|
93
|
-
descriptionContent
|
|
94
|
-
|
|
95
|
-
if !props.error.isEmpty {
|
|
96
|
-
MomoText(
|
|
97
|
-
errorCodePrefix + props.error,
|
|
98
|
-
typography: .descriptionXsRegular,
|
|
99
|
-
color: Colors.black12,
|
|
100
|
-
maxLines: 1
|
|
101
|
-
)
|
|
102
|
-
.padding(.top, Spacing.S)
|
|
76
|
+
// scrolls when iPhone Duo landscape is too short; actions stay visible
|
|
77
|
+
PopupScrollBody(maxHeight: layout.popupMaxHeight.map { $0 - actionsHeight }) {
|
|
78
|
+
VStack(spacing: 0) {
|
|
79
|
+
if !props.image.isEmpty {
|
|
80
|
+
popupImage
|
|
81
|
+
}
|
|
82
|
+
textContent
|
|
103
83
|
}
|
|
104
84
|
}
|
|
105
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
106
|
-
.padding(Spacing.XL)
|
|
107
85
|
|
|
108
86
|
actionButtons
|
|
109
87
|
.padding(.horizontal, Spacing.XL)
|
|
110
88
|
.padding(.bottom, Spacing.XL)
|
|
89
|
+
.background(
|
|
90
|
+
GeometryReader { proxy in
|
|
91
|
+
Color.clear
|
|
92
|
+
.onAppear { actionsHeight = proxy.size.height }
|
|
93
|
+
.onChange(of: proxy.size.height) { actionsHeight = $0 }
|
|
94
|
+
}
|
|
95
|
+
)
|
|
111
96
|
}
|
|
112
97
|
.background(Colors.black01)
|
|
113
98
|
.clipShape(RoundedRectangle(cornerRadius: Radius.L))
|
|
@@ -117,12 +102,38 @@ public struct PopupNotify: View {
|
|
|
117
102
|
.offset(x: 11, y: -11)
|
|
118
103
|
}
|
|
119
104
|
}
|
|
120
|
-
.frame(maxWidth: popupMaxWidth
|
|
105
|
+
.frame(maxWidth: layout.popupMaxWidth)
|
|
121
106
|
.padding(.horizontal, Spacing.M)
|
|
122
107
|
.accessibilityElement(children: .ignore)
|
|
123
108
|
.accessibilityIdentifier("popup_notify")
|
|
124
109
|
}
|
|
125
110
|
|
|
111
|
+
private var textContent: some View {
|
|
112
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
113
|
+
MomoText(
|
|
114
|
+
props.title,
|
|
115
|
+
typography: .headerDefaultBold,
|
|
116
|
+
maxLines: 2,
|
|
117
|
+
accessibilityId: "title_popup_permission"
|
|
118
|
+
)
|
|
119
|
+
.padding(.bottom, Spacing.S)
|
|
120
|
+
|
|
121
|
+
descriptionContent
|
|
122
|
+
|
|
123
|
+
if !props.error.isEmpty {
|
|
124
|
+
MomoText(
|
|
125
|
+
errorCodePrefix + props.error,
|
|
126
|
+
typography: .descriptionXsRegular,
|
|
127
|
+
color: Colors.black12,
|
|
128
|
+
maxLines: 1
|
|
129
|
+
)
|
|
130
|
+
.padding(.top, Spacing.S)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
134
|
+
.padding(Spacing.XL)
|
|
135
|
+
}
|
|
136
|
+
|
|
126
137
|
private var popupImage: some View {
|
|
127
138
|
WebImage(url: URL(string: props.image), isAnimating: .constant(true))
|
|
128
139
|
.resizable()
|
|
@@ -3,6 +3,7 @@ import SDWebImageSwiftUI
|
|
|
3
3
|
|
|
4
4
|
public struct PopupPromotion: View {
|
|
5
5
|
@Environment(\.appTheme) private var theme
|
|
6
|
+
@Environment(\.screenLayout) private var layout
|
|
6
7
|
|
|
7
8
|
var imageUrl: String
|
|
8
9
|
var onIconClose: (() -> Void)?
|
|
@@ -19,22 +20,33 @@ public struct PopupPromotion: View {
|
|
|
19
20
|
self.onAction = onAction
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
private static let aspectRatio: CGFloat = 0.72
|
|
24
|
+
// close button row under the image: stack spacing + padded 40pt button
|
|
25
|
+
private static let closeRowHeight: CGFloat = 8 + 40 + Spacing.S * 2
|
|
26
|
+
|
|
27
|
+
// fit the portrait image by height when iPhone Duo leaves too little room
|
|
28
|
+
private var imageWidth: CGFloat {
|
|
29
|
+
let width = min(layout.width - Spacing.XL * 2, layout.popupMaxWidth)
|
|
30
|
+
guard let maxHeight = layout.popupMaxHeight else { return width }
|
|
31
|
+
return min(width, (maxHeight - Self.closeRowHeight) * Self.aspectRatio)
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
public var body: some View {
|
|
23
35
|
VStack(spacing: 8) {
|
|
24
36
|
if(imageUrl.isEmpty) {
|
|
25
|
-
Icon(source: "media_fail", size:
|
|
37
|
+
Icon(source: "media_fail", size: imageWidth)
|
|
26
38
|
}
|
|
27
39
|
else {
|
|
28
40
|
WebImage(url: URL(string: imageUrl)!)
|
|
29
41
|
.resizable()
|
|
30
42
|
.placeholder {
|
|
31
43
|
Color.gray
|
|
32
|
-
.aspectRatio(
|
|
33
|
-
.frame(maxWidth:
|
|
44
|
+
.aspectRatio(Self.aspectRatio, contentMode: .fit)
|
|
45
|
+
.frame(maxWidth: imageWidth)
|
|
34
46
|
.clipped()
|
|
35
47
|
}
|
|
36
|
-
.aspectRatio(
|
|
37
|
-
.frame(maxWidth:
|
|
48
|
+
.aspectRatio(Self.aspectRatio, contentMode: .fit)
|
|
49
|
+
.frame(maxWidth: imageWidth, alignment: .center)
|
|
38
50
|
.clipped()
|
|
39
51
|
.onTapGesture {
|
|
40
52
|
onAction?()
|
package/ios/native-kits.podspec
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DuoTabDemo.swift
|
|
3
|
+
// Example
|
|
4
|
+
//
|
|
5
|
+
// Mirrors RN `app/screens/IPhoneDuo/DuoTabUsage`: glass rail in the Duo side bar, floating
|
|
6
|
+
// glass bar when unfolded portrait. `useShortcut` gives the tallest side bar header, for the
|
|
7
|
+
// overlap review.
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
import MoMoUIKits
|
|
11
|
+
import SwiftUI
|
|
12
|
+
|
|
13
|
+
private let swatches = [Colors.pink03, Colors.orange03, Colors.green03]
|
|
14
|
+
private let history = (1...18).map { "Giao dịch #\($0)" }
|
|
15
|
+
|
|
16
|
+
struct DuoTabDemo: View {
|
|
17
|
+
let useShortcut: Bool
|
|
18
|
+
|
|
19
|
+
var body: some View {
|
|
20
|
+
if #available(iOS 16.0, *) {
|
|
21
|
+
DuoTabDemoContent(useShortcut: useShortcut)
|
|
22
|
+
} else {
|
|
23
|
+
Text("Requires iOS 16+").padding()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@available(iOS 16.0, *)
|
|
29
|
+
private struct DuoTabDemoContent: View {
|
|
30
|
+
let useShortcut: Bool
|
|
31
|
+
|
|
32
|
+
var body: some View {
|
|
33
|
+
BottomTab(
|
|
34
|
+
items: [
|
|
35
|
+
tab("DuoMoMo", label: "MoMo", icon: "basic_home", title: "MoMo"),
|
|
36
|
+
tab("DuoPromotion", label: "Ưu đãi", icon: "shopping_coupon", title: "Ưu đãi", badge: ""),
|
|
37
|
+
tab("DuoHistory", label: "Lịch sử GD", icon: "time_clock_reset", title: "Lịch sử giao dịch", badge: "3"),
|
|
38
|
+
tab("DuoMe", label: "Tôi", icon: "basic_person", title: "Tôi"),
|
|
39
|
+
],
|
|
40
|
+
floatingButton: BottomTabFloatingButton(icon: "navigation_full_screen", label: "Quét mọi QR", onPress: {})
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private func tab(_ name: String, label: String, icon: String, title: String, badge: String? = nil) -> BottomTabItem {
|
|
45
|
+
BottomTabItem(
|
|
46
|
+
name: name,
|
|
47
|
+
label: label,
|
|
48
|
+
icon: icon,
|
|
49
|
+
badgeLabel: badge,
|
|
50
|
+
options: NavigationOptions(title: title, headerRightData: HeaderRightData(useShortcut: useShortcut)),
|
|
51
|
+
screen: { DuoTabList() }
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private struct DuoTabList: View {
|
|
57
|
+
var body: some View {
|
|
58
|
+
VStack(spacing: 0) {
|
|
59
|
+
ForEach(history.indices, id: \.self) { index in
|
|
60
|
+
if index > 0 { MomoDivider() }
|
|
61
|
+
HStack(spacing: Spacing.M) {
|
|
62
|
+
RoundedRectangle(cornerRadius: Radius.S)
|
|
63
|
+
.fill(swatches[index % swatches.count])
|
|
64
|
+
.frame(width: Spacing.XL, height: Spacing.XL)
|
|
65
|
+
MomoText(history[index], typography: .bodyDefaultRegular)
|
|
66
|
+
Spacer()
|
|
67
|
+
}
|
|
68
|
+
.padding(.vertical, Spacing.M)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
.padding(.horizontal, Spacing.M)
|
|
72
|
+
.background(Colors.black01)
|
|
73
|
+
.clipShape(RoundedRectangle(cornerRadius: Radius.M))
|
|
74
|
+
.padding(Spacing.M)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -85,6 +85,7 @@ public let ScreenUsages: [ScreenUsage] = [
|
|
|
85
85
|
ScreenUsage(title: "DateTimePicker", destination: AnyView(DateTimePickerDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
86
86
|
ScreenUsage(title: "Navigation", destination: AnyView(NavigationDemo()), icon: getIcon(icon: "Layout")),
|
|
87
87
|
ScreenUsage(title: "Bottom Tab", destination: AnyView(BottomTabDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
88
|
+
ScreenUsage(title: "iPhone Duo", destination: AnyView(IPhoneDuoDemo()), icon: getIcon(icon: "Layout")),
|
|
88
89
|
ScreenUsage(title: "Animated Header", destination: AnyView(AnimatedHeaderDemo()), icon: getIcon(icon: "Layout")),
|
|
89
90
|
ScreenUsage(title: "Theme", destination: AnyView(ThemeDemo()), icon: getIcon(icon: "Typography")),
|
|
90
91
|
ScreenUsage(title: "Localize", destination: AnyView(LocalizeDemo()), icon: getIcon(icon: "Typography")),
|