@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.
Files changed (218) hide show
  1. package/CLAUDE.md +51 -7
  2. package/README.md +2 -2
  3. package/compose/build.gradle.kts +10 -2
  4. package/compose/build.gradle.kts.backup +8 -0
  5. package/compose/compose.podspec +1 -3
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +4 -3
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -14
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +351 -111
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +20 -21
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +2 -1
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +4 -2
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +1 -1
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +1 -1
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -1
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +9 -6
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +11 -12
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -9
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +2 -2
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +10 -9
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +6 -6
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +55 -22
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -1
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +7 -9
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +1 -1
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +13 -6
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +13 -5
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +3 -2
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +14 -6
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +19 -21
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +2 -2
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +5 -2
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +3 -1
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +7 -15
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +2 -1
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +20 -15
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +1 -2
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +10 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +6 -2
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +6 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +197 -60
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +132 -83
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +54 -62
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +122 -24
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +34 -14
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +3 -3
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +1 -2
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +113 -26
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +2 -2
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -2
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +37 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +8 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +13 -0
  65. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +44 -0
  66. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +15 -0
  67. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +3 -1
  68. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
  69. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
  70. package/gradle/libs.versions.toml +3 -14
  71. package/gradle/wrapper/gradle-wrapper.properties +0 -2
  72. package/gradle.properties +3 -5
  73. package/ios/Application/ApplicationEnvironment.swift +24 -6
  74. package/ios/Application/DesignSystemKits.swift +100 -0
  75. package/ios/Application/DesignSystemKitsPayload.swift +68 -0
  76. package/ios/Application/MaxApi.swift +30 -3
  77. package/ios/Avatar/Avatar.swift +4 -3
  78. package/ios/Badge/Badge.swift +6 -4
  79. package/ios/Badge/BadgeDot.swift +1 -1
  80. package/ios/Badge/BadgeRibbon.swift +8 -2
  81. package/ios/Button/Button.swift +15 -12
  82. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
  83. package/ios/Carousel/Carousel.swift +29 -12
  84. package/ios/Checkbox/Checkbox.swift +7 -3
  85. package/ios/Chip/Chip.swift +5 -4
  86. package/ios/Collapse/Collapse.swift +70 -19
  87. package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
  88. package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
  89. package/ios/DateTimePicker/DateTimePicker.swift +54 -20
  90. package/ios/DateTimePicker/WheelPicker.swift +103 -105
  91. package/ios/Divider/Divider.swift +37 -4
  92. package/ios/Extensions/Color++.swift +38 -9
  93. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  94. package/ios/Extensions/SkeletonUICompat.swift +11 -0
  95. package/ios/Icon/Icon.swift +21 -23
  96. package/ios/Icon/Icons.swift +1329 -0
  97. package/ios/IconButton/IconButton.swift +15 -7
  98. package/ios/Image/Image.swift +7 -23
  99. package/ios/Information/Information.swift +27 -9
  100. package/ios/Input/ErrorView.swift +2 -2
  101. package/ios/Input/Input.swift +171 -148
  102. package/ios/Input/InputPhoneNumber.swift +10 -9
  103. package/ios/Input/InputSearch.swift +107 -78
  104. package/ios/Input/InputTextArea.swift +133 -148
  105. package/ios/InputDropDown/InputDropDown.swift +10 -7
  106. package/ios/InputMoney/InputMoney.swift +32 -11
  107. package/ios/InputOTP/InputOTP.swift +22 -12
  108. package/ios/Layout/Card.swift +50 -0
  109. package/ios/Layout/GridContext.swift +187 -3
  110. package/ios/Layout/Item.swift +5 -4
  111. package/ios/Layout/ScreenLayout.swift +415 -0
  112. package/ios/Layout/Section.swift +4 -100
  113. package/ios/Loader/Loader.swift +5 -9
  114. package/ios/Lottie/LottieView.swift +14 -2
  115. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
  116. package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
  117. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
  118. package/ios/Navigation/BottomTab/DuoBarCompression.swift +104 -0
  119. package/ios/Navigation/BottomTab/DuoTabBar.swift +329 -0
  120. package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
  121. package/ios/Navigation/Component/Header.swift +545 -0
  122. package/ios/Navigation/Component/HeaderBackground.swift +154 -0
  123. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  124. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  125. package/ios/Navigation/Component/HeaderRight.swift +425 -0
  126. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  127. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
  128. package/ios/Navigation/Component/SideBar.swift +183 -0
  129. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
  130. package/ios/Navigation/HostBackGestureBridge.swift +78 -0
  131. package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
  132. package/ios/Navigation/Navigation.swift +204 -0
  133. package/ios/Navigation/NavigationContainer.swift +195 -0
  134. package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
  135. package/ios/Navigation/StackScreen.swift +930 -0
  136. package/ios/Navigation/StackTransition.swift +400 -0
  137. package/ios/Navigation/SwipeBackHaptic.swift +33 -0
  138. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  139. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  140. package/ios/Pagination/PaginationScroll.swift +2 -2
  141. package/ios/Popup/PopupDisplay.swift +72 -135
  142. package/ios/Popup/PopupInput.swift +10 -13
  143. package/ios/Popup/PopupLayout.swift +138 -0
  144. package/ios/Popup/PopupNotify.swift +59 -69
  145. package/ios/Popup/PopupPromotion.swift +31 -47
  146. package/ios/PopupView/FullscreenPopup.swift +13 -19
  147. package/ios/PopupView/PopupView.swift +1 -1
  148. package/ios/PopupView/Utils++.swift +0 -8
  149. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  150. package/ios/Radio/Radio.swift +2 -1
  151. package/ios/Rating/Rating.swift +9 -1
  152. package/ios/Resources/dot_loading.json +558 -0
  153. package/ios/Resources/lottie_circle_loader.json +1 -0
  154. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  155. package/ios/Skeleton/Skeleton.swift +22 -27
  156. package/ios/Slider/Slider.swift +16 -18
  157. package/ios/Stepper/Stepper.swift +7 -3
  158. package/ios/Steps/Steps.swift +8 -6
  159. package/ios/SuggestAction/SuggestAction.swift +5 -0
  160. package/ios/Swipeable/SwipeCell.swift +10 -6
  161. package/ios/Switch/Switch.swift +22 -20
  162. package/ios/TabView/TabView.swift +102 -21
  163. package/ios/Tag/Tag.swift +15 -4
  164. package/ios/Template/Logo/Logo.swift +2 -1
  165. package/ios/Template/TrustBanner/TrustBanner.swift +109 -55
  166. package/ios/Title/Title.swift +6 -17
  167. package/ios/Tooltip/Tooltip.swift +29 -14
  168. package/ios/Typography/FontScale.swift +17 -14
  169. package/ios/Typography/Text.swift +156 -20
  170. package/ios/Typography/Typography.swift +2 -2
  171. package/ios/Uploader/Uploader.swift +3 -1
  172. package/ios/native-kits.podspec +2 -3
  173. package/ios-demo/MoMoUIKitsDemo.podspec +1 -3
  174. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  175. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
  176. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
  177. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
  178. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  179. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
  180. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
  181. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
  182. package/package.json +1 -1
  183. package/publish.sh +19 -2
  184. package/scripts/gen-ios-icons.mjs +36 -0
  185. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  186. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  187. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  188. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  189. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  190. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  191. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  192. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  193. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  194. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  195. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  196. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  197. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
  198. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
  199. package/ios/Application/Components.swift +0 -270
  200. package/ios/Application/ComposeApi.swift +0 -22
  201. package/ios/Application/HeaderRight.swift +0 -306
  202. package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
  203. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  204. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  205. package/ios/Application/Navigation/NavigationContainer.swift +0 -213
  206. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  207. package/ios/Application/Navigation/NavigatorManager.swift +0 -22
  208. package/ios/Application/Navigation/component/Header.swift +0 -178
  209. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
  210. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  211. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  212. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  213. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
  214. package/ios/Application/Screen.swift +0 -250
  215. package/ios/Application/StackScreen.swift +0 -492
  216. package/ios/Typography/FontScaleStore.swift +0 -58
  217. package/ios/Typography/KitDesignSystem.swift +0 -17
  218. /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
