@momo-kits/native-kits 0.167.1-duo.1-debug → 0.167.1-duo.10

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 (260) hide show
  1. package/CLAUDE.md +32 -0
  2. package/ios/Application/ApplicationEnvironment.swift +17 -0
  3. package/ios/Application/ComposeApi.swift +2 -1
  4. package/ios/Application/{Components.swift → LegacyHeader.swift} +7 -65
  5. package/ios/Application/Screen.swift +3 -2
  6. package/ios/Avatar/Avatar.swift +4 -3
  7. package/ios/Badge/Badge.swift +6 -4
  8. package/ios/Badge/BadgeDot.swift +1 -1
  9. package/ios/Badge/BadgeRibbon.swift +0 -1
  10. package/ios/Button/Button.swift +15 -12
  11. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
  12. package/ios/Carousel/Carousel.swift +29 -12
  13. package/ios/Checkbox/Checkbox.swift +7 -3
  14. package/ios/Chip/Chip.swift +5 -4
  15. package/ios/Collapse/Collapse.swift +70 -19
  16. package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
  17. package/ios/DateTimePicker/DateTimePicker.swift +54 -20
  18. package/ios/DateTimePicker/WheelPicker.swift +103 -105
  19. package/ios/Divider/Divider.swift +37 -4
  20. package/ios/Extensions/Color++.swift +38 -9
  21. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  22. package/ios/Extensions/SkeletonUICompat.swift +11 -0
  23. package/ios/Icon/Icon.swift +21 -23
  24. package/ios/Icon/Icons.swift +1329 -0
  25. package/ios/IconButton/IconButton.swift +15 -7
  26. package/ios/Image/Image.swift +7 -23
  27. package/ios/Information/Information.swift +27 -9
  28. package/ios/Input/ErrorView.swift +2 -2
  29. package/ios/Input/Input.swift +160 -147
  30. package/ios/Input/InputPhoneNumber.swift +12 -9
  31. package/ios/Input/InputSearch.swift +102 -75
  32. package/ios/Input/InputTextArea.swift +128 -145
  33. package/ios/InputDropDown/InputDropDown.swift +10 -7
  34. package/ios/InputMoney/InputMoney.swift +33 -11
  35. package/ios/InputOTP/InputOTP.swift +22 -12
  36. package/ios/Layout/Card.swift +50 -0
  37. package/ios/Layout/GridContext.swift +123 -3
  38. package/ios/Layout/Item.swift +5 -4
  39. package/ios/Layout/ScreenLayout.swift +312 -0
  40. package/ios/Layout/Section.swift +4 -100
  41. package/ios/Loader/Loader.swift +5 -9
  42. package/ios/Lottie/LottieView.swift +14 -2
  43. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +97 -8
  44. package/ios/Navigation/BottomTab/BottomTab.swift +187 -0
  45. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +15 -7
  46. package/ios/Navigation/BottomTab/DuoBarCompression.swift +105 -0
  47. package/ios/Navigation/BottomTab/DuoTabBar.swift +304 -0
  48. package/ios/{Application → Navigation/Component}/FloatingButton.swift +14 -3
  49. package/ios/Navigation/Component/Header.swift +493 -0
  50. package/ios/Navigation/Component/HeaderBackground.swift +134 -0
  51. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  52. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  53. package/ios/Navigation/Component/HeaderRight.swift +386 -0
  54. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  55. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
  56. package/ios/Navigation/Component/SideBar.swift +176 -0
  57. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +5 -5
  58. package/ios/Navigation/HostBackGestureBridge.swift +57 -0
  59. package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +5 -1
  60. package/ios/Navigation/Navigation.swift +190 -0
  61. package/ios/{Application/Navigation → Navigation}/NavigationContainer.swift +92 -81
  62. package/ios/{Application/Navigation → Navigation}/Navigator.swift +185 -36
  63. package/ios/Navigation/StackScreen.swift +782 -0
  64. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  65. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  66. package/ios/Pagination/PaginationScroll.swift +2 -2
  67. package/ios/Popup/PopupDisplay.swift +72 -136
  68. package/ios/Popup/PopupInput.swift +10 -13
  69. package/ios/Popup/PopupLayout.swift +140 -0
  70. package/ios/Popup/PopupNotify.swift +56 -73
  71. package/ios/Popup/PopupPromotion.swift +31 -47
  72. package/ios/PopupView/FullscreenPopup.swift +13 -19
  73. package/ios/PopupView/PopupView.swift +1 -1
  74. package/ios/PopupView/Utils++.swift +0 -8
  75. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  76. package/ios/Radio/Radio.swift +2 -1
  77. package/ios/Rating/Rating.swift +9 -1
  78. package/ios/Resources/dot_loading.json +558 -0
  79. package/ios/Resources/lottie_circle_loader.json +1 -0
  80. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  81. package/ios/Skeleton/Skeleton.swift +22 -27
  82. package/ios/Slider/Slider.swift +16 -18
  83. package/ios/Stepper/Stepper.swift +6 -1
  84. package/ios/Steps/Steps.swift +8 -6
  85. package/ios/SuggestAction/SuggestAction.swift +5 -0
  86. package/ios/Swipeable/SwipeCell.swift +10 -6
  87. package/ios/Switch/Switch.swift +22 -20
  88. package/ios/TabView/TabView.swift +102 -21
  89. package/ios/Tag/Tag.swift +15 -4
  90. package/ios/Template/Logo/Logo.swift +2 -1
  91. package/ios/Template/TrustBanner/TrustBanner.swift +106 -54
  92. package/ios/Title/Title.swift +6 -17
  93. package/ios/Tooltip/Tooltip.swift +29 -14
  94. package/ios/Typography/Text.swift +61 -19
  95. package/ios/Uploader/Uploader.swift +3 -1
  96. package/ios/native-kits.podspec +2 -1
  97. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  98. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  99. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
  100. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +1 -0
  101. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
  102. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  103. package/package.json +1 -1
  104. package/scripts/gen-ios-icons.mjs +36 -0
  105. package/.claude/settings.local.json +0 -185
  106. package/build.gradle.kts +0 -11
  107. package/compose/build.gradle.kts +0 -190
  108. package/compose/build.gradle.kts.backup +0 -190
  109. package/compose/compose.podspec +0 -47
  110. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -19
  111. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
  112. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
  113. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
  114. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -151
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -124
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -831
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -56
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
  132. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -82
  133. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
  134. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
  135. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
  136. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -356
  137. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -113
  138. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
  139. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
  140. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
  141. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
  142. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
  143. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -76
  144. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
  145. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
  146. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
  147. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -475
  148. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
  149. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -266
  150. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -240
  151. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
  152. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -264
  153. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
  154. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
  155. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
  156. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
  157. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
  158. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
  159. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
  160. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -334
  161. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -98
  162. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -310
  163. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
  164. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
  165. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
  166. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
  167. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -323
  168. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
  169. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
  170. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
  171. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -199
  172. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -91
  173. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -552
  174. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -88
  175. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
  176. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
  177. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -498
  178. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
  179. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
  180. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
  181. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
  182. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -234
  183. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -188
  184. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
  185. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -16
  186. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
  187. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
  188. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
  189. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
  190. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -188
  191. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -297
  192. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -52
  193. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -38
  194. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -39
  195. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
  196. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
  197. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
  198. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
  199. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
  200. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
  201. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -299
  202. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +0 -58
  203. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -129
  204. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -106
  205. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -201
  206. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -393
  207. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
  208. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -596
  209. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
  210. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -35
  211. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -175
  212. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -225
  213. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
  214. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
  215. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -360
  216. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -83
  217. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -106
  218. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -75
  219. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -309
  220. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -37
  221. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -364
  222. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -132
  223. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -127
  224. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
  225. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
  226. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
  227. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -71
  228. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
  229. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
  230. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
  231. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -115
  232. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -40
  233. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
  234. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -32
  235. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
  236. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
  237. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -213
  238. package/gradle/libs.versions.toml +0 -69
  239. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  240. package/gradle/wrapper/gradle-wrapper.properties +0 -8
  241. package/gradle.properties +0 -26
  242. package/gradlew +0 -252
  243. package/gradlew.bat +0 -94
  244. package/ios/Application/HeaderRight.swift +0 -306
  245. package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
  246. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  247. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  248. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  249. package/ios/Application/Navigation/NavigatorManager.swift +0 -22
  250. package/ios/Application/Navigation/component/Header.swift +0 -178
  251. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
  252. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  253. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  254. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  255. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
  256. package/ios/Application/StackScreen.swift +0 -492
  257. package/ios-demo/README.md +0 -120
  258. package/local.properties +0 -8
  259. package/settings.gradle.kts +0 -64
  260. /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
