@momo-kits/native-kits 0.167.1-beta.9 → 0.167.1-duo-search.1-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 (196) hide show
  1. package/CLAUDE.md +36 -0
  2. package/build.gradle.kts +11 -0
  3. package/compose/build.gradle.kts +204 -0
  4. package/compose/build.gradle.kts.backup +204 -0
  5. package/compose/compose.podspec +47 -0
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +19 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  10. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +153 -0
  11. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +52 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +124 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +183 -0
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +207 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +39 -0
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +293 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +833 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +110 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +378 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +82 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +356 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +113 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +542 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +49 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +76 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +189 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +475 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +266 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +240 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +223 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +264 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +185 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +361 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +108 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +310 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +323 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +220 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +199 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +91 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +552 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +88 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +125 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +207 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +498 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +176 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +234 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +188 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +325 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +16 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +18 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +22 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +188 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +297 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +54 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +50 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +232 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +41 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +338 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +137 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +106 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +205 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +393 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +708 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +35 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +203 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +232 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +98 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +312 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +408 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +92 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +105 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +74 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +366 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +37 -0
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +363 -0
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +185 -0
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +132 -0
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +127 -0
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +74 -0
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +13 -0
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
  132. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  133. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +118 -0
  134. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +40 -0
  135. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
  136. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +32 -0
  137. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
  138. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  139. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +217 -0
  140. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +86 -0
  141. package/gradle/libs.versions.toml +69 -0
  142. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  143. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  144. package/gradle.properties +26 -0
  145. package/gradlew +252 -0
  146. package/gradlew.bat +94 -0
  147. package/ios/Application/ApplicationEnvironment.swift +6 -0
  148. package/ios/Application/Components.swift +1 -3
  149. package/ios/Application/HeaderRight.swift +32 -14
  150. package/ios/Application/Layout/ScreenLayout.swift +312 -0
  151. package/ios/Application/Navigation/BottomTab/BottomTab.swift +67 -10
  152. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +5 -2
  153. package/ios/Application/Navigation/BottomTab/DuoBarCompression.swift +105 -0
  154. package/ios/Application/Navigation/BottomTab/DuoTabBar.swift +304 -0
  155. package/ios/Application/Navigation/HeaderUser.swift +4 -4
  156. package/ios/Application/Navigation/NavigationContainer.swift +48 -42
  157. package/ios/Application/Navigation/Navigator.swift +122 -29
  158. package/ios/Application/Navigation/NavigatorManager.swift +13 -6
  159. package/ios/Application/Navigation/Overplay/BottomSheet.swift +41 -4
  160. package/ios/Application/Navigation/Overplay/ModalScreen.swift +7 -0
  161. package/ios/Application/Navigation/component/DuoScrollGradient.swift +54 -0
  162. package/ios/Application/Navigation/component/Header.swift +11 -6
  163. package/ios/Application/Navigation/component/HeaderBackground.swift +3 -2
  164. package/ios/Application/Navigation/component/NavigationFooter.swift +1 -1
  165. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +93 -9
  166. package/ios/Application/Navigation/component/SideBar.swift +155 -0
  167. package/ios/Application/StackScreen.swift +175 -18
  168. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +59 -5
  169. package/ios/Colors+Radius+Spacing/Colors.swift +1 -1
  170. package/ios/Extensions/Color++.swift +20 -9
  171. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  172. package/ios/Icon/Icon.swift +14 -22
  173. package/ios/Icon/Icons.swift +1329 -0
  174. package/ios/Image/Image.swift +16 -5
  175. package/ios/Lottie/LottieView.swift +4 -2
  176. package/ios/Popup/PopupDisplay.swift +84 -67
  177. package/ios/Popup/PopupLayout.swift +45 -0
  178. package/ios/Popup/PopupNotify.swift +44 -26
  179. package/ios/Popup/PopupPromotion.swift +18 -6
  180. package/ios/Resources/dot_loading.json +558 -0
  181. package/ios/Resources/lottie_circle_loader.json +1 -0
  182. package/ios/Template/TrustBanner/TrustBanner.swift +69 -31
  183. package/ios/Typography/FontScale.swift +3 -3
  184. package/ios/native-kits.podspec +1 -1
  185. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  186. package/ios-demo/README.md +120 -0
  187. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
  188. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
  189. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
  190. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
  191. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
  192. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
  193. package/local.properties +8 -0
  194. package/package.json +1 -1
  195. package/scripts/gen-ios-icons.mjs +36 -0
  196. package/settings.gradle.kts +64 -0
