@momo-kits/native-kits 0.162.44 → 0.162.45
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.
- package/CLAUDE.md +45 -3
- package/README.md +2 -2
- package/ios/Application/ApplicationEnvironment.swift +17 -0
- package/ios/Application/MaxApi.swift +30 -3
- package/ios/Avatar/Avatar.swift +4 -3
- package/ios/Badge/Badge.swift +6 -4
- package/ios/Badge/BadgeDot.swift +1 -1
- package/ios/Badge/BadgeRibbon.swift +0 -1
- package/ios/Button/Button.swift +15 -12
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
- package/ios/Carousel/Carousel.swift +29 -12
- package/ios/Checkbox/Checkbox.swift +7 -3
- package/ios/Chip/Chip.swift +5 -4
- package/ios/Collapse/Collapse.swift +70 -19
- package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
- package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
- package/ios/DateTimePicker/DateTimePicker.swift +54 -20
- package/ios/DateTimePicker/WheelPicker.swift +103 -105
- package/ios/Divider/Divider.swift +37 -4
- package/ios/Extensions/Color++.swift +38 -9
- package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
- package/ios/Extensions/SkeletonUICompat.swift +11 -0
- package/ios/Icon/Icon.swift +21 -23
- package/ios/Icon/Icons.swift +1329 -0
- package/ios/IconButton/IconButton.swift +15 -7
- package/ios/Image/Image.swift +7 -23
- package/ios/Information/Information.swift +27 -9
- package/ios/Input/ErrorView.swift +2 -2
- package/ios/Input/Input.swift +160 -147
- package/ios/Input/InputPhoneNumber.swift +12 -9
- package/ios/Input/InputSearch.swift +107 -77
- package/ios/Input/InputTextArea.swift +128 -145
- package/ios/InputDropDown/InputDropDown.swift +10 -7
- package/ios/InputMoney/InputMoney.swift +33 -11
- package/ios/InputOTP/InputOTP.swift +22 -12
- package/ios/Layout/Card.swift +50 -0
- package/ios/Layout/GridContext.swift +187 -3
- package/ios/Layout/Item.swift +5 -4
- package/ios/Layout/ScreenLayout.swift +385 -0
- package/ios/Layout/Section.swift +4 -100
- package/ios/Loader/Loader.swift +5 -9
- package/ios/Lottie/LottieView.swift +14 -2
- package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
- package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
- package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
- package/ios/Navigation/BottomTab/DuoBarCompression.swift +104 -0
- package/ios/Navigation/BottomTab/DuoTabBar.swift +329 -0
- package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
- package/ios/Navigation/Component/Header.swift +545 -0
- package/ios/Navigation/Component/HeaderBackground.swift +154 -0
- package/ios/Navigation/Component/HeaderJourney.swift +73 -0
- package/ios/Navigation/Component/HeaderLocation.swift +48 -0
- package/ios/Navigation/Component/HeaderRight.swift +425 -0
- package/ios/Navigation/Component/HeaderTitle.swift +36 -0
- package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
- package/ios/Navigation/Component/SideBar.swift +183 -0
- package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
- package/ios/Navigation/HostBackGestureBridge.swift +78 -0
- package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
- package/ios/Navigation/Navigation.swift +204 -0
- package/ios/Navigation/NavigationContainer.swift +195 -0
- package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
- package/ios/Navigation/StackScreen.swift +930 -0
- package/ios/Navigation/StackTransition.swift +400 -0
- package/ios/Navigation/SwipeBackHaptic.swift +33 -0
- package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
- package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
- package/ios/Pagination/PaginationScroll.swift +2 -2
- package/ios/Popup/PopupDisplay.swift +72 -135
- package/ios/Popup/PopupInput.swift +10 -13
- package/ios/Popup/PopupLayout.swift +140 -0
- package/ios/Popup/PopupNotify.swift +59 -69
- package/ios/Popup/PopupPromotion.swift +31 -47
- package/ios/PopupView/FullscreenPopup.swift +13 -19
- package/ios/PopupView/PopupView.swift +1 -1
- package/ios/PopupView/Utils++.swift +0 -8
- package/ios/ProgressInfo/ProgressInfo.swift +4 -2
- package/ios/Radio/Radio.swift +2 -1
- package/ios/Rating/Rating.swift +9 -1
- package/ios/Resources/dot_loading.json +558 -0
- package/ios/Resources/lottie_circle_loader.json +1 -0
- package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
- package/ios/Skeleton/Skeleton.swift +22 -27
- package/ios/Slider/Slider.swift +16 -18
- package/ios/Stepper/Stepper.swift +6 -1
- package/ios/Steps/Steps.swift +8 -6
- package/ios/SuggestAction/SuggestAction.swift +5 -0
- package/ios/Swipeable/SwipeCell.swift +10 -6
- package/ios/Switch/Switch.swift +22 -20
- package/ios/TabView/TabView.swift +102 -21
- package/ios/Tag/Tag.swift +15 -4
- package/ios/Template/Logo/Logo.swift +2 -1
- package/ios/Template/TrustBanner/TrustBanner.swift +106 -54
- package/ios/Title/Title.swift +6 -17
- package/ios/Tooltip/Tooltip.swift +29 -14
- package/ios/Typography/FontScale.swift +3 -3
- package/ios/Typography/Text.swift +81 -19
- package/ios/Uploader/Uploader.swift +3 -1
- package/ios/native-kits.podspec +2 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
- package/package.json +1 -1
- package/scripts/gen-ios-icons.mjs +36 -0
- package/ios/Application/Components.swift +0 -270
- package/ios/Application/ComposeApi.swift +0 -22
- package/ios/Application/HeaderRight.swift +0 -306
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
- package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
- package/ios/Application/Navigation/HeaderTitle.swift +0 -57
- package/ios/Application/Navigation/NavigationContainer.swift +0 -213
- package/ios/Application/Navigation/NavigationOptions.swift +0 -109
- package/ios/Application/Navigation/NavigatorManager.swift +0 -22
- package/ios/Application/Navigation/component/Header.swift +0 -178
- package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
- package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
- package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
- package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
- package/ios/Application/Screen.swift +0 -250
- package/ios/Application/StackScreen.swift +0 -492
- /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
|
|
19
|
-
let
|
|
20
|
-
let icons: [String]
|
|
21
|
-
let
|
|
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/
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
65
|
-
|
|
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: .
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
}
|
package/ios/Title/Title.swift
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
156
|
-
.
|
|
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(
|
|
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
|
-
|
|
81
|
+
let points: [CGPoint]
|
|
82
82
|
switch placement {
|
|
83
83
|
case .top:
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
//
|
|
34
|
-
|
|
35
|
-
let deviceScale =
|
|
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
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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,
|
package/ios/native-kits.podspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Pod::Spec.new do |s|
|
|
2
2
|
s.name = 'MoMoUIKits'
|
|
3
|
-
s.version = '0.167.1-
|
|
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}",
|
|
@@ -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")),
|