@@ -1,12 +1,15 @@
1
- import SDWebImageSwiftUI
2
1
  import SwiftUI
3
2
 
4
3
  public typealias PopupActionDirection = PopupButtonDirection
5
4
 
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)
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
+ }
10
13
  }
11
14
 
12
15
  public struct PopupNotifyProps {
@@ -44,19 +47,26 @@ public struct PopupNotifyProps {
44
47
  }
45
48
 
46
49
  public struct PopupNotify: View {
50
+ @Environment(\.appTheme) private var theme
47
51
  @Environment(\.localize) private var localize
48
52
  @Environment(\.momoFontScale) private var fontScale
53
+ @Environment(\.screenLayout) private var layout
49
54
 
50
55
  public let props: PopupNotifyProps
51
56
 
52
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
53
61
 
54
62
  private var errorCodePrefix: String {
55
63
  localize.translate("errorCode")
56
64
  }
57
65
 
66
+ // ~8.5 lines as laid out (Compose measures the real lines); nominal line height until measured
58
67
  private var maxDescriptionHeight: CGFloat {
59
- fontScale(TypographyStyle.bodyDefaultRegular.lineHeight) * 8.5
68
+ popupLinesHeight(8.5, oneLine: oneLineHeight, twoLines: twoLinesHeight)
69
+ ?? fontScale(TypographyStyle.bodyDefaultRegular.lineHeight) * 8.5
60
70
  }
61
71
 
62
72
  private var shouldUseColumnButtons: Bool {
@@ -66,7 +76,9 @@ public struct PopupNotify: View {
66
76
  case .row:
67
77
  return false
68
78
  case .auto:
69
- 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
70
82
  }
71
83
  }
72
84
 
@@ -77,64 +89,60 @@ public struct PopupNotify: View {
77
89
  public var body: some View {
78
90
  ZStack(alignment: .topTrailing) {
79
91
  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)
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
103
99
  }
104
100
  }
105
- .frame(maxWidth: .infinity, alignment: .leading)
106
- .padding(Spacing.XL)
107
101
 
108
102
  actionButtons
109
103
  .padding(.horizontal, Spacing.XL)
110
104
  .padding(.bottom, Spacing.XL)
105
+ .popupReadHeight($actionsHeight)
111
106
  }
112
- .background(Colors.black01)
107
+ .background(theme.colors.background.surface)
113
108
  .clipShape(RoundedRectangle(cornerRadius: Radius.L))
114
109
 
115
110
  if props.isShowCloseIcon {
116
- closeButton
111
+ PopupCloseButton(action: props.onIconClose)
117
112
  .offset(x: 11, y: -11)
118
113
  }
119
114
  }
