@momo-kits/native-kits 0.167.1-duo.1-debug → 0.167.1-duo.10-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/CLAUDE.md +32 -0
- package/compose/build.gradle.kts +15 -1
- package/compose/build.gradle.kts.backup +14 -0
- package/compose/compose.podspec +1 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +20 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +4 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +9 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +11 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +10 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +6 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +53 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +7 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +13 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +13 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +3 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +14 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +19 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +5 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +7 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +13 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +21 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +232 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +11 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +4 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +143 -31
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +100 -66
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +22 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +98 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +312 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +66 -18
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +23 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +3 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +81 -24
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +185 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +13 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +4 -1
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +86 -0
- package/gradle.properties +1 -1
- package/ios/Application/ApplicationEnvironment.swift +17 -0
- package/ios/Application/ComposeApi.swift +2 -1
- package/ios/Application/{Components.swift → LegacyHeader.swift} +7 -65
- package/ios/Application/Screen.swift +3 -2
- 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/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 +102 -75
- 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 +123 -3
- package/ios/Layout/Item.swift +5 -4
- package/ios/Layout/ScreenLayout.swift +312 -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 +97 -8
- package/ios/Navigation/BottomTab/BottomTab.swift +187 -0
- package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +15 -7
- package/ios/Navigation/BottomTab/DuoBarCompression.swift +105 -0
- package/ios/Navigation/BottomTab/DuoTabBar.swift +304 -0
- package/ios/{Application → Navigation/Component}/FloatingButton.swift +14 -3
- package/ios/Navigation/Component/Header.swift +493 -0
- package/ios/Navigation/Component/HeaderBackground.swift +134 -0
- package/ios/Navigation/Component/HeaderJourney.swift +73 -0
- package/ios/Navigation/Component/HeaderLocation.swift +48 -0
- package/ios/Navigation/Component/HeaderRight.swift +386 -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 +176 -0
- package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +5 -5
- package/ios/Navigation/HostBackGestureBridge.swift +57 -0
- package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +5 -1
- package/ios/Navigation/Navigation.swift +190 -0
- package/ios/{Application/Navigation → Navigation}/NavigationContainer.swift +92 -81
- package/ios/{Application/Navigation → Navigation}/Navigator.swift +185 -36
- package/ios/Navigation/StackScreen.swift +782 -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 -136
- package/ios/Popup/PopupInput.swift +10 -13
- package/ios/Popup/PopupLayout.swift +140 -0
- package/ios/Popup/PopupNotify.swift +56 -73
- 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/Text.swift +61 -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 +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
- package/package.json +1 -1
- package/scripts/gen-ios-icons.mjs +36 -0
- package/.claude/settings.local.json +0 -185
- 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/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/StackScreen.swift +0 -492
- package/local.properties +0 -8
- /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import SDWebImageSwiftUI
|
|
2
1
|
import SwiftUI
|
|
3
2
|
|
|
4
3
|
public typealias PopupActionDirection = PopupButtonDirection
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let
|
|
9
|
-
|
|
5
|
+
public struct PopupAction {
|
|
6
|
+
public let title: String
|
|
7
|
+
public let onPress: (() -> Void)?
|
|
8
|
+
|
|
9
|
+
public init(title: String, onPress: (() -> Void)? = nil) {
|
|
10
|
+
self.title = title
|
|
11
|
+
self.onPress = onPress
|
|
12
|
+
}
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
public struct PopupNotifyProps {
|
|
@@ -44,19 +47,26 @@ public struct PopupNotifyProps {
|
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
public struct PopupNotify: View {
|
|
50
|
+
@Environment(\.appTheme) private var theme
|
|
47
51
|
@Environment(\.localize) private var localize
|
|
48
52
|
@Environment(\.momoFontScale) private var fontScale
|
|
53
|
+
@Environment(\.screenLayout) private var layout
|
|
49
54
|
|
|
50
55
|
public let props: PopupNotifyProps
|
|
51
56
|
|
|
52
57
|
@State private var descriptionHeight: CGFloat = 0
|
|
58
|
+
@State private var actionsHeight: CGFloat = 0
|
|
59
|
+
@State private var oneLineHeight: CGFloat = 0
|
|
60
|
+
@State private var twoLinesHeight: CGFloat = 0
|
|
53
61
|
|
|
54
62
|
private var errorCodePrefix: String {
|
|
55
63
|
localize.translate("errorCode")
|
|
56
64
|
}
|
|
57
65
|
|
|
66
|
+
// ~8.5 lines as laid out (Compose measures the real lines); nominal line height until measured
|
|
58
67
|
private var maxDescriptionHeight: CGFloat {
|
|
59
|
-
|
|
68
|
+
popupLinesHeight(8.5, oneLine: oneLineHeight, twoLines: twoLinesHeight)
|
|
69
|
+
?? fontScale(TypographyStyle.bodyDefaultRegular.lineHeight) * 8.5
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
private var shouldUseColumnButtons: Bool {
|
|
@@ -66,7 +76,9 @@ public struct PopupNotify: View {
|
|
|
66
76
|
case .row:
|
|
67
77
|
return false
|
|
68
78
|
case .auto:
|
|
69
|
-
|
|
79
|
+
// UTF-16 length, as Kotlin and JS count it
|
|
80
|
+
guard let secondary = props.secondary else { return false }
|
|
81
|
+
return secondary.title.utf16.count > 12 || (props.primary?.title.utf16.count ?? 0) > 12
|
|
70
82
|
}
|
|
71
83
|
}
|
|
72
84
|
|
|
@@ -77,64 +89,60 @@ public struct PopupNotify: View {
|
|
|
77
89
|
public var body: some View {
|
|
78
90
|
ZStack(alignment: .topTrailing) {
|
|
79
91
|
VStack(spacing: 0) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
typography: .headerDefaultBold,
|
|
88
|
-
maxLines: 2,
|
|
89
|
-
accessibilityId: "title_popup_permission"
|
|
90
|
-
)
|
|
91
|
-
.padding(.bottom, Spacing.S)
|
|
92
|
-
|
|
93
|
-
descriptionContent
|
|
94
|
-
|
|
95
|
-
if !props.error.isEmpty {
|
|
96
|
-
MomoText(
|
|
97
|
-
errorCodePrefix + props.error,
|
|
98
|
-
typography: .descriptionXsRegular,
|
|
99
|
-
color: Colors.black12,
|
|
100
|
-
maxLines: 1
|
|
101
|
-
)
|
|
102
|
-
.padding(.top, Spacing.S)
|
|
92
|
+
// scrolls when iPhone Duo landscape is too short; actions stay visible
|
|
93
|
+
PopupScrollBody(maxHeight: layout.popupMaxHeight.map { $0 - actionsHeight }) {
|
|
94
|
+
VStack(spacing: 0) {
|
|
95
|
+
if !props.image.isEmpty {
|
|
96
|
+
PopupHeaderImage(url: props.image)
|
|
97
|
+
}
|
|
98
|
+
textContent
|
|
103
99
|
}
|
|
104
100
|
}
|
|
105
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
106
|
-
.padding(Spacing.XL)
|
|
107
101
|
|
|
108
102
|
actionButtons
|
|
109
103
|
.padding(.horizontal, Spacing.XL)
|
|
110
104
|
.padding(.bottom, Spacing.XL)
|
|
105
|
+
.popupReadHeight($actionsHeight)
|
|
111
106
|
}
|
|
112
|
-
.background(
|
|
107
|
+
.background(theme.colors.background.surface)
|
|
113
108
|
.clipShape(RoundedRectangle(cornerRadius: Radius.L))
|
|
114
109
|
|
|
115
110
|
if props.isShowCloseIcon {
|
|
116
|
-
|
|
111
|
+
PopupCloseButton(action: props.onIconClose)
|
|
117
112
|
.offset(x: 11, y: -11)
|
|
118
113
|
}
|
|
119
114
|
}
|
|
120
|
-
.frame(maxWidth: popupMaxWidth
|
|
115
|
+
.frame(maxWidth: layout.popupMaxWidth)
|
|
121
116
|
.padding(.horizontal, Spacing.M)
|
|
122
|
-
.accessibilityElement(children: .
|
|
117
|
+
.accessibilityElement(children: .contain)
|
|
123
118
|
.accessibilityIdentifier("popup_notify")
|
|
124
119
|
}
|
|
125
120
|
|
|
126
|
-
private var
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
121
|
+
private var textContent: some View {
|
|
122
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
123
|
+
MomoText(
|
|
124
|
+
props.title,
|
|
125
|
+
typography: .headerDefaultBold,
|
|
126
|
+
maxLines: 2,
|
|
127
|
+
accessibilityId: "title_popup_permission"
|
|
128
|
+
)
|
|
129
|
+
.padding(.bottom, Spacing.S)
|
|
130
|
+
|
|
131
|
+
descriptionContent
|
|
132
|
+
|
|
133
|
+
if !props.error.isEmpty {
|
|
134
|
+
MomoText(
|
|
135
|
+
errorCodePrefix + props.error,
|
|
136
|
+
typography: .descriptionXsRegular,
|
|
137
|
+
color: theme.colors.text.hint,
|
|
138
|
+
maxLines: 1
|
|
139
|
+
)
|
|
140
|
+
.padding(.top, Spacing.S)
|
|
134
141
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
}
|
|
143
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
144
|
+
.padding(Spacing.XL)
|
|
145
|
+
.measureLineHeights(typography: .bodyDefaultRegular, oneLine: $oneLineHeight, twoLines: $twoLinesHeight)
|
|
138
146
|
}
|
|
139
147
|
|
|
140
148
|
@ViewBuilder
|
|
@@ -156,15 +164,7 @@ public struct PopupNotify: View {
|
|
|
156
164
|
accessibilityId: "description_popup_permission"
|
|
157
165
|
)
|
|
158
166
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
159
|
-
.
|
|
160
|
-
GeometryReader { proxy in
|
|
161
|
-
Color.clear
|
|
162
|
-
.onAppear { descriptionHeight = proxy.size.height }
|
|
163
|
-
.onChange(of: props.description) { _ in
|
|
164
|
-
descriptionHeight = proxy.size.height
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
)
|
|
167
|
+
.popupReadHeight($descriptionHeight)
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
@ViewBuilder
|
|
@@ -206,21 +206,4 @@ public struct PopupNotify: View {
|
|
|
206
206
|
)
|
|
207
207
|
.accessibilityIdentifier("btn_popup_cancel")
|
|
208
208
|
}
|
|
209
|
-
|
|
210
|
-
private var closeButton: some View {
|
|
211
|
-
SwiftUI.Button(action: props.onIconClose) {
|
|
212
|
-
ZStack {
|
|
213
|
-
Circle()
|
|
214
|
-
.fill(Colors.black17)
|
|
215
|
-
.overlay(
|
|
216
|
-
Circle()
|
|
217
|
-
.stroke(Colors.black01, lineWidth: 2)
|
|
218
|
-
)
|
|
219
|
-
.frame(width: 22, height: 22)
|
|
220
|
-
|
|
221
|
-
Icon(source: "navigation_close", size: 16, color: Colors.black01)
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
.accessibilityIdentifier("ic_popup_close")
|
|
225
|
-
}
|
|
226
209
|
}
|
|
@@ -2,12 +2,13 @@ import SwiftUI
|
|
|
2
2
|
import SDWebImageSwiftUI
|
|
3
3
|
|
|
4
4
|
public struct PopupPromotion: View {
|
|
5
|
-
@Environment(\.
|
|
5
|
+
@Environment(\.screenLayout) private var layout
|
|
6
6
|
|
|
7
7
|
var imageUrl: String
|
|
8
8
|
var onIconClose: (() -> Void)?
|
|
9
9
|
var onAction: (() -> Void)?
|
|
10
10
|
|
|
11
|
+
@State private var imageFailed = false
|
|
11
12
|
|
|
12
13
|
public init(
|
|
13
14
|
imageUrl: String,
|
|
@@ -19,56 +20,39 @@ public struct PopupPromotion: View {
|
|
|
19
20
|
self.onAction = onAction
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
WebImage(url: URL(string: imageUrl)!)
|
|
29
|
-
.resizable()
|
|
30
|
-
.placeholder {
|
|
31
|
-
Color.gray
|
|
32
|
-
.aspectRatio(0.72, contentMode: .fit)
|
|
33
|
-
.frame(maxWidth: popupMaxWidth - Spacing.XL * 2)
|
|
34
|
-
.clipped()
|
|
35
|
-
}
|
|
36
|
-
.aspectRatio(0.72, contentMode: .fit)
|
|
37
|
-
.frame(maxWidth: popupMaxWidth - Spacing.XL * 2, alignment: .center)
|
|
38
|
-
.clipped()
|
|
39
|
-
.onTapGesture {
|
|
40
|
-
onAction?()
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
buildCloseIcon()
|
|
44
|
-
.accessibility(identifier: "ic_popup_close")
|
|
45
|
-
}
|
|
46
|
-
.accessibilityElement(children: .ignore)
|
|
47
|
-
.accessibility(identifier: "popup_promotion")
|
|
48
|
-
}
|
|
23
|
+
private static let aspectRatio: CGFloat = 0.72
|
|
24
|
+
private static let closeTapSize: CGFloat = 40
|
|
25
|
+
// close button row under the image: top padding + 40pt tap target
|
|
26
|
+
private static let closeRowHeight: CGFloat = Spacing.S + closeTapSize
|
|
49
27
|
|
|
50
|
-
|
|
51
|
-
private
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
ZStack {
|
|
57
|
-
Circle()
|
|
58
|
-
.strokeBorder(theme.colors.background.surface, lineWidth: 2)
|
|
59
|
-
.background(Circle().fill(theme.colors.text.default))
|
|
60
|
-
.frame(width: 22, height: 22)
|
|
28
|
+
// fit the portrait image by height when iPhone Duo leaves too little room
|
|
29
|
+
private var imageWidth: CGFloat {
|
|
30
|
+
let width = min(layout.width - Spacing.M * 2, layout.popupMaxWidth)
|
|
31
|
+
guard let maxHeight = layout.popupMaxHeight else { return width }
|
|
32
|
+
return min(width, (maxHeight - Self.closeRowHeight) * Self.aspectRatio)
|
|
33
|
+
}
|
|
61
34
|
|
|
62
|
-
|
|
35
|
+
public var body: some View {
|
|
36
|
+
VStack(spacing: 0) {
|
|
37
|
+
// stretched like Compose's ContentScale.FillBounds; an empty url shows the error state, as in Compose
|
|
38
|
+
WebImage(url: URL(string: imageUrl))
|
|
39
|
+
.onSuccess { _, _, _ in imageFailed = false }
|
|
40
|
+
.onFailure { _ in imageFailed = true }
|
|
41
|
+
.resizable()
|
|
42
|
+
.placeholder { PopupImagePlaceholder(failed: imageFailed || URL(string: imageUrl) == nil) }
|
|
43
|
+
.aspectRatio(Self.aspectRatio, contentMode: .fit)
|
|
44
|
+
.frame(maxWidth: imageWidth)
|
|
45
|
+
.contentShape(Rectangle())
|
|
46
|
+
.onTapGesture {
|
|
47
|
+
onAction?()
|
|
63
48
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
49
|
+
|
|
50
|
+
PopupCloseButton(tapSize: Self.closeTapSize) {
|
|
51
|
+
onIconClose?()
|
|
67
52
|
}
|
|
53
|
+
.padding(.top, Spacing.S)
|
|
68
54
|
}
|
|
55
|
+
.accessibilityElement(children: .contain)
|
|
56
|
+
.accessibilityIdentifier("popup_promotion")
|
|
69
57
|
}
|
|
70
|
-
|
|
71
58
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
@@ -51,12 +51,9 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
|
|
|
51
51
|
@State private var opacity = 0.0
|
|
52
52
|
|
|
53
53
|
// MARK: - Autohide
|
|
54
|
-
///
|
|
55
|
-
|
|
56
|
-
private var
|
|
57
|
-
|
|
58
|
-
/// holder for autohiding dispatch work (to be able to cancel it when needed)
|
|
59
|
-
private var dispatchWorkHolder = DispatchWorkHolder()
|
|
54
|
+
/// holder for autohiding dispatch work (to be able to cancel it when needed);
|
|
55
|
+
/// state so it outlives re-renders, which would otherwise orphan the pending work
|
|
56
|
+
@State private var dispatchWorkHolder = DispatchWorkHolder()
|
|
60
57
|
|
|
61
58
|
// MARK: - Internal
|
|
62
59
|
/// Set dismiss souce to pass to dismiss callback
|
|
@@ -85,9 +82,6 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
|
|
|
85
82
|
self.backgroundColor = backgroundColor
|
|
86
83
|
self.dismissCallback = dismissCallback
|
|
87
84
|
self.view = view
|
|
88
|
-
|
|
89
|
-
self.isPresentedRef = ClassReference(self.$isPresented)
|
|
90
|
-
self.itemRef = ClassReference(self.$item)
|
|
91
85
|
}
|
|
92
86
|
|
|
93
87
|
init(item: Binding<Item?>,
|
|
@@ -113,9 +107,6 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
|
|
|
113
107
|
self.backgroundColor = backgroundColor
|
|
114
108
|
self.dismissCallback = dismissCallback
|
|
115
109
|
self.view = view
|
|
116
|
-
|
|
117
|
-
self.isPresentedRef = ClassReference(self.$isPresented)
|
|
118
|
-
self.itemRef = ClassReference(self.$item)
|
|
119
110
|
}
|
|
120
111
|
|
|
121
112
|
public func body(content: Content) -> some View {
|
|
@@ -214,7 +205,8 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
|
|
|
214
205
|
}
|
|
215
206
|
|
|
216
207
|
func onAnimationCompleted() -> () {
|
|
217
|
-
|
|
208
|
+
// only proceed on hiding, once: both the offset animation and the fallback timer land here
|
|
209
|
+
if shouldShowContent || !showContent {
|
|
218
210
|
return
|
|
219
211
|
}
|
|
220
212
|
showContent = false // unload popup body after hiding animation is done
|
|
@@ -229,12 +221,14 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
|
|
|
229
221
|
if let autohideIn = autohideIn {
|
|
230
222
|
dispatchWorkHolder.work?.cancel()
|
|
231
223
|
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
dismissSource = .autohide
|
|
224
|
+
// bindings only: capturing the struct would retain the work holder in a cycle
|
|
225
|
+
let isPresented = $isPresented
|
|
226
|
+
let item = $item
|
|
227
|
+
let dismissSource = $dismissSource
|
|
228
|
+
dispatchWorkHolder.work = DispatchWorkItem(block: {
|
|
229
|
+
dismissSource.wrappedValue = .autohide
|
|
230
|
+
isPresented.wrappedValue = false
|
|
231
|
+
item.wrappedValue = nil
|
|
238
232
|
})
|
|
239
233
|
if sheetPresented, let work = dispatchWorkHolder.work {
|
|
240
234
|
DispatchQueue.main.asyncAfter(deadline: .now() + autohideIn, execute: work)
|
|
@@ -239,7 +239,7 @@ public struct Popup<Item: Equatable, PopupContent: View>: ViewModifier {
|
|
|
239
239
|
.onAnimationCompleted(for: currentOffset) {
|
|
240
240
|
animationCompletedCallback()
|
|
241
241
|
}
|
|
242
|
-
.animation(animation)
|
|
242
|
+
.animation(animation, value: currentOffset)
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
#if !os(tvOS)
|
|
@@ -112,7 +112,9 @@ private struct ProgressInfoHorizontal: View {
|
|
|
112
112
|
|
|
113
113
|
var body: some View {
|
|
114
114
|
HStack(alignment: .top, spacing: 0) {
|
|
115
|
-
|
|
115
|
+
// Keyed by position: `ProgressInfoItem.id` is a fresh UUID per init, so it churns
|
|
116
|
+
// identity whenever the caller rebuilds `steps`.
|
|
117
|
+
ForEach(Array(steps.enumerated()), id: \.offset) { index, item in
|
|
116
118
|
let isFirst = index == 0
|
|
117
119
|
let isLast = index == steps.count - 1
|
|
118
120
|
|
|
@@ -207,7 +209,7 @@ private struct ProgressInfoVertical: View {
|
|
|
207
209
|
|
|
208
210
|
var body: some View {
|
|
209
211
|
VStack(alignment: .leading, spacing: 0) {
|
|
210
|
-
ForEach(Array(steps.enumerated()), id: \.
|
|
212
|
+
ForEach(Array(steps.enumerated()), id: \.offset) { index, item in
|
|
211
213
|
let isLast = index == steps.count - 1
|
|
212
214
|
|
|
213
215
|
HStack(alignment: .top, spacing: 0) {
|
package/ios/Radio/Radio.swift
CHANGED
|
@@ -36,8 +36,9 @@ public struct Radio: View {
|
|
|
36
36
|
public var body: some View {
|
|
37
37
|
SwiftUI.Button(action: onTap) {
|
|
38
38
|
HStack(spacing: 0) {
|
|
39
|
+
// strokeBorder keeps the ring inside 20pt, like Compose `border`.
|
|
39
40
|
RoundedRectangle(cornerRadius: Radius.M)
|
|
40
|
-
.
|
|
41
|
+
.strokeBorder(borderColor, lineWidth: borderWidth)
|
|
41
42
|
.frame(width: 20, height: 20)
|
|
42
43
|
.padding(.trailing, label.isEmpty ? 0 : Spacing.XS)
|
|
43
44
|
|
package/ios/Rating/Rating.swift
CHANGED
|
@@ -35,7 +35,7 @@ public struct Rating: View {
|
|
|
35
35
|
|
|
36
36
|
public var body: some View {
|
|
37
37
|
HStack(spacing: 2) {
|
|
38
|
-
ForEach(0..<numOfStars, id: \.self) { index in
|
|
38
|
+
ForEach(0..<max(numOfStars, 0), id: \.self) { index in
|
|
39
39
|
star(at: index)
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -59,7 +59,14 @@ public struct Rating: View {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// Kotlin's toInt() maps NaN to 0 and saturates; Swift's Int(_:) traps on both.
|
|
63
|
+
private var safeRating: Float {
|
|
64
|
+
guard rating.isFinite else { return 0 }
|
|
65
|
+
return min(max(rating, -Float(Int32.max)), Float(Int32.max))
|
|
66
|
+
}
|
|
67
|
+
|
|
62
68
|
private func starIcon(at index: Int) -> String {
|
|
69
|
+
let rating = safeRating
|
|
63
70
|
let intRating = Int(rating)
|
|
64
71
|
if index < intRating {
|
|
65
72
|
return "reaction_star_full"
|
|
@@ -71,6 +78,7 @@ public struct Rating: View {
|
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
private func starColor(at index: Int) -> Color {
|
|
81
|
+
let rating = safeRating
|
|
74
82
|
let intRating = Int(rating)
|
|
75
83
|
if index < intRating {
|
|
76
84
|
return Colors.yellow03
|