@momo-kits/native-kits 0.162.21-debug → 0.162.22-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.
- package/compose/build.gradle.kts +1 -1
- package/compose/compose.podspec +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +26 -7
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +3 -1
- package/gradle.properties +1 -1
- package/ios/Application/ApplicationEnvironment.swift +4 -2
- package/ios/Application/MaxApi.swift +18 -0
- package/ios/Application/Navigation/NavigationContainer.swift +21 -3
- package/ios/Application/Navigation/Navigator.swift +50 -29
- package/ios/Application/Navigation/Overplay/ModalScreen.swift +6 -0
- package/ios/Application/Navigation/Tracking/ScreenTracker.swift +117 -0
- package/ios/Application/StackScreen.swift +41 -0
- package/ios/Badge/BadgeRibbon.swift +80 -0
- package/ios/Button/Button.swift +67 -11
- package/ios/Carousel/Carousel.swift +16 -1
- package/ios/Checkbox/Checkbox.swift +14 -20
- package/ios/Chip/Chip.swift +35 -26
- package/ios/Collapse/Collapse.swift +11 -1
- package/ios/DateTimePicker/DateTimePicker.swift +4 -2
- package/ios/DateTimePicker/WheelPicker.swift +7 -3
- package/ios/Extensions/ActiveOpacityButtonStyle.swift +14 -0
- package/ios/Icon/Icon.swift +5 -1
- package/ios/IconButton/IconButton.swift +35 -7
- package/ios/Image/Image.swift +16 -3
- package/ios/Information/Information.swift +3 -1
- package/ios/Input/ErrorView.swift +3 -1
- package/ios/Input/Input.swift +31 -8
- package/ios/Input/InputPhoneNumber.swift +14 -3
- package/ios/Input/InputSearch.swift +7 -1
- package/ios/Input/InputTextArea.swift +78 -34
- package/ios/InputDropDown/InputDropDown.swift +4 -1
- package/ios/InputMoney/InputMoney.swift +16 -4
- package/ios/Popup/PopupDisplay.swift +3 -4
- package/ios/Popup/PopupNotify.swift +3 -4
- package/ios/Popup/PopupPromotion.swift +16 -8
- package/ios/ProgressInfo/ProgressInfo.swift +4 -2
- package/ios/Radio/Radio.swift +12 -13
- package/ios/Rating/Rating.swift +1 -1
- package/ios/Stepper/Stepper.swift +2 -2
- package/ios/Steps/Steps.swift +22 -6
- package/ios/SuggestAction/SuggestAction.swift +5 -3
- package/ios/Swipeable/SwipeCell.swift +5 -3
- package/ios/Switch/Switch.swift +53 -28
- package/ios/TabView/TabView.swift +18 -14
- package/ios/Template/TrustBanner/TrustBanner.swift +6 -7
- package/ios/Tooltip/Tooltip.swift +3 -0
- package/ios/Uploader/Uploader.swift +0 -9
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/package.json +1 -1
|
@@ -7,6 +7,8 @@ public enum InformationState {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
public struct Information: View {
|
|
10
|
+
@Environment(\.appTheme) private var theme
|
|
11
|
+
|
|
10
12
|
private let title: String?
|
|
11
13
|
private let description: String
|
|
12
14
|
private let state: InformationState
|
|
@@ -93,7 +95,7 @@ public struct Information: View {
|
|
|
93
95
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
94
96
|
|
|
95
97
|
if let onPressClose, showIconClose {
|
|
96
|
-
Icon(source: "navigation_close", size: 16, color:
|
|
98
|
+
Icon(source: "navigation_close", size: 16, color: theme.colors.text.default)
|
|
97
99
|
.contentShape(Rectangle())
|
|
98
100
|
.onTapGesture(perform: onPressClose)
|
|
99
101
|
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import SwiftUI
|
|
9
9
|
|
|
10
10
|
public struct ErrorView: View {
|
|
11
|
+
@Environment(\.appTheme) private var theme
|
|
12
|
+
|
|
11
13
|
public var errorMessage: String
|
|
12
14
|
public var errorSpacing: Bool
|
|
13
15
|
public var hintText: String
|
|
@@ -43,7 +45,7 @@ public struct ErrorView: View {
|
|
|
43
45
|
// MARK: - Helpers
|
|
44
46
|
|
|
45
47
|
private func displayColor() -> Color {
|
|
46
|
-
!errorMessage.isEmpty ?
|
|
48
|
+
!errorMessage.isEmpty ? theme.colors.error.primary : theme.colors.text.hint
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
private func displayText() -> String {
|
package/ios/Input/Input.swift
CHANGED
|
@@ -35,6 +35,8 @@ public struct Input: View {
|
|
|
35
35
|
|
|
36
36
|
@State private var isFocused: Bool = false
|
|
37
37
|
@State private var isPasswordHidden: Bool = true
|
|
38
|
+
@FocusState private var isFieldFocused: Bool
|
|
39
|
+
@State private var floatingLabelHeight: CGFloat = 16
|
|
38
40
|
|
|
39
41
|
public init(
|
|
40
42
|
text: Binding<String>,
|
|
@@ -99,8 +101,13 @@ public struct Input: View {
|
|
|
99
101
|
let textBinding = Binding<String>(
|
|
100
102
|
get: { self.text },
|
|
101
103
|
set: { newValue in
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
// Trim atomically before publishing, mirroring Kotlin's `onValueChange`
|
|
105
|
+
// (`newText.take(maxLength)` before the state ever updates) — avoids a
|
|
106
|
+
// render with the untrimmed value that a separate `.onChange` correction
|
|
107
|
+
// would otherwise briefly show.
|
|
108
|
+
let limited = limitText(newValue)
|
|
109
|
+
self.text = limited
|
|
110
|
+
self.onChangeText?(limited)
|
|
104
111
|
}
|
|
105
112
|
)
|
|
106
113
|
|
|
@@ -118,8 +125,19 @@ public struct Input: View {
|
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
127
|
.padding(.horizontal, Spacing.S)
|
|
128
|
+
.background(
|
|
129
|
+
GeometryReader { geo in
|
|
130
|
+
Color.clear.onAppear { floatingLabelHeight = geo.size.height }
|
|
131
|
+
}
|
|
132
|
+
)
|
|
121
133
|
.background(theme.colors.background.surface)
|
|
122
|
-
|
|
134
|
+
// Kotlin centers the label within the (box-height-tall) decorationBox
|
|
135
|
+
// by default, then applies `offsetY = -size.values.height / 2` — net
|
|
136
|
+
// effect: the label ends up centered exactly on the box's top border
|
|
137
|
+
// (spanning -labelHeight/2 ... +labelHeight/2 around y=0). Our ZStack
|
|
138
|
+
// anchors top-leading instead, so reproducing that requires offsetting
|
|
139
|
+
// by half the label's OWN measured height, not half the box height.
|
|
140
|
+
.offset(x: Spacing.S, y: -floatingLabelHeight / 2)
|
|
123
141
|
.zIndex(10)
|
|
124
142
|
}
|
|
125
143
|
|
|
@@ -151,6 +169,7 @@ public struct Input: View {
|
|
|
151
169
|
cursorColor: UIColor(theme.colors.primary),
|
|
152
170
|
isDisabled: disabled || readOnly,
|
|
153
171
|
maxLength: maxLength,
|
|
172
|
+
autofocus: autofocus,
|
|
154
173
|
onFocusChange: { focused in
|
|
155
174
|
handleFocusChange(focused)
|
|
156
175
|
},
|
|
@@ -166,11 +185,9 @@ public struct Input: View {
|
|
|
166
185
|
.foregroundColor(getTextColor())
|
|
167
186
|
.disabled(disabled || readOnly)
|
|
168
187
|
.applyCursorColor(theme.colors.primary)
|
|
169
|
-
.
|
|
170
|
-
|
|
171
|
-
if
|
|
172
|
-
text = limited
|
|
173
|
-
}
|
|
188
|
+
.focused($isFieldFocused)
|
|
189
|
+
.onAppear {
|
|
190
|
+
if autofocus { isFieldFocused = true }
|
|
174
191
|
}
|
|
175
192
|
}
|
|
176
193
|
}
|
|
@@ -312,6 +329,7 @@ private struct SecureInputField: UIViewRepresentable {
|
|
|
312
329
|
var cursorColor: UIColor
|
|
313
330
|
var isDisabled: Bool
|
|
314
331
|
var maxLength: Int?
|
|
332
|
+
var autofocus: Bool = false
|
|
315
333
|
var onFocusChange: (Bool) -> Void
|
|
316
334
|
var onChangeText: ((String) -> Void)?
|
|
317
335
|
|
|
@@ -336,6 +354,11 @@ private struct SecureInputField: UIViewRepresentable {
|
|
|
336
354
|
action: #selector(Coordinator.textFieldDidChange(_:)),
|
|
337
355
|
for: .editingChanged
|
|
338
356
|
)
|
|
357
|
+
if autofocus {
|
|
358
|
+
DispatchQueue.main.async {
|
|
359
|
+
textField.becomeFirstResponder()
|
|
360
|
+
}
|
|
361
|
+
}
|
|
339
362
|
return textField
|
|
340
363
|
}
|
|
341
364
|
|
|
@@ -16,6 +16,7 @@ public struct InputPhoneNumber: View {
|
|
|
16
16
|
public var error: String
|
|
17
17
|
public var errorSpacing: Bool
|
|
18
18
|
public var loading: Bool
|
|
19
|
+
public var required: Bool
|
|
19
20
|
public var rightIcon: String
|
|
20
21
|
public var rightIconColor: Color?
|
|
21
22
|
public var autofocus: Bool
|
|
@@ -24,8 +25,9 @@ public struct InputPhoneNumber: View {
|
|
|
24
25
|
public var onBlur: (() -> Void)?
|
|
25
26
|
public var onRightIconPressed: (() -> Void)?
|
|
26
27
|
public var accessibilityLabel: String?
|
|
27
|
-
|
|
28
|
+
|
|
28
29
|
@State private var isFocused: Bool = false
|
|
30
|
+
@FocusState private var isFieldFocused: Bool
|
|
29
31
|
|
|
30
32
|
public init(
|
|
31
33
|
text: Binding<String>,
|
|
@@ -52,6 +54,7 @@ public struct InputPhoneNumber: View {
|
|
|
52
54
|
self.error = error
|
|
53
55
|
self.errorSpacing = errorSpacing
|
|
54
56
|
self.loading = loading
|
|
57
|
+
self.required = required
|
|
55
58
|
self.rightIcon = rightIcon
|
|
56
59
|
self.rightIconColor = rightIconColor
|
|
57
60
|
self.autofocus = autofocus
|
|
@@ -90,9 +93,13 @@ public struct InputPhoneNumber: View {
|
|
|
90
93
|
// Text input
|
|
91
94
|
ZStack(alignment: .leading) {
|
|
92
95
|
if text.isEmpty {
|
|
93
|
-
MomoText(
|
|
96
|
+
MomoText(
|
|
97
|
+
placeholder + (required ? " *" : ""),
|
|
98
|
+
typography: size == .small ? .headerSSemibold : .headerMBold,
|
|
99
|
+
color: theme.colors.text.hint
|
|
100
|
+
)
|
|
94
101
|
}
|
|
95
|
-
|
|
102
|
+
|
|
96
103
|
TextField("", text: textBinding, onEditingChanged: { focused in
|
|
97
104
|
handleFocusChange(focused)
|
|
98
105
|
})
|
|
@@ -102,6 +109,10 @@ public struct InputPhoneNumber: View {
|
|
|
102
109
|
.foregroundColor(theme.colors.text.default)
|
|
103
110
|
.applyCursorColor(theme.colors.primary)
|
|
104
111
|
.lineLimit(1)
|
|
112
|
+
.focused($isFieldFocused)
|
|
113
|
+
.onAppear {
|
|
114
|
+
if autofocus { isFieldFocused = true }
|
|
115
|
+
}
|
|
105
116
|
.accessibility(identifier: accessibilityLabel ?? "")
|
|
106
117
|
.accessibilityValue(textBinding.wrappedValue.isEmpty ? placeholder : textBinding.wrappedValue)
|
|
107
118
|
}
|
|
@@ -238,7 +238,13 @@ public struct InputSearch: View {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
if isFocused && !text.isEmpty {
|
|
241
|
-
SwiftButton(action: {
|
|
241
|
+
SwiftButton(action: {
|
|
242
|
+
text = ""
|
|
243
|
+
// Kotlin's clear icon fires a callback distinct from
|
|
244
|
+
// onChangeText (`onClearPress`, not ported here); route
|
|
245
|
+
// through onChangeText so clearing isn't a silent no-op.
|
|
246
|
+
onChangeText("")
|
|
247
|
+
}) {
|
|
242
248
|
Image(systemName: "xmark.circle.fill")
|
|
243
249
|
.foregroundColor(theme.colors.text.hint)
|
|
244
250
|
.frame(width: 16, height: 16)
|
|
@@ -109,6 +109,7 @@ public struct InputTextArea: View {
|
|
|
109
109
|
|
|
110
110
|
@State private var isFocused = false
|
|
111
111
|
@State private var isBlurred = false
|
|
112
|
+
@State private var floatingLabelHeight: CGFloat = 16
|
|
112
113
|
|
|
113
114
|
// Make the initializer public
|
|
114
115
|
public init(
|
|
@@ -182,45 +183,88 @@ public struct InputTextArea: View {
|
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
.padding(.horizontal, Spacing.S)
|
|
186
|
+
.background(
|
|
187
|
+
GeometryReader { geo in
|
|
188
|
+
Color.clear.onAppear { floatingLabelHeight = geo.size.height }
|
|
189
|
+
}
|
|
190
|
+
)
|
|
185
191
|
.background(theme.colors.background.surface)
|
|
186
|
-
|
|
192
|
+
// Kotlin centers the label within the box-height-tall decorationBox
|
|
193
|
+
// by default, then applies `offsetY = -scaleHeight / 2` — net effect:
|
|
194
|
+
// the label ends up centered exactly on the box's top border. Our
|
|
195
|
+
// ZStack anchors top-leading instead, so matching that requires
|
|
196
|
+
// offsetting by half the label's OWN measured height, not half the
|
|
197
|
+
// (104pt-tall) text-area box height.
|
|
198
|
+
.offset(x: Spacing.S, y: -floatingLabelHeight / 2)
|
|
187
199
|
.zIndex(10)
|
|
188
200
|
}
|
|
189
|
-
|
|
201
|
+
|
|
190
202
|
VStack {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
203
|
+
HStack(alignment: .top, spacing: 0) {
|
|
204
|
+
ZStack(alignment: .topLeading) {
|
|
205
|
+
if text.isEmpty {
|
|
206
|
+
Text(placeholder)
|
|
207
|
+
.foregroundColor(disabled ? theme.colors.text.disable : theme.colors.text.hint)
|
|
208
|
+
.font(.system(size: 16, weight: fontWeight))
|
|
209
|
+
.padding(.horizontal, 16)
|
|
210
|
+
.padding(.vertical, 12)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
UITextViewWrapper(
|
|
214
|
+
text: $text,
|
|
215
|
+
maxLength: maxLength,
|
|
216
|
+
textColor: disabled ? theme.colors.text.disable : theme.colors.text.default,
|
|
217
|
+
cursorColor: theme.colors.primary,
|
|
218
|
+
fontWeight: fontWeight,
|
|
219
|
+
disabled: disabled,
|
|
220
|
+
onChangeText: onChangeText,
|
|
221
|
+
onDone: {
|
|
222
|
+
// Handle done action if needed
|
|
223
|
+
},
|
|
224
|
+
onFocus: {
|
|
225
|
+
isFocused = true
|
|
226
|
+
onFocus()
|
|
227
|
+
}, onBlur: {
|
|
228
|
+
isFocused = false
|
|
229
|
+
isBlurred = true
|
|
230
|
+
onBlur()
|
|
231
|
+
})
|
|
232
|
+
.frame(height: height)
|
|
233
|
+
.padding(.horizontal, 12)
|
|
234
|
+
.padding(.vertical, 8)
|
|
235
|
+
.disabled(disabled)
|
|
198
236
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
.
|
|
222
|
-
|
|
223
|
-
|
|
237
|
+
.frame(maxWidth: .infinity)
|
|
238
|
+
|
|
239
|
+
// Clear button (mirrors Compose: focused + non-empty).
|
|
240
|
+
if isFocused && !text.isEmpty {
|
|
241
|
+
SwiftUI.Button(action: {
|
|
242
|
+
text = ""
|
|
243
|
+
onChangeText("")
|
|
244
|
+
}) {
|
|
245
|
+
Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
|
|
246
|
+
}
|
|
247
|
+
.padding(.leading, Spacing.XS)
|
|
248
|
+
.padding(.top, 12)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Loading indicator and right icon (independent, mirrors
|
|
252
|
+
// Compose's RenderRightIcon: both can render at once).
|
|
253
|
+
if loading {
|
|
254
|
+
ActivityIndicator(isAnimating: .constant(true), style: .medium)
|
|
255
|
+
.frame(width: 16, height: 16)
|
|
256
|
+
.padding(.leading, Spacing.XS)
|
|
257
|
+
.padding(.top, 12)
|
|
258
|
+
}
|
|
259
|
+
if !icon.isEmpty {
|
|
260
|
+
SwiftUI.Button(action: onRightIconPressed) {
|
|
261
|
+
Icon(source: icon, size: 24, color: disabled ? theme.colors.text.disable : (iconColor ?? theme.colors.text.default))
|
|
262
|
+
}
|
|
263
|
+
.padding(.leading, Spacing.XS)
|
|
264
|
+
.padding(.top, 12)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
224
268
|
HStack {
|
|
225
269
|
Spacer()
|
|
226
270
|
MomoText("\(text.count)/\(maxLength)", typography: .descriptionXsRegular, color: theme.colors.text.hint)
|
|
@@ -16,10 +16,13 @@ private struct RenderRightIcon: View {
|
|
|
16
16
|
let onPressed: () -> Void
|
|
17
17
|
|
|
18
18
|
var body: some View {
|
|
19
|
+
// Mirrors Compose's two independent `if` blocks: loading spinner and icon
|
|
20
|
+
// can both render at once, not exclusively (not an if/else).
|
|
19
21
|
if loading {
|
|
20
22
|
ActivityIndicator(isAnimating: .constant(true), style: .medium)
|
|
21
23
|
.frame(width: 16, height: 16)
|
|
22
|
-
}
|
|
24
|
+
}
|
|
25
|
+
if !icon.isEmpty {
|
|
23
26
|
SwiftUI.Button(action: onPressed) {
|
|
24
27
|
Icon(source: icon, size: 24, color: color)
|
|
25
28
|
}
|
|
@@ -75,6 +75,7 @@ public struct InputMoney: View {
|
|
|
75
75
|
public var onBlur: (() -> Void)?
|
|
76
76
|
|
|
77
77
|
@State private var isFocused: Bool = false
|
|
78
|
+
@State private var floatingLabelHeight: CGFloat = 16
|
|
78
79
|
|
|
79
80
|
public init(
|
|
80
81
|
text: Binding<String>,
|
|
@@ -141,8 +142,18 @@ public struct InputMoney: View {
|
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
.padding(.horizontal, Spacing.S)
|
|
145
|
+
.background(
|
|
146
|
+
GeometryReader { geo in
|
|
147
|
+
Color.clear.onAppear { floatingLabelHeight = geo.size.height }
|
|
148
|
+
}
|
|
149
|
+
)
|
|
144
150
|
.background(theme.colors.background.surface)
|
|
145
|
-
|
|
151
|
+
// Kotlin centers the label within the box-height-tall decorationBox by
|
|
152
|
+
// default, then applies `offsetY = -size.values.height / 2` — net effect:
|
|
153
|
+
// the label ends up centered exactly on the box's top border. Our ZStack
|
|
154
|
+
// anchors top-leading instead, so matching that requires offsetting by
|
|
155
|
+
// half the label's OWN measured height, not half the box height.
|
|
156
|
+
.offset(x: Spacing.S, y: -floatingLabelHeight / 2)
|
|
146
157
|
.zIndex(10)
|
|
147
158
|
}
|
|
148
159
|
|
|
@@ -188,13 +199,14 @@ public struct InputMoney: View {
|
|
|
188
199
|
}
|
|
189
200
|
}
|
|
190
201
|
|
|
191
|
-
// Loading indicator
|
|
202
|
+
// Loading indicator and right icon can both render (mirrors
|
|
203
|
+
// Compose's two independent `if` blocks, not if/else).
|
|
192
204
|
if loading {
|
|
193
205
|
ActivityIndicator(isAnimating: .constant(true), style: .medium)
|
|
194
206
|
.frame(width: 16, height: 16)
|
|
195
207
|
.padding(.leading, Spacing.S)
|
|
196
|
-
}
|
|
197
|
-
|
|
208
|
+
}
|
|
209
|
+
if !icon.isEmpty {
|
|
198
210
|
SwiftUI.Button(action: { onPressIcon?() }) {
|
|
199
211
|
Icon(source: icon, size: 24, color: iconTintColor)
|
|
200
212
|
.padding(.leading, Spacing.S)
|
|
@@ -54,6 +54,8 @@ extension View {
|
|
|
54
54
|
// MARK: - PopupDisplay
|
|
55
55
|
|
|
56
56
|
public struct PopupDisplay: View {
|
|
57
|
+
@EnvironmentObject private var localize: Localize
|
|
58
|
+
|
|
57
59
|
// MARK: Lifecycle
|
|
58
60
|
|
|
59
61
|
public init(isPresented: Binding<Bool>, title: String = "", description: String = "", url: String = "", buttonDirection: PopupButtonDirection = .column, buttonType: PopupButtonType = .button, actionButtonTitle: String = "", closeButtonTitle: String = "", onPressAction: @escaping () -> Void = {}, onPressCloseButton: @escaping () -> Void = {}, onClose: @escaping () -> Void = {}, errorCode: String = "", isShowCloseIcon: Bool = true) {
|
|
@@ -91,9 +93,6 @@ public struct PopupDisplay: View {
|
|
|
91
93
|
@State private var isScrollable = false
|
|
92
94
|
|
|
93
95
|
public var body: some View {
|
|
94
|
-
let language = UserDefaults.standard.string(forKey: "language")?.replacingOccurrences(of: "\"", with: "") ?? "vi"
|
|
95
|
-
let errorCodeLabels: [String: String] = ["vi": "Mã lỗi: ", "en": "Error code: "]
|
|
96
|
-
|
|
97
96
|
var shouldUseColumn: Bool {
|
|
98
97
|
buttonDirection == .column || (buttonDirection == .auto && (closeButtonTitle.count > 12 || actionButtonTitle.count > 12))
|
|
99
98
|
}
|
|
@@ -170,7 +169,7 @@ public struct PopupDisplay: View {
|
|
|
170
169
|
.padding(.bottom, 8)
|
|
171
170
|
|
|
172
171
|
if(!errorCode.isEmpty) {
|
|
173
|
-
MomoText((
|
|
172
|
+
MomoText(localize.translate("errorCode") + errorCode, typography: .descriptionXsRegular, color: Colors.black12)
|
|
174
173
|
.lineLimit(1)
|
|
175
174
|
.padding(.bottom, 8)
|
|
176
175
|
}
|
|
@@ -38,15 +38,14 @@ public struct PopupNotifyProps {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
public struct PopupNotify: View {
|
|
41
|
+
@EnvironmentObject private var localize: Localize
|
|
42
|
+
|
|
41
43
|
public let props: PopupNotifyProps
|
|
42
44
|
|
|
43
45
|
@State private var descriptionHeight: CGFloat = 0
|
|
44
46
|
|
|
45
47
|
private var errorCodePrefix: String {
|
|
46
|
-
|
|
47
|
-
.string(forKey: "language")?
|
|
48
|
-
.replacingOccurrences(of: "\"", with: "") ?? "vi"
|
|
49
|
-
return language == "en" ? "Error code: " : "Mã lỗi: "
|
|
48
|
+
localize.translate("errorCode")
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
private var maxDescriptionHeight: CGFloat {
|
|
@@ -2,6 +2,8 @@ import SwiftUI
|
|
|
2
2
|
import SDWebImageSwiftUI
|
|
3
3
|
|
|
4
4
|
public struct PopupPromotion: View {
|
|
5
|
+
@Environment(\.appTheme) private var theme
|
|
6
|
+
|
|
5
7
|
var imageUrl: String
|
|
6
8
|
var onIconClose: (() -> Void)?
|
|
7
9
|
var onAction: (() -> Void)?
|
|
@@ -18,21 +20,26 @@ public struct PopupPromotion: View {
|
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
public var body: some View {
|
|
23
|
+
// Mirrors Kotlin's `Modifier.fillMaxWidth().aspectRatio(0.72f)` box with
|
|
24
|
+
// `ContentScale.FillBounds` inside it: a fixed-ratio frame the image stretches
|
|
25
|
+
// to exactly fill (no `.aspectRatio` modifier on the image itself, unlike the
|
|
26
|
+
// previous `.fit`, which letterboxed instead of stretching).
|
|
27
|
+
let boxWidth = UIScreen.main.bounds.width - Spacing.XL * 2
|
|
28
|
+
let boxHeight = boxWidth / 0.72
|
|
29
|
+
|
|
21
30
|
VStack(spacing: 8) {
|
|
22
31
|
if(imageUrl.isEmpty) {
|
|
23
|
-
Icon(source: "media_fail", size:
|
|
32
|
+
Icon(source: "media_fail", size: boxWidth)
|
|
24
33
|
}
|
|
25
34
|
else {
|
|
26
35
|
WebImage(url: URL(string: imageUrl)!)
|
|
27
36
|
.resizable()
|
|
28
37
|
.placeholder {
|
|
29
38
|
Color.gray
|
|
30
|
-
.
|
|
31
|
-
.frame(maxWidth: UIScreen.main.bounds.width - Spacing.XL * 2)
|
|
39
|
+
.frame(width: boxWidth, height: boxHeight)
|
|
32
40
|
.clipped()
|
|
33
41
|
}
|
|
34
|
-
.
|
|
35
|
-
.frame(maxWidth: UIScreen.main.bounds.width - Spacing.XL * 2, alignment: .center)
|
|
42
|
+
.frame(width: boxWidth, height: boxHeight, alignment: .center)
|
|
36
43
|
.clipped()
|
|
37
44
|
.onTapGesture {
|
|
38
45
|
onAction?()
|
|
@@ -53,16 +60,17 @@ public struct PopupPromotion: View {
|
|
|
53
60
|
}) {
|
|
54
61
|
ZStack {
|
|
55
62
|
Circle()
|
|
56
|
-
.strokeBorder(
|
|
57
|
-
.background(Circle().fill(
|
|
63
|
+
.strokeBorder(theme.colors.background.surface, lineWidth: 2)
|
|
64
|
+
.background(Circle().fill(theme.colors.text.default))
|
|
58
65
|
.frame(width: 22, height: 22)
|
|
59
66
|
|
|
60
|
-
Icon(source: "navigation_close", size: 16,
|
|
67
|
+
Icon(source: "navigation_close", size: 16, color: theme.colors.background.surface)
|
|
61
68
|
}
|
|
62
69
|
.frame(width: 40, height: 40)
|
|
63
70
|
.padding(8)
|
|
64
71
|
.zIndex(1)
|
|
65
72
|
}
|
|
73
|
+
.buttonStyle(.plain)
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
76
|
|
|
@@ -97,7 +97,7 @@ public struct ProgressInfo: View {
|
|
|
97
97
|
// MARK: - Tokens
|
|
98
98
|
|
|
99
99
|
private let progressInfoLineColor = Colors.pink08
|
|
100
|
-
private let progressInfoAccentColor =
|
|
100
|
+
private let progressInfoAccentColor = Colors.pinkMoMoBranding
|
|
101
101
|
|
|
102
102
|
// MARK: - Horizontal variant
|
|
103
103
|
|
|
@@ -196,6 +196,8 @@ private struct ProgressInfoHorizontal: View {
|
|
|
196
196
|
// MARK: - Vertical variant
|
|
197
197
|
|
|
198
198
|
private struct ProgressInfoVertical: View {
|
|
199
|
+
@Environment(\.appTheme) private var theme
|
|
200
|
+
|
|
199
201
|
let steps: [ProgressInfoItem]
|
|
200
202
|
let size: ProgressInfoSize
|
|
201
203
|
let useNumber: Bool
|
|
@@ -218,7 +220,7 @@ private struct ProgressInfoVertical: View {
|
|
|
218
220
|
)
|
|
219
221
|
if !isLast {
|
|
220
222
|
Rectangle()
|
|
221
|
-
.fill(
|
|
223
|
+
.fill(theme.colors.primary.opacity(0.2))
|
|
222
224
|
.frame(width: 2)
|
|
223
225
|
.frame(maxHeight: .infinity)
|
|
224
226
|
}
|
package/ios/Radio/Radio.swift
CHANGED
|
@@ -34,22 +34,21 @@ public struct Radio: View {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
public var body: some View {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
SwiftUI.Button(action: onTap) {
|
|
38
|
+
HStack(spacing: 0) {
|
|
39
|
+
RoundedRectangle(cornerRadius: Radius.M)
|
|
40
|
+
.stroke(borderColor, lineWidth: borderWidth)
|
|
41
|
+
.frame(width: 20, height: 20)
|
|
42
|
+
.padding(.trailing, label.isEmpty ? 0 : Spacing.XS)
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
.contentShape(Rectangle())
|
|
48
|
-
.onTapGesture {
|
|
49
|
-
if !disabled {
|
|
50
|
-
onTap()
|
|
44
|
+
if !label.isEmpty {
|
|
45
|
+
MomoText(label, typography: .descriptionDefaultRegular)
|
|
46
|
+
}
|
|
51
47
|
}
|
|
48
|
+
.contentShape(Rectangle())
|
|
52
49
|
}
|
|
50
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
51
|
+
.disabled(disabled)
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
private var borderWidth: CGFloat {
|
package/ios/Rating/Rating.swift
CHANGED
|
@@ -70,14 +70,14 @@ private struct StepperButton: View {
|
|
|
70
70
|
let iconColor: Color = disabled ? theme.colors.text.disable : theme.colors.primary
|
|
71
71
|
let iconName = sign == "-" ? "24_navigation_minus_circle" : "24_navigation_plus_circle"
|
|
72
72
|
|
|
73
|
-
SwiftUI.Button(action:
|
|
73
|
+
SwiftUI.Button(action: onPress) {
|
|
74
74
|
ZStack {
|
|
75
75
|
Circle().fill(theme.colors.background.disable)
|
|
76
76
|
Icon(source: iconName, size: specs.iconSize, color: iconColor)
|
|
77
77
|
}
|
|
78
78
|
.frame(width: specs.buttonSize, height: specs.buttonSize)
|
|
79
79
|
}
|
|
80
|
-
.buttonStyle(
|
|
80
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
81
81
|
.disabled(disabled)
|
|
82
82
|
}
|
|
83
83
|
}
|
package/ios/Steps/Steps.swift
CHANGED
|
@@ -201,7 +201,7 @@ private struct StepsHorizontal: View {
|
|
|
201
201
|
|
|
202
202
|
let clickable = status == .completed && onPress != nil && !disabled
|
|
203
203
|
|
|
204
|
-
VStack(alignment: colAlignment, spacing: 0) {
|
|
204
|
+
let columnBody = VStack(alignment: colAlignment, spacing: 0) {
|
|
205
205
|
if let time = item.time, !time.isEmpty {
|
|
206
206
|
MomoText(time, typography: .descriptionXsRegular, color: subColor)
|
|
207
207
|
.multilineTextAlignment(textAlignment)
|
|
@@ -250,8 +250,16 @@ private struct StepsHorizontal: View {
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
.contentShape(Rectangle())
|
|
253
|
-
|
|
254
|
-
|
|
253
|
+
|
|
254
|
+
if clickable {
|
|
255
|
+
// Mirrors Compose's `activeOpacityClickable` press feedback; non-clickable
|
|
256
|
+
// steps render with no press interaction at all.
|
|
257
|
+
SwiftUI.Button(action: { onPress?(item, index) }) {
|
|
258
|
+
columnBody
|
|
259
|
+
}
|
|
260
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
261
|
+
} else {
|
|
262
|
+
columnBody
|
|
255
263
|
}
|
|
256
264
|
}
|
|
257
265
|
|
|
@@ -338,7 +346,7 @@ private struct StepsVertical: View {
|
|
|
338
346
|
|
|
339
347
|
let clickable = status == .completed && onPress != nil && !disabled
|
|
340
348
|
|
|
341
|
-
HStack(alignment: .top, spacing: 0) {
|
|
349
|
+
let rowBody = HStack(alignment: .top, spacing: 0) {
|
|
342
350
|
VStack(spacing: 0) {
|
|
343
351
|
StepBubble(
|
|
344
352
|
index: index,
|
|
@@ -380,8 +388,16 @@ private struct StepsVertical: View {
|
|
|
380
388
|
}
|
|
381
389
|
.frame(maxWidth: .infinity)
|
|
382
390
|
.contentShape(Rectangle())
|
|
383
|
-
|
|
384
|
-
|
|
391
|
+
|
|
392
|
+
if clickable {
|
|
393
|
+
// Mirrors Compose's `activeOpacityClickable` press feedback; non-clickable
|
|
394
|
+
// steps render with no press interaction at all.
|
|
395
|
+
SwiftUI.Button(action: { onPress?(item, index) }) {
|
|
396
|
+
rowBody
|
|
397
|
+
}
|
|
398
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
399
|
+
} else {
|
|
400
|
+
rowBody
|
|
385
401
|
}
|
|
386
402
|
}
|
|
387
403
|
}
|