@momo-kits/native-kits 0.162.44-debug → 0.162.45-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 (199) hide show
  1. package/CLAUDE.md +45 -3
  2. package/README.md +2 -2
  3. package/compose/build.gradle.kts +9 -1
  4. package/compose/build.gradle.kts.backup +8 -0
  5. package/compose/compose.podspec +1 -1
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +351 -111
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +20 -21
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +2 -1
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +4 -2
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +1 -1
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +1 -1
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -1
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +9 -6
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +11 -12
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -9
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +2 -2
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +10 -9
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +6 -6
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +55 -22
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -1
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +7 -9
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +1 -1
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +13 -6
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +13 -5
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +3 -2
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +14 -6
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +19 -21
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +2 -2
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +5 -2
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +3 -1
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +7 -15
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +2 -1
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +1 -2
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +314 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +10 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +4 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +6 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +197 -60
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +127 -83
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +54 -62
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +122 -24
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +34 -14
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +3 -3
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +1 -2
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +116 -26
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -2
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +13 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +13 -0
  58. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
  59. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +94 -0
  60. package/gradle/libs.versions.toml +1 -1
  61. package/gradle.properties +1 -1
  62. package/ios/Application/ApplicationEnvironment.swift +17 -0
  63. package/ios/Application/MaxApi.swift +30 -3
  64. package/ios/Avatar/Avatar.swift +4 -3
  65. package/ios/Badge/Badge.swift +6 -4
  66. package/ios/Badge/BadgeDot.swift +1 -1
  67. package/ios/Badge/BadgeRibbon.swift +0 -1
  68. package/ios/Button/Button.swift +15 -12
  69. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
  70. package/ios/Carousel/Carousel.swift +29 -12
  71. package/ios/Checkbox/Checkbox.swift +7 -3
  72. package/ios/Chip/Chip.swift +5 -4
  73. package/ios/Collapse/Collapse.swift +70 -19
  74. package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
  75. package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
  76. package/ios/DateTimePicker/DateTimePicker.swift +54 -20
  77. package/ios/DateTimePicker/WheelPicker.swift +103 -105
  78. package/ios/Divider/Divider.swift +37 -4
  79. package/ios/Extensions/Color++.swift +38 -9
  80. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  81. package/ios/Extensions/SkeletonUICompat.swift +11 -0
  82. package/ios/Icon/Icon.swift +21 -23
  83. package/ios/Icon/Icons.swift +1329 -0
  84. package/ios/IconButton/IconButton.swift +15 -7
  85. package/ios/Image/Image.swift +7 -23
  86. package/ios/Information/Information.swift +27 -9
  87. package/ios/Input/ErrorView.swift +2 -2
  88. package/ios/Input/Input.swift +160 -147
  89. package/ios/Input/InputPhoneNumber.swift +12 -9
  90. package/ios/Input/InputSearch.swift +107 -77
  91. package/ios/Input/InputTextArea.swift +128 -145
  92. package/ios/InputDropDown/InputDropDown.swift +10 -7
  93. package/ios/InputMoney/InputMoney.swift +33 -11
  94. package/ios/InputOTP/InputOTP.swift +22 -12
  95. package/ios/Layout/Card.swift +50 -0
  96. package/ios/Layout/GridContext.swift +187 -3
  97. package/ios/Layout/Item.swift +5 -4
  98. package/ios/Layout/ScreenLayout.swift +385 -0
  99. package/ios/Layout/Section.swift +4 -100
  100. package/ios/Loader/Loader.swift +5 -9
  101. package/ios/Lottie/LottieView.swift +14 -2
  102. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
  103. package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
  104. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
  105. package/ios/Navigation/BottomTab/DuoBarCompression.swift +104 -0
  106. package/ios/Navigation/BottomTab/DuoTabBar.swift +329 -0
  107. package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
  108. package/ios/Navigation/Component/Header.swift +545 -0
  109. package/ios/Navigation/Component/HeaderBackground.swift +154 -0
  110. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  111. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  112. package/ios/Navigation/Component/HeaderRight.swift +425 -0
  113. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  114. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
  115. package/ios/Navigation/Component/SideBar.swift +183 -0
  116. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
  117. package/ios/Navigation/HostBackGestureBridge.swift +78 -0
  118. package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
  119. package/ios/Navigation/Navigation.swift +204 -0
  120. package/ios/Navigation/NavigationContainer.swift +195 -0
  121. package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
  122. package/ios/Navigation/StackScreen.swift +930 -0
  123. package/ios/Navigation/StackTransition.swift +400 -0
  124. package/ios/Navigation/SwipeBackHaptic.swift +33 -0
  125. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  126. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  127. package/ios/Pagination/PaginationScroll.swift +2 -2
  128. package/ios/Popup/PopupDisplay.swift +72 -135
  129. package/ios/Popup/PopupInput.swift +10 -13
  130. package/ios/Popup/PopupLayout.swift +140 -0
  131. package/ios/Popup/PopupNotify.swift +59 -69
  132. package/ios/Popup/PopupPromotion.swift +31 -47
  133. package/ios/PopupView/FullscreenPopup.swift +13 -19
  134. package/ios/PopupView/PopupView.swift +1 -1
  135. package/ios/PopupView/Utils++.swift +0 -8
  136. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  137. package/ios/Radio/Radio.swift +2 -1
  138. package/ios/Rating/Rating.swift +9 -1
  139. package/ios/Resources/dot_loading.json +558 -0
  140. package/ios/Resources/lottie_circle_loader.json +1 -0
  141. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  142. package/ios/Skeleton/Skeleton.swift +22 -27
  143. package/ios/Slider/Slider.swift +16 -18
  144. package/ios/Stepper/Stepper.swift +6 -1
  145. package/ios/Steps/Steps.swift +8 -6
  146. package/ios/SuggestAction/SuggestAction.swift +5 -0
  147. package/ios/Swipeable/SwipeCell.swift +10 -6
  148. package/ios/Switch/Switch.swift +22 -20
  149. package/ios/TabView/TabView.swift +102 -21
  150. package/ios/Tag/Tag.swift +15 -4
  151. package/ios/Template/Logo/Logo.swift +2 -1
  152. package/ios/Template/TrustBanner/TrustBanner.swift +106 -54
  153. package/ios/Title/Title.swift +6 -17
  154. package/ios/Tooltip/Tooltip.swift +29 -14
  155. package/ios/Typography/FontScale.swift +3 -3
  156. package/ios/Typography/Text.swift +81 -19
  157. package/ios/Uploader/Uploader.swift +3 -1
  158. package/ios/native-kits.podspec +2 -1
  159. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  160. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  161. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
  162. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
  163. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
  164. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  165. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
  166. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
  167. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
  168. package/package.json +1 -1
  169. package/scripts/gen-ios-icons.mjs +36 -0
  170. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  171. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  172. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  173. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  174. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  175. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  176. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  177. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  178. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  179. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  180. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  181. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  182. package/ios/Application/Components.swift +0 -270
  183. package/ios/Application/ComposeApi.swift +0 -22
  184. package/ios/Application/HeaderRight.swift +0 -306
  185. package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
  186. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  187. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  188. package/ios/Application/Navigation/NavigationContainer.swift +0 -213
  189. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  190. package/ios/Application/Navigation/NavigatorManager.swift +0 -22
  191. package/ios/Application/Navigation/component/Header.swift +0 -178
  192. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
  193. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  194. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  195. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  196. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
  197. package/ios/Application/Screen.swift +0 -250
  198. package/ios/Application/StackScreen.swift +0 -492
  199. /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