@@ -1,4 +1,3 @@
1
- import SDWebImageSwiftUI
2
1
  import SwiftUI
3
2
 
4
3
  // MARK: - PopupButtonDirection
@@ -16,45 +15,13 @@ public enum PopupButtonType {
16
15
  case button
17
16
  }
18
17
 
19
- extension View {
20
- func measureLineHeights(font: Font,
21
- oneLine: Binding<CGFloat>,
22
- twoLines: Binding<CGFloat>) -> some View {
23
- overlay(
24
- VStack(spacing: 0) {
25
- Text("A")
26
- .font(font)
27
- .ignoreBoldTextSetting()
28
- .lineLimit(1)
29
- .fixedSize()
30
- .background(
31
- GeometryReader { g in
32
- Color.clear.onAppear { oneLine.wrappedValue = g.size.height }
33
- }
34
- )
35
- .hidden()
36
-
37
- Text("A\nA")
38
- .font(font)
39
- .ignoreBoldTextSetting()
40
- .lineLimit(2)
41
- .fixedSize(horizontal: false, vertical: true)
42
- .background(
43
- GeometryReader { g in
44
- Color.clear.onAppear { twoLines.wrappedValue = g.size.height }
45
- }
46
- )
47
- .hidden()
48
- }
49
- .frame(width: 0, height: 0)
50
- )
51
- }
52
- }
53
-
54
18
  // MARK: - PopupDisplay