@@ -41,11 +41,7 @@ public struct ImageView: View {
41
41
  .placeholder {
42
42
  VStack(alignment: .center, content: {
43
43
  if error {
44
- Image("media_fail")
45
- .resizable()
46
- .renderingMode(.template)
47
- .foregroundColor(theme.colors.text.disable)
48
- .frame(width: 24, height: 24)
44
+ ImageErrorIcon(color: theme.colors.text.disable)
49
45
  } else if placeholder != nil {
50
46
  placeholder
51
47
  }
@@ -74,6 +70,21 @@ public struct ImageView: View {
74
70
  var url: String
75
71
  }
76
72
 
73
+ // A bare WebImage on purpose: going through `Icon` nests another `ImageView`, whose own
74
+ // failure would render this placeholder again, without end while offline.
75
+ private struct ImageErrorIcon: View {
76
+ let color: Color
77
+
78
+ var body: some View {
79
+ WebImage(url: URL(string: iconSources["media_fail"] ?? ""))
80
+ .resizable()
81
+ .renderingMode(.template)
82
+ .scaledToFit()
83
+ .foregroundColor(color)
84
+ .frame(width: 24, height: 24)
85
+ }
86
+ }
87
+
77
88
  private struct ColorMultiplyModifier: ViewModifier {
78
89
  let color: Color?
79
90
 
@@ -36,7 +36,8 @@ struct LottieView: UIViewRepresentable {
36
36
  animationView.animation = cachedAnimation
37
37
  setupAnimationView(animationView, view)
38
38
  } else {
39
- LottieAnimation.loadedFrom(url: URL(string: url)!, closure: { animation in
39
+ guard let animationURL = URL(string: url) else { return view }
40
+ LottieAnimation.loadedFrom(url: animationURL, closure: { animation in
40
41
  if let animation = animation {
41
42
  Self.animationCache.setObject(animation, forKey: url as NSString)
42
43
  animationView.animation = animation
@@ -49,7 +50,8 @@ struct LottieView: UIViewRepresentable {
49
50
  if let cachedAnimation = Self.animationCache.object(forKey: name as NSString) {
50
51
  animationView.animation = cachedAnimation
51
52
  setupAnimationView(animationView, view)
52
- } else if let animation = LottieAnimation.named(name) {
53
+ } else if let animation = LottieAnimation.named(name, bundle: MoMoUIKitsResources.bundle)
54
+ ?? LottieAnimation.named(name) {
53
55
  Self.animationCache.setObject(animation, forKey: name as NSString)
54
56
  animationView.animation = animation
55
57
  setupAnimationView(animationView, view)
@@ -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,88 +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))
215
+ .frame(maxWidth: layout.popupMaxWidth)
199
216
  .padding(.horizontal, 12)
200
217
  .accessibilityElement(children: .ignore)
201
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
+ }
@@ -40,10 +40,12 @@ public struct PopupNotifyProps {
40
40
  public struct PopupNotify: View {
41
41
  @Environment(\.localize) private var localize
42
42
  @Environment(\.momoFontScale) private var fontScale
43
+ @Environment(\.screenLayout) private var layout
43
44
 
44
45
  public let props: PopupNotifyProps
45
46
 
46
47
  @State private var descriptionHeight: CGFloat = 0
48
+ @State private var actionsHeight: CGFloat = 0
47
49
 
48
50
  private var errorCodePrefix: String {
49
51
  localize.translate("errorCode")
@@ -71,51 +73,67 @@ public struct PopupNotify: View {
71
73
  public var body: some View {
72
74
  ZStack(alignment: .topTrailing) {
73
75
  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)
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
97
83
  }
98
84
  }
99
- .frame(maxWidth: .infinity, alignment: .leading)
100
- .padding(Spacing.XL)
101
85
 
102
86
  actionButtons
103
87
  .padding(.horizontal, Spacing.XL)
104
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
+ )
105
96
  }
106
97
  .background(Colors.black01)
107
98
  .clipShape(RoundedRectangle(cornerRadius: Radius.L))
108
99
 
109
100
  if props.isShowCloseIcon {
110
101
  closeButton
111
- .offset(x: 1, y: -11)
102
+ .offset(x: 11, y: -11)
112
103
  }
113
104
  }
105
+ .frame(maxWidth: layout.popupMaxWidth)
114
106
  .padding(.horizontal, Spacing.M)
115
107
  .accessibilityElement(children: .ignore)
116
108
  .accessibilityIdentifier("popup_notify")
117
109
  }
118
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
+
119
137
  private var popupImage: some View {
120
138
  WebImage(url: URL(string: props.image), isAnimating: .constant(true))
121
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: UIScreen.main.bounds.width - Spacing.XL * 2)
37
+ Icon(source: "media_fail", size: imageWidth)
26
38
  }
27
39
  else {
28
- WebImage(url: URL(string: imageUrl)!)
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: UIScreen.main.bounds.width - 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: UIScreen.main.bounds.width - 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?()