120
- .frame(maxWidth: popupMaxWidth - Spacing.M * 2)
115
+ .frame(maxWidth: layout.popupMaxWidth)
121
116
  .padding(.horizontal, Spacing.M)
122
- .accessibilityElement(children: .ignore)
117
+ .accessibilityElement(children: .contain)
123
118
  .accessibilityIdentifier("popup_notify")
124
119
  }
125
120
 
126
- private var popupImage: some View {
127
- WebImage(url: URL(string: props.image), isAnimating: .constant(true))
128
- .resizable()
129
- .placeholder {
130
- Rectangle()
131
- .fill(Colors.black03)
132
- .aspectRatio(1.777, contentMode: .fit)
133
- .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)
134
141
  }
135
- .aspectRatio(1.777, contentMode: .fit)
136
- .frame(maxWidth: .infinity)
137
- .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)
138
146
  }
139
147
 
140
148
  @ViewBuilder
@@ -156,15 +164,7 @@ public struct PopupNotify: View {
156
164
  accessibilityId: "description_popup_permission"
157
165
  )
158
166
  .frame(maxWidth: .infinity, alignment: .leading)
159
- .background(
160
- GeometryReader { proxy in
161
- Color.clear
162
- .onAppear { descriptionHeight = proxy.size.height }
163
- .onChange(of: props.description) { _ in
164
- descriptionHeight = proxy.size.height
165
- }
166
- }
167
- )
167
+ .popupReadHeight($descriptionHeight)
168
168
  }