@@ -12,14 +12,14 @@ import SDWebImageSwiftUI
12
12
  let jsonUrl = "https://static.momocdn.net/app/json/component-kits/design_system.json"
13
13
 
14
14
  public struct TrustBannerData: Decodable {
15
- let content: [String: String]
16
- let subContent: [String: String]
17
- let pciImage: String
18
- let sslImage: String
19
- let urlConfig: String
20
- let icons: [String]
21
- let momoImage: String
22
-
15
+ public let content: [String: String]
16
+ public let subContent: [String: String]
17
+ public let pciImage: String?
18
+ public let momoImage: String?
19
+ public let sslImage: String?
20
+ public let icons: [String]?
21
+ public let urlConfig: String
22
+
23
23
  enum CodingKeys: String, CodingKey {
24
24
  case content
25
25
  case subContent
@@ -29,6 +29,38 @@ public struct TrustBannerData: Decodable {
29
29
  case momoImage
30
30
  case urlConfig = "url_config"
31
31
  }
32
+
33
+ public init(
34
+ content: [String: String] = ["vi": "", "en": ""],
35
+ subContent: [String: String] = ["vi": "", "en": ""],
36
+ pciImage: String? = nil,
37
+ momoImage: String? = nil,
38
+ sslImage: String? = nil,
39
+ icons: [String]? = nil,
40
+ urlConfig: String = ""
41
+ ) {
42
+ self.content = content
43
+ self.subContent = subContent
44
+ self.pciImage = pciImage
45
+ self.momoImage = momoImage
46
+ self.sslImage = sslImage
47
+ self.icons = icons
48
+ self.urlConfig = urlConfig
49
+ }
50
+
51
+ // Missing keys fall back to the init defaults, like the Compose data class.
52
+ public init(from decoder: Decoder) throws {
53
+ let container = try decoder.container(keyedBy: CodingKeys.self)
54
+ self.init(
55
+ content: try container.decodeIfPresent([String: String].self, forKey: .content) ?? ["vi": "", "en": ""],
56
+ subContent: try container.decodeIfPresent([String: String].self, forKey: .subContent) ?? ["vi": "", "en": ""],
57
+ pciImage: try container.decodeIfPresent(String.self, forKey: .pciImage),
58
+ momoImage: try container.decodeIfPresent(String.self, forKey: .momoImage),
59
+ sslImage: try container.decodeIfPresent(String.self, forKey: .sslImage),
60
+ icons: try container.decodeIfPresent([String].self, forKey: .icons),
61
+ urlConfig: try container.decodeIfPresent(String.self, forKey: .urlConfig) ?? ""
62
+ )
63
+ }
32
64
  }
33
65
 
34
66
  let defaultBanner = TrustBannerData(
@@ -38,16 +70,16 @@ let defaultBanner = TrustBannerData(
38
70
  "vi": "Tìm hiểu thêm",
39
71
  "en": "Learn more"
40
72
  ],
41
- pciImage: "https://static.momocdn.net/app/img/kits/trustBanner/pci.png",
73
+ pciImage: "https://static.momocdn.net/app/img/kits/trustBanner/pci_dss.png",
74
+ momoImage: "https://static.momocdn.net/app/img/kits/trustBanner/ic_secu.png",
42
75
  sslImage: "https://static.momocdn.net/app/img/kits/trustBanner/ssl.png",
43
- urlConfig: "login_and_security",
44
76
  icons: [
45
77
  "https://static.momocdn.net/app/img/kits/trustBanner/ic_viettinbank.png",
46
78
  "https://static.momocdn.net/app/img/kits/trustBanner/ic_agribank.png",
47
79
  "https://static.momocdn.net/app/img/kits/trustBanner/ic_vietcombank.png",
48
80
  "https://static.momocdn.net/app/img/kits/trustBanner/ic_bidv.png"
49
81
  ],
50
- momoImage: "https://static.momocdn.net/app/img/kits/trustBanner/ic_secu.png"
82
+ urlConfig: "login_and_security"
51
83
  )
52
84
 
53
85
  public struct TrustBanner: View {
@@ -55,66 +87,86 @@ public struct TrustBanner: View {
55
87
  @Environment(\.localize) private var localize
56
88
 
57
89
  var onPress: ((String)->Void)?
90
+ private let serviceName: String
91
+ private let screenName: String
92
+ private let trackEvent: ((String, [String: String]) -> Void)?
58
93
 
59
- public init(onPress: ((String)->Void)?) {
94
+ public init(
95
+ onPress: ((String)->Void)?,
96
+ serviceName: String = "",
97
+ screenName: String = "",
98
+ trackEvent: ((String, [String: String]) -> Void)? = nil
99
+ ) {
60
100
  self.onPress = onPress
101
+ self.serviceName = serviceName
102
+ self.screenName = screenName
103
+ self.trackEvent = trackEvent
104
+ }
105
+
106
+ private var banner: TrustBannerData {
107
+ applicationEnvironment.config?.trustBanner ?? defaultBanner
61
108
  }
62
109
 
63
110
  func onPressSecurity() {
64
- if (onPress != nil) {
65
- onPress!(applicationEnvironment.config?.trustBanner?.urlConfig ?? defaultBanner.urlConfig)
66
- }
111
+ trackEvent?("service_component_clicked", [
112
+ "service_name": serviceName,
113
+ "screen_name": screenName,
114
+ "component_name": "logo_trust",
115
+ "url_config": banner.urlConfig
116
+ ])
117
+ onPress?(banner.urlConfig)
67
118
  }
68
-
69
-
119
+
70
120
  public var body: some View {
71
- HStack(alignment: .center, spacing: 8) {
72
- WebImage(url: URL(string: applicationEnvironment.config?.trustBanner?.momoImage ?? defaultBanner.momoImage))
73
- .resizable()
74
- .scaledToFit()
75
- .frame(width: 64, height: 64)
76
- VStack(alignment: .leading){
77
- Text(applicationEnvironment.config?.trustBanner?.content[localize.currentLanguage]
78
- ?? localize.translateData(defaultBanner.content))
79
- .foregroundColor(Color(hex: "484848"))
80
- .lineLimit(2)
81
- .font(.system(size: 13, weight: Font.Weight.regular))
82
- .ignoreBoldTextSetting()
83
- .lineSpacing(3)
84
- .padding(.bottom, 8)
85
- HStack {
121
+ HStack(alignment: .top, spacing: 10) {
122
+ if let momoImage = banner.momoImage {
123
+ WebImage(url: URL(string: momoImage))
124
+ .resizable()
125
+ .scaledToFit()
126
+ .frame(width: 64, height: 64)
127
+ }
128
+ VStack(alignment: .leading, spacing: 0) {
129
+ MomoText(
130
+ localize.translateData(banner.content),
131
+ typography: .descriptionDefaultRegular,
132
+ color: Color(hex: "484848"),
133
+ maxLines: 2
134
+ )
135
+ .padding(.bottom, 8)
136
+ HStack(alignment: .top, spacing: 0) {
86
137
  HStack(spacing: 0) {
87
- Text(applicationEnvironment.config?.trustBanner?.subContent[localize.currentLanguage]
88
- ?? localize.translateData(defaultBanner.subContent))
89
- .foregroundColor(Colors.pink03)
90
- .font(.system(size: 13, weight: .bold))
91
- .ignoreBoldTextSetting()
92
- .padding(.trailing, 4)
93
-
138
+ MomoText(
139
+ localize.translateData(banner.subContent),
140
+ typography: .actionXsBold,
141
+ color: Colors.pink03,
142
+ maxLines: 2
143
+ )
94
144
  Icon(source: "arrow_chevron_right_small", color: Colors.pink03)
95
- }.onTapGesture {
96
- onPressSecurity()
97
145
  }
98
-
99
- Spacer()
100
- HStack{
101
- WebImage(url: URL(string: applicationEnvironment.config?.trustBanner?.pciImage ?? defaultBanner.pciImage))
102
- .resizable()
103
- .scaledToFit()
104
- .frame(width: 24, height: 20)
105
- WebImage(url: URL(string: applicationEnvironment.config?.trustBanner?.sslImage ?? defaultBanner.sslImage))
106
- .resizable()
107
- .scaledToFit()
108
- .frame(width: 52, height: 20)
146
+
147
+ Spacer(minLength: 0)
148
+ HStack(spacing: 0) {
149
+ if let pciImage = banner.pciImage {
150
+ WebImage(url: URL(string: pciImage))
151
+ .resizable()
152
+ .scaledToFit()
153
+ .frame(width: 27, height: 20)
154
+ .padding(.trailing, 4)
155
+ }
156
+ if let sslImage = banner.sslImage {
157
+ WebImage(url: URL(string: sslImage))
158
+ .resizable()
159
+ .scaledToFit()
160
+ .frame(width: 52, height: 20)
161
+ }
109
162
  }
110
163
  }
111
-
112
164
  }
113
-
114
165
  }
115
166
  .padding(.all, 12)
116
167
  .background(Color(hex: "F2F8FF"))
117
168
  .cornerRadius(12)
169
+ .contentShape(Rectangle())
118
170
  .onTapGesture {
119
171
  onPressSecurity()
120
172
  }
@@ -28,7 +28,6 @@ public enum TitleButtonSize {
28
28
 
29
29
  public struct Title: View {
30
30
  @Environment(\.appTheme) private var theme
31
- @Environment(\.momoFontScale) private var fontScale
32
31
 
33
32
  private let title: String
34
33
  private let type: TitleType
@@ -116,15 +115,9 @@ public struct Title: View {
116
115
  }
117
116
  }
118
117
 
119
- private var titleFontWeight: Font.Weight {
120
- switch type {
121
- case .card: return .regular
122
- case .section: return .bold
123
- }
124
- }
125
-
126
118
  private var titleMaxLines: Int {
127
- (showTrailingAction || badgeLabel != nil) ? 1 : 2
119
+ if textOnly { return Int.max }
120
+ return (showTrailingAction || badgeLabel != nil) ? 1 : 2
128
121
  }
129
122
 
130
123
  private var iconVerticalAlignment: VerticalAlignment {
@@ -151,14 +144,10 @@ public struct Title: View {
151
144
  }
152
145
  }
153
146
 
147
+ // Every Title variant is bold in the theme font; `headerDefaultBold` only supplies the weight.
154
148
  private var titleText: some View {
155
- SwiftUI.Text(title)
156
- .font(.system(size: fontScale(titleFontSize), weight: titleFontWeight))
157
- .ignoreBoldTextSetting()
158
- .foregroundColor(theme.colors.text.default)
159
- .lineSpacing(fontScale(titleLineHeight) - fontScale(titleFontSize))
160
- .lineLimit(titleMaxLines)
161
- .truncationMode(.tail)
149
+ MomoText(title, typography: .headerDefaultBold, maxLines: titleMaxLines)
150
+ .fontSize(titleFontSize, lineHeight: titleLineHeight)
162
151
  }
163
152
 
164
153
  private var fullLayout: some View {
@@ -237,7 +226,7 @@ public struct Title: View {
237
226
  }
238
227
  }
239
228
  .frame(maxWidth: .infinity, alignment: .leading)
240
- .padding(8)
229
+ .padding(.top, type == .section ? Spacing.S : 0)
241
230
  }
242
231
  }
243
232
 
@@ -3,7 +3,7 @@ import SwiftUI
3
3
  // MARK: - Types
4
4
 
5
5
  /// Tooltip placement relative to the anchor element.
6
- public enum TooltipPlacement {
6
+ public enum TooltipPlacement: Sendable {
7
7
  case top
8
8
  case bottom
9
9
  case left
@@ -78,24 +78,37 @@ struct TooltipArrowShape: Shape {
78
78
  func path(in rect: CGRect) -> Path {
79
79
  let w = rect.width
80
80
  let h = rect.height
81
- var path = Path()
81
+ let points: [CGPoint]
82
82
  switch placement {
83
83
  case .top:
84
- path.move(to: CGPoint(x: 0, y: 0))
85
- path.addLine(to: CGPoint(x: w, y: 0))
86
- path.addLine(to: CGPoint(x: w / 2, y: h))
84
+ points = [CGPoint(x: 0, y: 0), CGPoint(x: w, y: 0), CGPoint(x: w / 2, y: h)]
87
85
  case .bottom:
88
- path.move(to: CGPoint(x: 0, y: h))
89
- path.addLine(to: CGPoint(x: w, y: h))
90
- path.addLine(to: CGPoint(x: w / 2, y: 0))
86
+ points = [CGPoint(x: 0, y: h), CGPoint(x: w, y: h), CGPoint(x: w / 2, y: 0)]
91
87
  case .left:
92
- path.move(to: CGPoint(x: 0, y: 0))
93
- path.addLine(to: CGPoint(x: 0, y: h))
94
- path.addLine(to: CGPoint(x: w, y: h / 2))
88
+ points = [CGPoint(x: 0, y: 0), CGPoint(x: 0, y: h), CGPoint(x: w, y: h / 2)]
95
89
  case .right:
96
- path.move(to: CGPoint(x: w, y: 0))
97
- path.addLine(to: CGPoint(x: w, y: h))
98
- path.addLine(to: CGPoint(x: 0, y: h / 2))
90
+ points = [CGPoint(x: w, y: 0), CGPoint(x: w, y: h), CGPoint(x: 0, y: h / 2)]
91
+ }
92
+ // rounded like Compose's cornerPathEffect: each corner is cut `radius` along both edges
93
+ let radius = TooltipConst.arrowCornerRadius
94
+ func toward(_ from: CGPoint, _ to: CGPoint) -> CGPoint {
95
+ let dx = to.x - from.x
96
+ let dy = to.y - from.y
97
+ let length = (dx * dx + dy * dy).squareRoot()
98
+ guard length > 0 else { return from }
99
+ let distance = min(radius, length / 2)
100
+ return CGPoint(x: from.x + dx / length * distance, y: from.y + dy / length * distance)
101
+ }
102
+ var path = Path()
103
+ for index in 0..<points.count {
104
+ let corner = points[index]
105
+ let start = toward(corner, points[(index + points.count - 1) % points.count])
106
+ if index == 0 {
107
+ path.move(to: start)
108
+ } else {
109
+ path.addLine(to: start)
110
+ }
111
+ path.addQuadCurve(to: toward(corner, points[(index + 1) % points.count]), control: corner)
99
112
  }
100
113
  path.closeSubpath()
101
114
  return path
@@ -209,6 +222,8 @@ private struct TooltipPositionModifier: ViewModifier {
209
222
  }
210
223
  )
211
224
  .offset(x: originX, y: originY)
225
+ // the first pass has no size yet and would flash at the wrong spot
226
+ .opacity(popupSize == .zero ? 0 : 1)
212
227
  }
213
228
 
214
229
  private var originX: CGFloat {
@@ -30,9 +30,9 @@ public struct FontScale: Equatable {
30
30
  public func callAsFunction(_ size: CGFloat) -> CGFloat {
31
31
  guard useOSScaleRate else { return size * userScaleRate }
32
32
 
33
- // Device width is read live rather than snapshotted, matching Compose's
34
- // getScreenDimensions() read inside scaleSize(), so a rotation or split-view resize counts.
35
- let deviceScale = UIScreen.main.bounds.width / Self.defaultScreenSize
33
+ // Short side, not width: landscape must not inflate every font (and so every popup's height).
34
+ let bounds = UIScreen.main.bounds.size
35
+ let deviceScale = min(bounds.width, bounds.height) / Self.defaultScreenSize
36
36
 
37
37
  var deviceScaled = size
38
38
  var osScaled = size
@@ -99,28 +99,63 @@ public extension TypographyStyle {
99
99
  }
100
100
  }
101
101
 
102
- private func momoFontName(family: String, weight: Font.Weight) -> String? {
103
- // Ngoai whitelist thi tra nil de momoFont() roi ve .system(...), dung nhu truoc DS-791.
104
- guard KitDesignSystem.shared.isWhiteList else { return nil }
102
+ private enum MomoFontFace {
103
+ case system
104
+ case named(String)
105
+ }
106
+
107
+ /// Port of Compose `getFontFamily`; SF Pro Text is the iOS system font, so the legacy family is `.system`.
108
+ private func momoFontFace(family: String, weight: Font.Weight) -> MomoFontFace? {
109
+ let isNewFamily = family == "MomoTrustSans" || family == headerFontFamily
110
+ // Outside the whitelist the new DS families drop to the legacy font, as before DS-791.
111
+ if family == "SFProText" || (isNewFamily && !KitDesignSystem.shared.isWhiteList) { return .system }
112
+ // single-file families (Compose maps every weight to the same .otf)
113
+ if family == headerFontFamily || family == "MoMoSignature" { return .named(family) }
105
114
  guard family == "MomoTrustSans" else { return nil }
106
115
  switch weight {
107
- case .ultraLight, .thin: return "MomoTrustSans-ExtraLight"
108
- case .light: return "MomoTrustSans-Light"
109
- case .regular: return "MomoTrustSans-Regular"
110
- case .medium: return "MomoTrustSans-Medium"
111
- case .semibold: return "MomoTrustSans-SemiBold"
112
- case .bold: return "MomoTrustSans-Bold"
113
- case .heavy, .black: return "MomoTrustSans-ExtraBold"
114
- default: return "MomoTrustSans-Regular"
116
+ case .ultraLight, .thin: return .named("MomoTrustSans-ExtraLight")
117
+ case .light: return .named("MomoTrustSans-Light")
118
+ case .regular: return .named("MomoTrustSans-Regular")
119
+ case .medium: return .named("MomoTrustSans-Medium")
120
+ case .semibold: return .named("MomoTrustSans-SemiBold")
121
+ case .bold: return .named("MomoTrustSans-Bold")
122
+ case .heavy, .black: return .named("MomoTrustSans-ExtraBold")
123
+ default: return .named("MomoTrustSans-Regular")
115
124
  }
116
125
  }
117
126
 
118
- private func momoFont(size: CGFloat, weight: Font.Weight, family: String) -> Font {
119
- guard let name = momoFontName(family: family, weight: weight),
120
- UIFont(name: name, size: size) != nil else {
127
+ /// Header title family (Compose `HEADER_FONT_FAMILY`), whitelist only like the default font.
128
+ let headerFontFamily = "MoMoTrustDisplay"
129
+
130
+ private func momoFont(size: CGFloat, weight: Font.Weight, family: String, fallbackFamily: String? = nil) -> Font {
131
+ switch momoFontFace(family: family, weight: weight) {
132
+ case .system:
133
+ return .system(size: size, weight: weight)
134
+ case .named(let name) where UIFont(name: name, size: size) != nil:
135
+ return .custom(name, fixedSize: size)
136
+ default:
137
+ // Compose falls back to the default font file when a family is missing
138
+ if let fallbackFamily, fallbackFamily != family {
139
+ return momoFont(size: size, weight: weight, family: fallbackFamily)
140
+ }
121
141
  return .system(size: size, weight: weight)
122
142
  }
123
- return .custom(name, fixedSize: size)
143
+ }
144
+
145
+ // lineHeight is identical across weights of a family, so the regular face is enough.
146
+ private func momoLineHeight(size: CGFloat, family: String, fallbackFamily: String? = nil) -> CGFloat {
147
+ switch momoFontFace(family: family, weight: .regular) {
148
+ case .system:
149
+ break
150
+ case .named(let name):
151
+ if let font = UIFont(name: name, size: size) { return font.lineHeight }
152
+ fallthrough
153
+ case nil:
154
+ if let fallbackFamily, fallbackFamily != family {
155
+ return momoLineHeight(size: size, family: fallbackFamily)
156
+ }
157
+ }
158
+ return UIFont.systemFont(ofSize: size).lineHeight
124
159
  }
125
160
 
126
161
  public struct MomoText: View {
@@ -139,7 +174,12 @@ public struct MomoText: View {
139
174
  private let letterSpacing: CGFloat?
140
175
  private let softWrap: Bool
141
176
  private let accessibilityId: String?
177
+ private let fontFamily: String?
142
178
  private var highlightText: [String] = []
179
+ private var sizeOverride: (fontSize: CGFloat, lineHeight: CGFloat)?
180
+
181
+ private var fontSize: CGFloat { sizeOverride?.fontSize ?? typography.fontSize }
182
+ private var lineHeight: CGFloat { sizeOverride?.lineHeight ?? typography.lineHeight }
143
183
 
144
184
  public init(
145
185
  _ content: String,
@@ -153,7 +193,8 @@ public struct MomoText: View {
153
193
  underline: Bool = false,
154
194
  letterSpacing: CGFloat? = nil,
155
195
  softWrap: Bool = true,
156
- accessibilityId: String? = nil
196
+ accessibilityId: String? = nil,
197
+ fontFamily: String? = nil
157
198
  ) {
158
199
  self.content = content
159
200
  self.typography = typography
@@ -175,6 +216,7 @@ public struct MomoText: View {
175
216
  self.letterSpacing = letterSpacing
176
217
  self.softWrap = softWrap
177
218
  self.accessibilityId = accessibilityId
219
+ self.fontFamily = fontFamily
178
220
  }
179
221
 
180
222
  init(_ content: String, typography: TypographyStyle, highlightText: [String]) {
@@ -182,11 +224,27 @@ public struct MomoText: View {
182
224
  self.highlightText = highlightText
183
225
  }
184
226
 
227
+ /// Compose's `style.copy(fontSize, lineHeight)`; the weight still comes from `typography`.
228
+ func fontSize(_ fontSize: CGFloat, lineHeight: CGFloat) -> MomoText {
229
+ var text = self
230
+ text.sizeOverride = (fontSize, lineHeight)
231
+ return text
232
+ }
233
+
234
+ private var font: Font {
235
+ momoFont(
236
+ size: fontScale(fontSize),
237
+ weight: typography.fontWeight,
238
+ family: fontFamily ?? theme.font,
239
+ fallbackFamily: theme.font
240
+ )
241
+ }
242
+
185
243
  // Whole-word matches of `highlightText` render semibold; the longest keyword wins (RN parity).
186
244
  private var baseText: SwiftUI.Text {
187
245
  let keywords = highlightText.filter { !$0.isEmpty }.sorted { $0.count > $1.count }
188
246
  guard !keywords.isEmpty else { return SwiftUI.Text(content) }
189
- let highlightFont = momoFont(size: fontScale(typography.fontSize), weight: TypographyStyle.headerXsSemibold.fontWeight, family: theme.font)
247
+ let highlightFont = momoFont(size: fontScale(fontSize), weight: TypographyStyle.headerXsSemibold.fontWeight, family: fontFamily ?? theme.font, fallbackFamily: theme.font)
190
248
  func isWordChar(_ character: Character?) -> Bool {
191
249
  guard let character else { return false }
192
250
  return character.isLetter || character.isNumber || character == "_"
@@ -222,8 +280,12 @@ public struct MomoText: View {
222
280
  // *Strikethrough styles) or the explicit `strikethrough` flag.
223
281
  let isStrikethrough = strikethrough || typography.isStrikethrough
224
282
 
283
+ // lineSpacing is added on top of the font's natural line height, not on top of fontSize.
284
+ let lineSpacing = max(0, fontScale(lineHeight)
285
+ - momoLineHeight(size: fontScale(fontSize), family: fontFamily ?? theme.font, fallbackFamily: theme.font))
286
+
225
287
  var text = baseText
226
- .font(momoFont(size: fontScale(typography.fontSize), weight: typography.fontWeight, family: theme.font))
288
+ .font(font)
227
289
  .foregroundColor(textColor)
228
290
  .strikethrough(isStrikethrough, color: textColor)
229
291
  .underline(underline, color: textColor)
@@ -234,7 +296,7 @@ public struct MomoText: View {
234
296
 
235
297
  return text
236
298
  .multilineTextAlignment(textAlign)
237
- .lineSpacing(fontScale(typography.lineHeight) - fontScale(typography.fontSize))
299
+ .lineSpacing(lineSpacing)
238
300
  .modifier(LineLimitModifier(range: lineLimitRange))
239
301
  .truncationMode(truncationMode)
240
302
  .fixedSize(horizontal: !softWrap, vertical: false)
@@ -80,7 +80,9 @@ public struct Uploader: View {
80
80
 
81
81
  ScrollView(.horizontal, showsIndicators: false) {
82
82
  HStack(alignment: .top, spacing: Spacing.S) {
83
- ForEach(Array(images.enumerated()), id: \.element.id) { index, image in
83
+ // Positional like Compose: `UploadImage.id` is a fresh UUID per init, so hosts that
84
+ // rebuild the array each render would otherwise re-create (and reload) every thumbnail.
85
+ ForEach(Array(images.enumerated()), id: \.offset) { index, image in
84
86
  UploaderImageItem(
85
87
  image: image,
86
88
  width: width,
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKits'
3
- s.version = '0.167.1-beta.4'
3
+ s.version = '0.167.1-duo.15'
4
4
  s.summary = 'MoMoUIKits for iOS'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -37,6 +37,7 @@ Pod::Spec.new do |s|
37
37
  "Layout/**/*.{swift,m,h}",
38
38
  "Loader/**/*.{swift,m,h}",
39
39
  "Lottie/**/*.{swift,m,h}",
40
+ "Navigation/**/*.{swift,m,h}",
40
41
  "OTPKeyboard/**/*.{swift,m,h}",
41
42
  "Pagination/**/*.{swift,m,h}",
42
43
  "Popup/**/*.{swift,m,h}",
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKitsDemo'
3
- s.version = '0.167.1-beta.4'
3
+ s.version = '0.167.1-duo.15'
4
4
  s.summary = 'Demo browser for MoMoUIKits SwiftUI components'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -25,7 +25,7 @@ struct ContentView: View {
25
25
 
26
26
  struct ScreenDemo: View {
27
27
  var body: some View {
28
- Screen (
28
+ StackScreen(
29
29
  // backgroundColor: Colors.blue01,
30
30
  headerTransparent: true,
31
31
  isBack: true,
@@ -0,0 +1,80 @@
1
+ //
2
+ // DuoTabDemo.swift
3
+ // Example
4
+ //
5
+ // Mirrors RN `app/screens/IPhoneDuo/DuoTabUsage`: glass rail in the Duo side bar, floating
6
+ // glass bar when unfolded portrait. `useShortcut` gives the tallest side bar header, for the
7
+ // overlap review; `compressTabBar` folds the tab bar instead of the toolbar when the side bar
8
+ // is short (HIG "Tab bar compressed").
9
+ //
10
+
11
+ import MoMoUIKits
12
+ import SwiftUI
13
+
14
+ private let swatches = [Colors.pink03, Colors.orange03, Colors.green03]
15
+ private let history = (1...18).map { "Giao dịch #\($0)" }
16
+
17
+ struct DuoTabDemo: View {
18
+ let useShortcut: Bool
19
+ var compressTabBar = false
20
+
21
+ var body: some View {
22
+ if #available(iOS 16.0, *) {
23
+ DuoTabDemoContent(useShortcut: useShortcut, compressTabBar: compressTabBar)
24
+ } else {
25
+ Text("Requires iOS 16+").padding()
26
+ }
27
+ }
28
+ }
29
+
30
+ @available(iOS 16.0, *)
31
+ private struct DuoTabDemoContent: View {
32
+ let useShortcut: Bool
33
+ let compressTabBar: Bool
34
+
35
+ var body: some View {
36
+ BottomTab(
37
+ items: [
38
+ tab("DuoMoMo", label: "MoMo", icon: "basic_home", title: "MoMo"),
39
+ tab("DuoPromotion", label: "Ưu đãi", icon: "shopping_coupon", title: "Ưu đãi", badge: ""),
40
+ tab("DuoHistory", label: "Lịch sử GD", icon: "time_clock_reset", title: "Lịch sử giao dịch", badge: "3"),
41
+ tab("DuoMe", label: "Tôi", icon: "basic_person", title: "Tôi"),
42
+ ],
43
+ floatingButton: BottomTabFloatingButton(icon: "navigation_full_screen", label: "Quét mọi QR", onPress: {}),
44
+ compressionBehavior: compressTabBar ? .tabBar : .toolbar
45
+ )
46
+ }
47
+
48
+ private func tab(_ name: String, label: String, icon: String, title: String, badge: String? = nil) -> BottomTabItem {
49
+ BottomTabItem(
50
+ name: name,
51
+ label: label,
52
+ icon: icon,
53
+ badgeLabel: badge,
54
+ options: NavigationOptions(title: title, headerRightData: HeaderRightData(useShortcut: useShortcut)),
55
+ screen: { DuoTabList() }
56
+ )
57
+ }
58
+ }
59
+
60
+ private struct DuoTabList: View {
61
+ var body: some View {
62
+ VStack(spacing: 0) {
63
+ ForEach(history.indices, id: \.self) { index in
64
+ if index > 0 { MomoDivider() }
65
+ HStack(spacing: Spacing.M) {
66
+ RoundedRectangle(cornerRadius: Radius.S)
67
+ .fill(swatches[index % swatches.count])
68
+ .frame(width: Spacing.XL, height: Spacing.XL)
69
+ MomoText(history[index], typography: .bodyDefaultRegular)
70
+ Spacer()
71
+ }
72
+ .padding(.vertical, Spacing.M)
73
+ }
74
+ }
75
+ .padding(.horizontal, Spacing.M)
76
+ .background(Colors.black01)
77
+ .clipShape(RoundedRectangle(cornerRadius: Radius.M))
78
+ .padding(Spacing.M)
79
+ }
80
+ }
@@ -51,6 +51,7 @@ public let ScreenUsages: [ScreenUsage] = [
51
51
  ScreenUsage(title: "InputTextArea", destination: AnyView(InputTextAreaDemo()), icon: getIcon(icon: "FormInput"), hostsOwnChrome: true),
52
52
  ScreenUsage(title: "InputSearch", destination: AnyView(InputSearchDemo()), icon: getIcon(icon: "FormInput"), hostsOwnChrome: true),
53
53
  ScreenUsage(title: "PopupNotify", destination: AnyView(PopupNotifyDemo()), icon: getIcon(icon: "Popup"), hostsOwnChrome: true),
54
+ ScreenUsage(title: "PopupDisplay", destination: AnyView(PopupDisplayDemo()), icon: getIcon(icon: "Popup"), hostsOwnChrome: true),
54
55
  ScreenUsage(title: "PopupPromotion", destination: AnyView(PopupPromotionDemo()), icon: getIcon(icon: "Popup"), hostsOwnChrome: true),
55
56
  ScreenUsage(title: "SnackBar", destination: AnyView(SnackBarDemo()), icon: getIcon(icon: "Popup"), hostsOwnChrome: true),
56
57
  ScreenUsage(title: "Switch", destination: AnyView(SwitchDemo()), icon: getIcon(icon: "Toggle"), hostsOwnChrome: true),
@@ -84,6 +85,7 @@ public let ScreenUsages: [ScreenUsage] = [
84
85
  ScreenUsage(title: "DateTimePicker", destination: AnyView(DateTimePickerDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
85
86
  ScreenUsage(title: "Navigation", destination: AnyView(NavigationDemo()), icon: getIcon(icon: "Layout")),
86
87
  ScreenUsage(title: "Bottom Tab", destination: AnyView(BottomTabDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
88
+ ScreenUsage(title: "iPhone Duo", destination: AnyView(IPhoneDuoDemo()), icon: getIcon(icon: "Layout")),
87
89
  ScreenUsage(title: "Animated Header", destination: AnyView(AnimatedHeaderDemo()), icon: getIcon(icon: "Layout")),
88
90
  ScreenUsage(title: "Theme", destination: AnyView(ThemeDemo()), icon: getIcon(icon: "Typography")),
89
91
  ScreenUsage(title: "Localize", destination: AnyView(LocalizeDemo()), icon: getIcon(icon: "Typography")),