55
19
 
56
20
  public struct PopupDisplay: View {
21
+ @Environment(\.appTheme) private var theme
57
22
  @Environment(\.localize) private var localize
23
+ @Environment(\.momoFontScale) private var fontScale
24
+ @Environment(\.screenLayout) private var layout
58
25
 
59
26
  // MARK: Lifecycle
60
27
 
@@ -75,109 +42,53 @@ public struct PopupDisplay: View {
75
42
  }
76
43
 
77
44
  // MARK: Public
78
-
45
+
79
46
  @State private var oneLineH: CGFloat = 0
80
47
  @State private var twoLineH: CGFloat = 0
48
+ @State private var textHeight: CGFloat = .zero
49
+ @State private var actionsHeight: CGFloat = 0
81
50
 
82
- private var fallbackLH: CGFloat { UIFont.preferredFont(forTextStyle: .body).lineHeight }
83
- private var perLine: CGFloat { max(twoLineH - oneLineH, oneLineH > 0 ? oneLineH : fallbackLH) }
84
-
85
- private func heightForLines(lines: CGFloat) -> CGFloat {
86
- guard oneLineH > 0 && twoLineH > 0 else { return ceil(fallbackLH * lines) }
87
- return ceil(oneLineH + max(0, lines - 1) * (twoLineH - oneLineH))
51
+ // ~8.5 lines as laid out; nominal line height until measured
52
+ private var maxHeight8_5: CGFloat {
53
+ popupLinesHeight(8.5, oneLine: oneLineH, twoLines: twoLineH)
54
+ ?? fontScale(TypographyStyle.bodyDefaultRegular.lineHeight) * 8.5
88
55
  }
89
- private var maxHeight8_5: CGFloat { heightForLines(lines: 8.5) }
90
56
 
57
+ private var shouldUseColumn: Bool {
58
+ // UTF-16 length, as PopupNotify counts it
59
+ buttonDirection == .column || (buttonDirection == .auto && (closeButtonTitle.utf16.count > 12 || actionButtonTitle.utf16.count > 12))
60
+ }
91
61
 
92
- @State private var textHeight: CGFloat = .zero
93
- @State private var isScrollable = false
94
-
95
62
  public var body: some View {
96
- var shouldUseColumn: Bool {
97
- buttonDirection == .column || (buttonDirection == .auto && (closeButtonTitle.count > 12 || actionButtonTitle.count > 12))
98
- }
99
-
100
- VStack(spacing: 12) {
101
- ZStack(alignment: .topTrailing) {
102
- if(isShowCloseIcon) {
103
- SwiftUI.Button(action: onClose) {
104
- Image(systemName: "xmark.circle.fill")
105
- .resizable()
106
- .frame(width: 22, height: 22)
107
- .overlay(RoundedRectangle(cornerRadius: Radius.L).stroke(Colors.black01, lineWidth: 2))
108
- }.foregroundColor(Colors.black20)
109
- .background(Colors.black01.cornerRadius(15))
110
- .padding(.trailing, -11)
111
- .padding(.top, -11)
112
- .zIndex(1)
113
- .accessibility(identifier: "ic_popup_close")
114
- }
115
-
116
- 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))
123
- .aspectRatio(1.777, contentMode: .fit)
124
- .frame(maxWidth: .infinity, maxHeight: 184)
125
- }
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")
137
-
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
161
- }
162
- })
163
- .measureLineHeights(font: .body_default_regular,
164
- oneLine: $oneLineH,
165
- twoLines: $twoLineH)
166
- .accessibility(identifier: "description_popup_permission")
167
- }
63
+ ZStack(alignment: .topTrailing) {
64
+ VStack(spacing: 0) {
65
+ // scrolls when iPhone Duo landscape is too short; actions stay visible
66
+ PopupScrollBody(maxHeight: layout.popupMaxHeight.map { $0 - actionsHeight }) {
67
+ VStack(spacing: 0) {
68
+ if !url.isEmpty {
69
+ PopupHeaderImage(url: url)
168
70
  }
169
- .padding(.bottom, 8)
170
-
171
- if(!errorCode.isEmpty) {
172
- MomoText(localize.translate("errorCode") + errorCode, typography: .descriptionXsRegular, color: Colors.black12)
173
- .lineLimit(1)
71
+ VStack(alignment: .leading, spacing: 0) {
72
+ MomoText(title, typography: .headerDefaultBold, maxLines: 2, accessibilityId: "title_popup_permission")
73
+ .padding(.top, 24)
174
74
  .padding(.bottom, 8)
75
+
76
+ descriptionContent
77
+ .padding(.bottom, 8)
78
+
79
+ if !errorCode.isEmpty {
80
+ MomoText(localize.translate("errorCode") + errorCode, typography: .descriptionXsRegular, color: theme.colors.text.hint, maxLines: 1)
81
+ .padding(.bottom, 8)
82
+ }
175
83
  }
84
+ .frame(maxWidth: .infinity, alignment: .leading)
85
+ .padding(.horizontal, 24)
86
+ .padding(.bottom, 16)
87
+ .measureLineHeights(typography: .bodyDefaultRegular, oneLine: $oneLineH, twoLines: $twoLineH)
176
88
  }
177
- .frame(maxWidth: .infinity, alignment: .leading)
178
- .padding(.horizontal, 24)
179
- .padding(.bottom, 16)
180
-
89
+ }
90
+
91
+ Group {
181
92
  if shouldUseColumn {
182
93
  if buttonType == .text {
183
94
  ColumnTextButtons
@@ -192,13 +103,39 @@ public struct PopupDisplay: View {
192
103
  }
193
104
  }
194
105
  }
195
- .fixedSize(horizontal: false, vertical: true)
106
+ .popupReadHeight($actionsHeight)
107
+ }
108
+ .fixedSize(horizontal: false, vertical: true)
109
+ .background(theme.colors.background.surface)
110
+ .clipShape(RoundedRectangle(cornerRadius: Radius.L))
111
+
112
+ if isShowCloseIcon {
113
+ PopupCloseButton(action: onClose)
114
+ .offset(x: 11, y: -11)
115
+ }
116
+ }
117
+ .frame(maxWidth: layout.popupMaxWidth)
118
+ .padding(.horizontal, 12)
119
+ .accessibilityElement(children: .contain)
120
+ .accessibilityIdentifier("popup_notify")
121
+ }
122
+
123
+ @ViewBuilder
124
+ private var descriptionContent: some View {
125
+ if textHeight > maxHeight8_5 {
126
+ ScrollView(showsIndicators: false) {
127
+ descriptionText
196
128
  }
129
+ .frame(height: maxHeight8_5)
130
+ } else {
131
+ descriptionText
197
132
  }
198
- .background(Color.white.cornerRadius(15))
199
- .padding(.horizontal, 12)
200
- .accessibilityElement(children: .ignore)
201
- .accessibility(identifier: "popup_notify")
133
+ }
134
+
135
+ private var descriptionText: some View {
136
+ MomoText(description, typography: .bodyDefaultRegular, accessibilityId: "description_popup_permission")
137
+ .frame(maxWidth: .infinity, alignment: .leading)
138
+ .popupReadHeight($textHeight)
202
139
  }
203
140
 
204
141
  // MARK: Internal
@@ -248,7 +185,7 @@ public struct PopupDisplay: View {
248
185
  }
249
186
 
250
187
  var RowButtons: some View {
251
- HStack {
188
+ HStack(spacing: Spacing.S) {
252
189
  if(!closeButtonTitle.isEmpty) {
253
190
  Button(title: closeButtonTitle, action: onPressCloseButton, type: .text, size: .medium).accessibility(identifier: "btn_popup_cancel")
254
191
  }
@@ -261,7 +198,7 @@ public struct PopupDisplay: View {
261
198
  }
262
199
 
263
200
  var ColumnButtons: some View {
264
- VStack(alignment: .trailing) {
201
+ VStack(alignment: .trailing, spacing: Spacing.S) {
265
202
  Button(
266
203
  title: actionButtonTitle,
267
204
  action: onPressAction,
@@ -1,7 +1,9 @@
1
- import SDWebImageSwiftUI
2
1
  import SwiftUI
3
2
 
4
3
  public struct PopupInput: View {
4
+ @Environment(\.appTheme) private var theme
5
+ @Environment(\.screenLayout) private var layout
6
+
5
7
  // MARK: Lifecycle
6
8
 
7
9
  public init(isPresented: Binding<Bool>, title: String = "", description: String = "", numberOfButtons: Int = 1, actionButtonTitle: String = "", closeButtonTitle: String = "", onPressAction: @escaping () -> Void = {}, onPressCloseButton: @escaping () -> Void = {}, onClose: @escaping () -> Void = {}, inputPlaceholder: String = "", inputValue: Binding<String> = Binding.constant(""), onTextChange: ((String) -> Void)? = nil) {
@@ -24,22 +26,13 @@ public struct PopupInput: View {
24
26
  public var body: some View {
25
27
  VStack(spacing: 12) {
26
28
  ZStack(alignment: .topTrailing) {
27
- SwiftUI.Button(action: onPressClose) {
28
- Image(systemName: "xmark.circle.fill")
29
- .resizable()
30
- .frame(width: 16, height: 16)
31
- }.foregroundColor(Colors.black20)
32
- .background(Colors.black01.cornerRadius(8))
33
- .offset(x: 6, y: -6)
34
- .zIndex(1)
35
-
36
29
  VStack {
37
30
  VStack(alignment: .leading) {
38
- MomoText(title, typography: .headerDefaultBold, color: .black)
31
+ MomoText(title, typography: .headerDefaultBold)
39
32
  .padding(.top, 12)
40
33
  .padding(.bottom, 24)
41
34
 
42
- MomoText(description, typography: .bodyDefaultRegular, color: .black)
35
+ MomoText(description, typography: .bodyDefaultRegular)
43
36
  .opacity(0.6)
44
37
  .multilineTextAlignment(.leading)
45
38
  .padding(.bottom, 20)
@@ -61,11 +54,15 @@ public struct PopupInput: View {
61
54
  }
62
55
 
63
56
  }.clipped()
57
+
58
+ PopupCloseButton(action: onPressClose)
59
+ .offset(x: 11, y: -11)
64
60
  }
65
61
  }
66
- .background(Color.white.cornerRadius(8))
62
+ .background(theme.colors.background.surface.cornerRadius(8))
67
63
  .shadow(color: .black.opacity(0.08), radius: 2, x: 0, y: 0)
68
64
  .shadow(color: .black.opacity(0.16), radius: 24, x: 0, y: 0)
65
+ .frame(maxWidth: layout.popupMaxWidth)
69
66
  .padding(.horizontal, 40)
70
67
  }
71
68
 
@@ -0,0 +1,138 @@
1
+ import SDWebImageSwiftUI
2
+ import SwiftUI
3
+
4
+ // Width of a system alert on the iPhone Duo inner display, in both orientations (RN Popup/utils.ts).
5
+ private let duoPopupMaxWidth: CGFloat = 440
6
+
7
+ extension ScreenLayout {
8
+ /// Card width cap (Compose `popupMaxWidth`); unbounded off iPhone Duo, where the popup's own padding sets the width.
9
+ var popupMaxWidth: CGFloat {
10
+ isDuoDevice ? duoPopupMaxWidth : .infinity
11
+ }
12
+
13
+ /// Room left for a popup on iPhone Duo, where landscape is too short for it; nil elsewhere.
14
+ var popupMaxHeight: CGFloat? {
15
+ guard isDuoDevice else { return nil }
16
+ return height - duoTopInset - insets.bottom - Spacing.XL * 2
17
+ }
18
+ }
19
+
20
+ /// Scrolls `content` once it is taller than `maxHeight`; otherwise sizes to it.
21
+ struct PopupScrollBody<Content: View>: View {
22
+ let maxHeight: CGFloat?
23
+ @ViewBuilder let content: () -> Content
24
+
25
+ @State private var contentHeight: CGFloat = 0
26
+
27
+ var body: some View {
28
+ if let maxHeight {
29
+ ScrollView(.vertical, showsIndicators: false) {
30
+ content()
31
+ .background(
32
+ GeometryReader { proxy in
33
+ Color.clear
34
+ .onAppear { contentHeight = proxy.size.height }
35
+ .onChange(of: proxy.size.height) { contentHeight = $0 }
36
+ }
37
+ )
38
+ }
39
+ .frame(height: min(contentHeight, max(maxHeight, 0)))
40
+ } else {
41
+ content()
42
+ }
43
+ }
44
+ }
45
+
46
+ /// Height of `lines` (may be fractional) lines measured by `measureLineHeights`; nil until measured.
47
+ func popupLinesHeight(_ lines: CGFloat, oneLine: CGFloat, twoLines: CGFloat) -> CGFloat? {
48
+ guard oneLine > 0, twoLines > oneLine else { return nil }
49
+ return oneLine + (lines - 1) * (twoLines - oneLine)
50
+ }
51
+
52
+ extension View {
53
+ /// Measures one and two lines of `typography` as MomoText lays them out, its line spacing included.
54
+ func measureLineHeights(typography: TypographyStyle,
55
+ oneLine: Binding<CGFloat>,
56
+ twoLines: Binding<CGFloat>) -> some View {
57
+ overlay(
58
+ VStack(spacing: 0) {
59
+ MomoText("A", typography: typography)
60
+ .fixedSize()
61
+ .popupReadHeight(oneLine)
62
+ MomoText("A\nA", typography: typography)
63
+ .fixedSize()
64
+ .popupReadHeight(twoLines)
65
+ }
66
+ .frame(width: 0, height: 0)
67
+ .hidden()
68
+ .accessibilityHidden(true)
69
+ )
70
+ }
71
+
72
+ func popupReadHeight(_ height: Binding<CGFloat>) -> some View {
73
+ background(
74
+ GeometryReader { proxy in
75
+ Color.clear
76
+ .onAppear { height.wrappedValue = proxy.size.height }
77
+ .onChange(of: proxy.size.height) { height.wrappedValue = $0 }
78
+ }
79
+ )
80
+ }
81
+ }
82
+
83
+ /// 22pt round close button shared by the popups (Compose `PopupNotify` / `PopupPromotion`).
84
+ struct PopupCloseButton: View {
85
+ @Environment(\.appTheme) private var theme
86
+ var tapSize: CGFloat = 22
87
+ let action: () -> Void
88
+
89
+ var body: some View {
90
+ SwiftUI.Button(action: action) {
91
+ Circle()
92
+ .fill(theme.colors.text.default)
93
+ .overlay(Circle().strokeBorder(theme.colors.background.surface, lineWidth: 2))
94
+ .overlay(Icon(source: "navigation_close", size: 16, color: theme.colors.background.surface))
95
+ .frame(width: 22, height: 22)
96
+ .frame(width: tapSize, height: tapSize)
97
+ .contentShape(Rectangle())
98
+ }
99
+ .accessibilityIdentifier("ic_popup_close")
100
+ }
101
+ }
102
+
103
+ /// Compose `Image` states: skeleton while loading, `media_fail` once loading fails.
104
+ struct PopupImagePlaceholder: View {
105
+ @Environment(\.appTheme) private var theme
106
+ let failed: Bool
107
+
108
+ var body: some View {
109
+ if failed {
110
+ Icon(source: "media_fail", color: theme.colors.text.disable)
111
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
112
+ } else {
113
+ Skeleton()
114
+ }
115
+ }
116
+ }
117
+
118
+ /// 16:9 header image, cropped like Compose `Image` (ContentScale.Crop).
119
+ struct PopupHeaderImage: View {
120
+ let url: String
121
+
122
+ @State private var failed = false
123
+
124
+ var body: some View {
125
+ Color.clear
126
+ .aspectRatio(1.777, contentMode: .fit)
127
+ .frame(maxWidth: .infinity)
128
+ .overlay(
129
+ WebImage(url: URL(string: url), isAnimating: .constant(true))
130
+ .onSuccess { _, _, _ in failed = false }
131
+ .onFailure { _ in failed = true }
132
+ .resizable()
133
+ .placeholder { PopupImagePlaceholder(failed: failed || URL(string: url) == nil) }
134
+ .scaledToFill()
135
+ )
136
+ .clipped()
137
+ }
138
+ }
@@ -1,8 +1,17 @@
1
- import SDWebImageSwiftUI
2
1
  import SwiftUI
3
2
 
4
3
  public typealias PopupActionDirection = PopupButtonDirection
5
4
 
5
+ public struct PopupAction {
6
+ public let title: String
7
+ public let onPress: (() -> Void)?
8
+
9
+ public init(title: String, onPress: (() -> Void)? = nil) {
10
+ self.title = title
11
+ self.onPress = onPress
12
+ }
13
+ }
14
+
6
15
  public struct PopupNotifyProps {
7
16
  public var image: String
8
17
  public var title: String
@@ -38,19 +47,26 @@ public struct PopupNotifyProps {
38
47
  }
39
48
 
40
49
  public struct PopupNotify: View {
50
+ @Environment(\.appTheme) private var theme
41
51
  @Environment(\.localize) private var localize
42
52
  @Environment(\.momoFontScale) private var fontScale
53
+ @Environment(\.screenLayout) private var layout
43
54
 
44
55
  public let props: PopupNotifyProps
45
56
 
46
57
  @State private var descriptionHeight: CGFloat = 0
58
+ @State private var actionsHeight: CGFloat = 0
59
+ @State private var oneLineHeight: CGFloat = 0
60
+ @State private var twoLinesHeight: CGFloat = 0
47
61
 
48
62
  private var errorCodePrefix: String {
49
63
  localize.translate("errorCode")
50
64
  }
51
65
 
66
+ // ~8.5 lines as laid out (Compose measures the real lines); nominal line height until measured
52
67
  private var maxDescriptionHeight: CGFloat {
53
- fontScale(TypographyStyle.bodyDefaultRegular.lineHeight) * 8.5
68
+ popupLinesHeight(8.5, oneLine: oneLineHeight, twoLines: twoLinesHeight)
69
+ ?? fontScale(TypographyStyle.bodyDefaultRegular.lineHeight) * 8.5
54
70
  }
55
71
 
56
72
  private var shouldUseColumnButtons: Bool {
@@ -60,7 +76,9 @@ public struct PopupNotify: View {
60
76
  case .row:
61
77
  return false
62
78
  case .auto:
63
- return (props.secondary?.title.count ?? 0) > 12 || (props.primary?.title.count ?? 0) > 12
79
+ // UTF-16 length, as Kotlin and JS count it
80
+ guard let secondary = props.secondary else { return false }
81
+ return secondary.title.utf16.count > 12 || (props.primary?.title.utf16.count ?? 0) > 12
64
82
  }
65
83
  }
66
84
 
@@ -71,63 +89,60 @@ public struct PopupNotify: View {
71
89
  public var body: some View {
72
90
  ZStack(alignment: .topTrailing) {
73
91
  VStack(spacing: 0) {
74
- if !props.image.isEmpty {
75
- popupImage
76
- }
77
-
78
- VStack(alignment: .leading, spacing: 0) {
79
- MomoText(
80
- props.title,
81
- typography: .headerDefaultBold,
82
- maxLines: 2,
83
- accessibilityId: "title_popup_permission"
84
- )
85
- .padding(.bottom, Spacing.S)
86
-
87
- descriptionContent
88
-
89
- if !props.error.isEmpty {
90
- MomoText(
91
- errorCodePrefix + props.error,
92
- typography: .descriptionXsRegular,
93
- color: Colors.black12,
94
- maxLines: 1
95
- )
96
- .padding(.top, Spacing.S)
92
+ // scrolls when iPhone Duo landscape is too short; actions stay visible
93
+ PopupScrollBody(maxHeight: layout.popupMaxHeight.map { $0 - actionsHeight }) {
94
+ VStack(spacing: 0) {
95
+ if !props.image.isEmpty {
96
+ PopupHeaderImage(url: props.image)
97
+ }
98
+ textContent
97
99
  }
98
100
  }
99
- .frame(maxWidth: .infinity, alignment: .leading)
100
- .padding(Spacing.XL)
101
101
 
102
102
  actionButtons
103
103
  .padding(.horizontal, Spacing.XL)
104
104
  .padding(.bottom, Spacing.XL)
105
+ .popupReadHeight($actionsHeight)
105
106
  }
106
- .background(Colors.black01)
107
+ .background(theme.colors.background.surface)
107
108
  .clipShape(RoundedRectangle(cornerRadius: Radius.L))
108
109
 
109
110
  if props.isShowCloseIcon {
110
- closeButton
111
- .offset(x: 1, y: -11)
111
+ PopupCloseButton(action: props.onIconClose)
112
+ .offset(x: 11, y: -11)
112
113
  }
113
114
  }
115
+ .frame(maxWidth: layout.popupMaxWidth)
114
116
  .padding(.horizontal, Spacing.M)
115
- .accessibilityElement(children: .ignore)
117
+ .accessibilityElement(children: .contain)
116
118
  .accessibilityIdentifier("popup_notify")
117
119
  }
118
120
 
119
- private var popupImage: some View {
120
- WebImage(url: URL(string: props.image), isAnimating: .constant(true))
121
- .resizable()
122
- .placeholder {
123
- Rectangle()
124
- .fill(Colors.black03)
125
- .aspectRatio(1.777, contentMode: .fit)
126
- .frame(maxWidth: .infinity)
121
+ private var textContent: some View {
122
+ VStack(alignment: .leading, spacing: 0) {
123
+ MomoText(
124
+ props.title,
125
+ typography: .headerDefaultBold,
126
+ maxLines: 2,
127
+ accessibilityId: "title_popup_permission"
128
+ )
129
+ .padding(.bottom, Spacing.S)
130
+
131
+ descriptionContent
132
+
133
+ if !props.error.isEmpty {
134
+ MomoText(
135
+ errorCodePrefix + props.error,
136
+ typography: .descriptionXsRegular,
137
+ color: theme.colors.text.hint,
138
+ maxLines: 1
139
+ )
140
+ .padding(.top, Spacing.S)
127
141
  }
128
- .aspectRatio(1.777, contentMode: .fit)
129
- .frame(maxWidth: .infinity)
130
- .clipShape(RoundedCorner(radius: Radius.L, corners: [.topLeft, .topRight]))
142
+ }
143
+ .frame(maxWidth: .infinity, alignment: .leading)
144
+ .padding(Spacing.XL)
145
+ .measureLineHeights(typography: .bodyDefaultRegular, oneLine: $oneLineHeight, twoLines: $twoLinesHeight)
131
146
  }
132
147
 
133
148
  @ViewBuilder
@@ -149,15 +164,7 @@ public struct PopupNotify: View {
149
164
  accessibilityId: "description_popup_permission"
150
165
  )
151
166
  .frame(maxWidth: .infinity, alignment: .leading)
152
- .background(
153
- GeometryReader { proxy in
154
- Color.clear
155
- .onAppear { descriptionHeight = proxy.size.height }
156
- .onChange(of: props.description) { _ in
157
- descriptionHeight = proxy.size.height
158
- }
159
- }
160
- )
167
+ .popupReadHeight($descriptionHeight)
161
168
  }
162
169
 
163
170
  @ViewBuilder
@@ -199,21 +206,4 @@ public struct PopupNotify: View {
199
206
  )
200
207
  .accessibilityIdentifier("btn_popup_cancel")
201
208
  }
202
-
203
- private var closeButton: some View {
204
- SwiftUI.Button(action: props.onIconClose) {
205
- ZStack {
206
- Circle()
207
- .fill(Colors.black17)
208
- .overlay(
209
- Circle()
210
- .stroke(Colors.black01, lineWidth: 2)
211
- )
212
- .frame(width: 22, height: 22)
213
-
214
- Icon(source: "navigation_close", size: 16, color: Colors.black01)
215
- }
216
- }
217
- .accessibilityIdentifier("ic_popup_close")
218
- }
219
209
  }