169
169
 
170
170
  @ViewBuilder
@@ -206,21 +206,4 @@ public struct PopupNotify: View {
206
206
  )
207
207
  .accessibilityIdentifier("btn_popup_cancel")
208
208
  }
209
-
210
- private var closeButton: some View {
211
- SwiftUI.Button(action: props.onIconClose) {
212
- ZStack {
213
- Circle()
214
- .fill(Colors.black17)
215
- .overlay(
216
- Circle()
217
- .stroke(Colors.black01, lineWidth: 2)
218
- )
219
- .frame(width: 22, height: 22)
220
-
221
- Icon(source: "navigation_close", size: 16, color: Colors.black01)
222
- }
223
- }
224
- .accessibilityIdentifier("ic_popup_close")
225
- }
226
209
  }
@@ -2,12 +2,13 @@ import SwiftUI
2
2
  import SDWebImageSwiftUI
3
3
 
4
4
  public struct PopupPromotion: View {
5
- @Environment(\.appTheme) private var theme
5
+ @Environment(\.screenLayout) private var layout
6
6
 
7
7
  var imageUrl: String
8
8
  var onIconClose: (() -> Void)?
9
9
  var onAction: (() -> Void)?
10
10
 
11
+ @State private var imageFailed = false
11
12
 
12
13
  public init(
13
14
  imageUrl: String,
@@ -19,56 +20,39 @@ public struct PopupPromotion: View {
19
20
  self.onAction = onAction
20
21
  }
21
22
 
22
- public var body: some View {
23
- VStack(spacing: 8) {
24
- if(imageUrl.isEmpty) {
25
- Icon(source: "media_fail", size: popupMaxWidth - Spacing.XL * 2)
26
- }
27
- else {
28
- WebImage(url: URL(string: imageUrl)!)
29
- .resizable()
30
- .placeholder {
31
- Color.gray
32
- .aspectRatio(0.72, contentMode: .fit)
33
- .frame(maxWidth: popupMaxWidth - Spacing.XL * 2)
34
- .clipped()
35
- }
36
- .aspectRatio(0.72, contentMode: .fit)
37
- .frame(maxWidth: popupMaxWidth - Spacing.XL * 2, alignment: .center)
38
- .clipped()
39
- .onTapGesture {
40
- onAction?()
41
- }
42
- }
43
- buildCloseIcon()
44
- .accessibility(identifier: "ic_popup_close")
45
- }
46
- .accessibilityElement(children: .ignore)
47
- .accessibility(identifier: "popup_promotion")
48
- }
23
+ private static let aspectRatio: CGFloat = 0.72
24
+ private static let closeTapSize: CGFloat = 40
25
+ // close button row under the image: top padding + 40pt tap target
26
+ private static let closeRowHeight: CGFloat = Spacing.S + closeTapSize
49
27
 
50
- @ViewBuilder
51
- private func buildCloseIcon() -> some View {
52
- HStack(alignment: .center, spacing: 0) {
53
- SwiftUI.Button(action: {
54
- onIconClose?()
55
- }) {
56
- ZStack {
57
- Circle()
58
- .strokeBorder(theme.colors.background.surface, lineWidth: 2)
59
- .background(Circle().fill(theme.colors.text.default))
60
- .frame(width: 22, height: 22)
28
+ // fit the portrait image by height when iPhone Duo leaves too little room
29
+ private var imageWidth: CGFloat {
30
+ let width = min(layout.width - Spacing.M * 2, layout.popupMaxWidth)
31
+ guard let maxHeight = layout.popupMaxHeight else { return width }
32
+ return min(width, (maxHeight - Self.closeRowHeight) * Self.aspectRatio)
33
+ }
61
34
 
62
- Icon(source: "navigation_close", size: 16, color: theme.colors.background.surface)
35
+ public var body: some View {
36
+ VStack(spacing: 0) {
37
+ // stretched like Compose's ContentScale.FillBounds; an empty url shows the error state, as in Compose
38
+ WebImage(url: URL(string: imageUrl))
39
+ .onSuccess { _, _, _ in imageFailed = false }
40
+ .onFailure { _ in imageFailed = true }
41
+ .resizable()
42
+ .placeholder { PopupImagePlaceholder(failed: imageFailed || URL(string: imageUrl) == nil) }
43
+ .aspectRatio(Self.aspectRatio, contentMode: .fit)
44
+ .frame(maxWidth: imageWidth)
45
+ .contentShape(Rectangle())
46
+ .onTapGesture {
47
+ onAction?()
63
48
  }
64
- .frame(width: 40, height: 40)
65
- .padding(8)
66
- .zIndex(1)
49
+
50
+ PopupCloseButton(tapSize: Self.closeTapSize) {
51
+ onIconClose?()
67
52
  }
53
+ .padding(.top, Spacing.S)
68
54
  }
55
+ .accessibilityElement(children: .contain)
56
+ .accessibilityIdentifier("popup_promotion")
69
57
  }
70
-
71
58
  }
72
-
73
-
74
-
@@ -51,12 +51,9 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
51
51
  @State private var opacity = 0.0
52
52
 
53
53
  // MARK: - Autohide
54
- /// Class reference for capturing a weak reference later in dispatch work holder.
55
- private var isPresentedRef: ClassReference<Binding<Bool>>?
56
- private var itemRef: ClassReference<Binding<Item?>>?
57
-
58
- /// holder for autohiding dispatch work (to be able to cancel it when needed)
59
- private var dispatchWorkHolder = DispatchWorkHolder()
54
+ /// holder for autohiding dispatch work (to be able to cancel it when needed);
55
+ /// state so it outlives re-renders, which would otherwise orphan the pending work
56
+ @State private var dispatchWorkHolder = DispatchWorkHolder()
60
57
 
61
58
  // MARK: - Internal
62
59
  /// Set dismiss souce to pass to dismiss callback
@@ -85,9 +82,6 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
85
82
  self.backgroundColor = backgroundColor
86
83
  self.dismissCallback = dismissCallback
87
84
  self.view = view
88
-
89
- self.isPresentedRef = ClassReference(self.$isPresented)
90
- self.itemRef = ClassReference(self.$item)
91
85
  }
92
86
 
93
87
  init(item: Binding<Item?>,
@@ -113,9 +107,6 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
113
107
  self.backgroundColor = backgroundColor
114
108
  self.dismissCallback = dismissCallback
115
109
  self.view = view
116
-
117
- self.isPresentedRef = ClassReference(self.$isPresented)
118
- self.itemRef = ClassReference(self.$item)
119
110
  }
120
111
 
121
112
  public func body(content: Content) -> some View {
@@ -214,7 +205,8 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
214
205
  }
215
206
 
216
207
  func onAnimationCompleted() -> () {
217
- if shouldShowContent { // return if this was called on showing animation, only proceed if called on hiding
208
+ // only proceed on hiding, once: both the offset animation and the fallback timer land here
209
+ if shouldShowContent || !showContent {
218
210
  return
219
211
  }
220
212
  showContent = false // unload popup body after hiding animation is done
@@ -229,12 +221,14 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
229
221
  if let autohideIn = autohideIn {
230
222
  dispatchWorkHolder.work?.cancel()
231
223
 
232
- // Weak reference to avoid the work item capturing the struct,
233
- // which would create a retain cycle with the work holder itself.
234
- dispatchWorkHolder.work = DispatchWorkItem(block: { [weak isPresentedRef, weak itemRef] in
235
- isPresentedRef?.value.wrappedValue = false
236
- itemRef?.value.wrappedValue = nil
237
- dismissSource = .autohide
224
+ // bindings only: capturing the struct would retain the work holder in a cycle
225
+ let isPresented = $isPresented
226
+ let item = $item
227
+ let dismissSource = $dismissSource
228
+ dispatchWorkHolder.work = DispatchWorkItem(block: {
229
+ dismissSource.wrappedValue = .autohide
230
+ isPresented.wrappedValue = false
231
+ item.wrappedValue = nil
238
232
  })
239
233
  if sheetPresented, let work = dispatchWorkHolder.work {
240
234
  DispatchQueue.main.asyncAfter(deadline: .now() + autohideIn, execute: work)
@@ -239,7 +239,7 @@ public struct Popup<Item: Equatable, PopupContent: View>: ViewModifier {
239
239
  .onAnimationCompleted(for: currentOffset) {
240
240
  animationCompletedCallback()
241
241
  }
242
- .animation(animation)
242
+ .animation(animation, value: currentOffset)
243
243
  }
244
244
 
245
245
  #if !os(tvOS)
@@ -5,14 +5,6 @@ final class DispatchWorkHolder {
5
5
  var work: DispatchWorkItem?
6
6
  }
7
7
 
8
- final class ClassReference<T> {
9
- var value: T
10
-
11
- init(_ value: T) {
12
- self.value = value
13
- }
14
- }
15
-
16
8
  public extension View {
17
9
 
18
10
  @ViewBuilder
@@ -112,7 +112,9 @@ private struct ProgressInfoHorizontal: View {
112
112
 
113
113
  var body: some View {
114
114
  HStack(alignment: .top, spacing: 0) {
115
- ForEach(Array(steps.enumerated()), id: \.element.id) { index, item in
115
+ // Keyed by position: `ProgressInfoItem.id` is a fresh UUID per init, so it churns
116
+ // identity whenever the caller rebuilds `steps`.
117
+ ForEach(Array(steps.enumerated()), id: \.offset) { index, item in
116
118
  let isFirst = index == 0
117
119
  let isLast = index == steps.count - 1
118
120
 
@@ -207,7 +209,7 @@ private struct ProgressInfoVertical: View {
207
209
 
208
210
  var body: some View {
209
211
  VStack(alignment: .leading, spacing: 0) {
210
- ForEach(Array(steps.enumerated()), id: \.element.id) { index, item in
212
+ ForEach(Array(steps.enumerated()), id: \.offset) { index, item in
211
213
  let isLast = index == steps.count - 1
212
214
 
213
215
  HStack(alignment: .top, spacing: 0) {
@@ -36,8 +36,9 @@ public struct Radio: View {
36
36
  public var body: some View {
37
37
  SwiftUI.Button(action: onTap) {
38
38
  HStack(spacing: 0) {
39
+ // strokeBorder keeps the ring inside 20pt, like Compose `border`.
39
40
  RoundedRectangle(cornerRadius: Radius.M)
40
- .stroke(borderColor, lineWidth: borderWidth)
41
+ .strokeBorder(borderColor, lineWidth: borderWidth)
41
42
  .frame(width: 20, height: 20)
42
43
  .padding(.trailing, label.isEmpty ? 0 : Spacing.XS)
43
44
 
@@ -35,7 +35,7 @@ public struct Rating: View {
35
35
 
36
36
  public var body: some View {
37
37
  HStack(spacing: 2) {
38
- ForEach(0..<numOfStars, id: \.self) { index in
38
+ ForEach(0..<max(numOfStars, 0), id: \.self) { index in
39
39
  star(at: index)
40
40
  }
41
41
  }
@@ -59,7 +59,14 @@ public struct Rating: View {
59
59
  }
60
60
  }
61
61
 
62
+ // Kotlin's toInt() maps NaN to 0 and saturates; Swift's Int(_:) traps on both.
63
+ private var safeRating: Float {
64
+ guard rating.isFinite else { return 0 }
65
+ return min(max(rating, -Float(Int32.max)), Float(Int32.max))
66
+ }
67
+
62
68
  private func starIcon(at index: Int) -> String {
69
+ let rating = safeRating
63
70
  let intRating = Int(rating)
64
71
  if index < intRating {
65
72
  return "reaction_star_full"
@@ -71,6 +78,7 @@ public struct Rating: View {
71
78
  }
72
79
 
73
80
  private func starColor(at index: Int) -> Color {
81
+ let rating = safeRating
74
82
  let intRating = Int(rating)
75
83
  if index < intRating {
76
84
  return Colors.yellow03