@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.
Files changed (48) hide show
  1. package/.claude/scheduled_tasks.lock +1 -0
  2. package/.claude/settings.local.json +185 -0
  3. package/CLAUDE.md +8 -3
  4. package/compose/build.gradle.kts +15 -1
  5. package/compose/build.gradle.kts.backup +14 -0
  6. package/compose/compose.podspec +1 -1
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -0
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +52 -25
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -2
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +45 -15
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +34 -8
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +81 -62
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +281 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +31 -4
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +27 -2
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +17 -10
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
  20. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
  21. package/gradle.properties +1 -1
  22. package/ios/Application/HeaderRight.swift +25 -12
  23. package/ios/Application/Layout/ScreenLayout.swift +196 -0
  24. package/ios/Application/Navigation/BottomTab/BottomTab.swift +30 -9
  25. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +5 -2
  26. package/ios/Application/Navigation/BottomTab/DuoTabBar.swift +275 -0
  27. package/ios/Application/Navigation/HeaderUser.swift +4 -1
  28. package/ios/Application/Navigation/NavigationContainer.swift +3 -0
  29. package/ios/Application/Navigation/Overplay/BottomSheet.swift +41 -4
  30. package/ios/Application/Navigation/Overplay/ModalScreen.swift +5 -1
  31. package/ios/Application/Navigation/component/DuoScrollGradient.swift +55 -0
  32. package/ios/Application/Navigation/component/Header.swift +9 -3
  33. package/ios/Application/Navigation/component/HeaderBackground.swift +3 -2
  34. package/ios/Application/Navigation/component/NavigationFooter.swift +1 -1
  35. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +61 -9
  36. package/ios/Application/Navigation/component/SideBar.swift +155 -0
  37. package/ios/Application/StackScreen.swift +135 -18
  38. package/ios/Popup/PopupDisplay.swift +84 -68
  39. package/ios/Popup/PopupLayout.swift +45 -0
  40. package/ios/Popup/PopupNotify.swift +43 -32
  41. package/ios/Popup/PopupPromotion.swift +17 -5
  42. package/ios/native-kits.podspec +1 -1
  43. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  44. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +76 -0
  45. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +1 -0
  46. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +170 -0
  47. package/local.properties +8 -0
  48. 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
- if(!url.isEmpty) {
118
- WebImage(url: URL(string: url), isAnimating: .constant(true))
119
- .resizable()
120
- .placeholder {
121
- Rectangle()
122
- .fill(Color.gray.opacity(0.3))
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, maxHeight: 184)
132
+ .frame(maxWidth: .infinity, alignment: .center)
133
+ .clipShape(RoundedCorner(radius: 15, corners: [.topLeft, .topRight]))
134
+ .clipped()
125
135
  }
126
- .aspectRatio(1.777, contentMode: .fit)
127
- .frame(maxWidth: .infinity, alignment: .center)
128
- .clipShape(RoundedCorner(radius: 15, corners: [.topLeft, .topRight]))
129
- .clipped()
130
- }
131
- VStack(alignment: .leading, spacing: 0) {
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
- Group {
139
- if isScrollable {
140
- ScrollView(showsIndicators: false) {
141
- MomoText(description, typography: .bodyDefaultRegular)
142
- .multilineTextAlignment(.leading)
143
- .background(GeometryReader { geo in
144
- Color.clear.onAppear { textHeight = geo.size.height }
145
- })
146
- .measureLineHeights(font: .body_default_regular,
147
- oneLine: $oneLineH,
148
- twoLines: $twoLineH)
149
- .accessibility(identifier: "description_popup_permission")
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
- .measureLineHeights(font: .body_default_regular,
164
- oneLine: $oneLineH,
165
- twoLines: $twoLineH)
166
- .accessibility(identifier: "description_popup_permission")
167
- }
168
- }
169
- .padding(.bottom, 8)
170
-
171
- if(!errorCode.isEmpty) {
172
- MomoText(localize.translate("errorCode") + errorCode, typography: .descriptionXsRegular, color: Colors.black12)
173
- .lineLimit(1)
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
- .frame(maxWidth: .infinity, alignment: .leading)
178
- .padding(.horizontal, 24)
179
- .padding(.bottom, 16)
180
-
181
- if shouldUseColumn {
182
- if buttonType == .text {
183
- ColumnTextButtons
184
- } else {
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
- RowButtons
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 - 24)
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
- if !props.image.isEmpty {
81
- popupImage
82
- }
83
-
84
- VStack(alignment: .leading, spacing: 0) {
85
- MomoText(
86
- props.title,
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 - Spacing.M * 2)
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: popupMaxWidth - Spacing.XL * 2)
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(0.72, contentMode: .fit)
33
- .frame(maxWidth: popupMaxWidth - Spacing.XL * 2)
44
+ .aspectRatio(Self.aspectRatio, contentMode: .fit)
45
+ .frame(maxWidth: imageWidth)
34
46
  .clipped()
35
47
  }
36
- .aspectRatio(0.72, contentMode: .fit)
37
- .frame(maxWidth: popupMaxWidth - Spacing.XL * 2, alignment: .center)
48
+ .aspectRatio(Self.aspectRatio, contentMode: .fit)
49
+ .frame(maxWidth: imageWidth, alignment: .center)
38
50
  .clipped()
39
51
  .onTapGesture {
40
52
  onAction?()
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKits'
3
- s.version = '0.167.1-beta.4'
3
+ s.version = '0.167.1-duo.3'
4
4
  s.summary = 'MoMoUIKits for iOS'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKitsDemo'
3
- s.version = '0.167.1-beta.4'
3
+ s.version = '0.167.1-duo.3'
4
4
  s.summary = 'Demo browser for MoMoUIKits SwiftUI components'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -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")),