@momo-kits/native-kits 0.300.3-debug → 0.300.4-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 +51 -7
- package/README.md +2 -2
- package/compose/build.gradle.kts +10 -2
- package/compose/build.gradle.kts.backup +8 -0
- package/compose/compose.podspec +1 -3
- package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +4 -3
- 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/application/Context.kt +0 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +351 -111
- 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/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 +55 -22
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -1
- 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/const/HostFont.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +20 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
- 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/Navigation.kt +10 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +6 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +6 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +197 -60
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +132 -83
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +54 -62
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +122 -24
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +34 -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 +113 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +37 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +13 -0
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +44 -0
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +15 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +3 -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 +138 -0
- package/gradle/libs.versions.toml +3 -14
- package/gradle/wrapper/gradle-wrapper.properties +0 -2
- package/gradle.properties +3 -5
- package/ios/Application/ApplicationEnvironment.swift +24 -6
- package/ios/Application/DesignSystemKits.swift +100 -0
- package/ios/Application/DesignSystemKitsPayload.swift +68 -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 +8 -2
- 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 +171 -148
- package/ios/Input/InputPhoneNumber.swift +10 -9
- package/ios/Input/InputSearch.swift +107 -78
- package/ios/Input/InputTextArea.swift +133 -148
- package/ios/InputDropDown/InputDropDown.swift +10 -7
- package/ios/InputMoney/InputMoney.swift +32 -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 +415 -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 +138 -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 +7 -3
- 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 +109 -55
- package/ios/Title/Title.swift +6 -17
- package/ios/Tooltip/Tooltip.swift +29 -14
- package/ios/Typography/FontScale.swift +17 -14
- package/ios/Typography/Text.swift +156 -20
- package/ios/Typography/Typography.swift +2 -2
- package/ios/Uploader/Uploader.swift +3 -1
- package/ios/native-kits.podspec +2 -3
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -3
- 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/publish.sh +19 -2
- package/scripts/gen-ios-icons.mjs +36 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
- 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/Typography/FontScaleStore.swift +0 -58
- package/ios/Typography/KitDesignSystem.swift +0 -17
- /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
|
@@ -28,7 +28,7 @@ public enum IconSize {
|
|
|
28
28
|
|
|
29
29
|
// MARK: - IconShape
|
|
30
30
|
|
|
31
|
-
public enum IconShape {
|
|
31
|
+
public enum IconShape: Sendable {
|
|
32
32
|
case circle
|
|
33
33
|
case square
|
|
34
34
|
}
|
|
@@ -68,8 +68,7 @@ public struct IconButton: View {
|
|
|
68
68
|
public var body: some View {
|
|
69
69
|
let bg = backgroundColor
|
|
70
70
|
let fg = contentColor
|
|
71
|
-
|
|
72
|
-
let border: Color? = type == .outline ? (color ?? theme.colors.primary) : borderColor
|
|
71
|
+
let border = borderColor
|
|
73
72
|
let borderWidth = type.borderWidth
|
|
74
73
|
|
|
75
74
|
SwiftUI.Button(action: {
|
|
@@ -82,9 +81,11 @@ public struct IconButton: View {
|
|
|
82
81
|
.background(bg)
|
|
83
82
|
.clipShape(IconButtonShape(shape))
|
|
84
83
|
.overlay(
|
|
84
|
+
// Inside the bounds, like Compose `border`.
|
|
85
85
|
IconButtonShape(shape)
|
|
86
|
-
.
|
|
86
|
+
.strokeBorder(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
|
|
87
87
|
)
|
|
88
|
+
.contentShape(IconButtonShape(shape))
|
|
88
89
|
}
|
|
89
90
|
.buttonStyle(.plain)
|
|
90
91
|
.disabled(!isEnabled)
|
|
@@ -122,8 +123,9 @@ public struct IconButton: View {
|
|
|
122
123
|
/// Type-erased shape switch (`Circle` vs a fixed `Radius.S` rounded rect) — mirrors
|
|
123
124
|
/// Compose's `IconShape.toShape()`. `AnyShape` needs iOS 17+, so this hand-rolls the same
|
|
124
125
|
/// erasure at the deployment target's iOS 15 floor.
|
|
125
|
-
private struct IconButtonShape:
|
|
126
|
+
private struct IconButtonShape: InsettableShape {
|
|
126
127
|
let shape: IconShape
|
|
128
|
+
var insetAmount: CGFloat = 0
|
|
127
129
|
|
|
128
130
|
init(_ shape: IconShape) {
|
|
129
131
|
self.shape = shape
|
|
@@ -132,9 +134,15 @@ private struct IconButtonShape: Shape {
|
|
|
132
134
|
func path(in rect: CGRect) -> Path {
|
|
133
135
|
switch shape {
|
|
134
136
|
case .circle:
|
|
135
|
-
return Circle().path(in: rect)
|
|
137
|
+
return Circle().inset(by: insetAmount).path(in: rect)
|
|
136
138
|
case .square:
|
|
137
|
-
return RoundedRectangle(cornerRadius: Radius.S).path(in: rect)
|
|
139
|
+
return RoundedRectangle(cornerRadius: Radius.S).inset(by: insetAmount).path(in: rect)
|
|
138
140
|
}
|
|
139
141
|
}
|
|
142
|
+
|
|
143
|
+
func inset(by amount: CGFloat) -> IconButtonShape {
|
|
144
|
+
var shape = self
|
|
145
|
+
shape.insetAmount += amount
|
|
146
|
+
return shape
|
|
147
|
+
}
|
|
140
148
|
}
|
package/ios/Image/Image.swift
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import Foundation
|
|
4
4
|
import SDWebImageSwiftUI
|
|
5
|
-
import SkeletonUI
|
|
6
5
|
import SwiftUI
|
|
7
6
|
|
|
8
7
|
public enum ImageState {
|
|
@@ -39,26 +38,23 @@ public struct ImageView: View {
|
|
|
39
38
|
.resizable()
|
|
40
39
|
.renderingMode(color != nil ? .template : .original)
|
|
41
40
|
.placeholder {
|
|
42
|
-
VStack(alignment: .center
|
|
41
|
+
VStack(alignment: .center) {
|
|
43
42
|
if error {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
.renderingMode(.template)
|
|
47
|
-
.foregroundColor(theme.colors.text.disable)
|
|
48
|
-
.frame(width: 24, height: 24)
|
|
49
|
-
} else if placeholder != nil {
|
|
43
|
+
Icon(source: "media_fail", color: theme.colors.text.disable)
|
|
44
|
+
} else if let placeholder {
|
|
50
45
|
placeholder
|
|
46
|
+
} else if loading {
|
|
47
|
+
Skeleton()
|
|
51
48
|
}
|
|
52
|
-
}
|
|
49
|
+
}
|
|
53
50
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
54
|
-
.skeleton(with: loading && !error && placeholder == nil)
|
|
55
|
-
.shape(type: .rectangle)
|
|
56
51
|
}
|
|
57
52
|
.scaledToFit()
|
|
58
53
|
.transition(.fade)
|
|
59
54
|
.aspectRatio(aspectRatio, contentMode: contentMode)
|
|
60
55
|
.foregroundColor(color)
|
|
61
56
|
.onAppear { onStateChanged?(.loading) }
|
|
57
|
+
.onChange(of: url) { _ in error = false }
|
|
62
58
|
}
|
|
63
59
|
|
|
64
60
|
// MARK: Internal
|
|
@@ -73,15 +69,3 @@ public struct ImageView: View {
|
|
|
73
69
|
@State var error: Bool = false
|
|
74
70
|
var url: String
|
|
75
71
|
}
|
|
76
|
-
|
|
77
|
-
private struct ColorMultiplyModifier: ViewModifier {
|
|
78
|
-
let color: Color?
|
|
79
|
-
|
|
80
|
-
func body(content: Content) -> some View {
|
|
81
|
-
if let color {
|
|
82
|
-
content.colorMultiply(color)
|
|
83
|
-
} else {
|
|
84
|
-
content
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -19,6 +19,7 @@ public struct Information: View {
|
|
|
19
19
|
private let showIconClose: Bool
|
|
20
20
|
private let onPressClose: (() -> Void)?
|
|
21
21
|
private let highlightText: [String]
|
|
22
|
+
private let accessibilityLabel: String?
|
|
22
23
|
|
|
23
24
|
public init(
|
|
24
25
|
title: String? = nil,
|
|
@@ -30,7 +31,8 @@ public struct Information: View {
|
|
|
30
31
|
action: String? = nil,
|
|
31
32
|
showIconClose: Bool = true,
|
|
32
33
|
onPressClose: (() -> Void)? = nil,
|
|
33
|
-
highlightText: [String] = []
|
|
34
|
+
highlightText: [String] = [],
|
|
35
|
+
accessibilityLabel: String? = nil
|
|
34
36
|
) {
|
|
35
37
|
self.title = title
|
|
36
38
|
self.description = description
|
|
@@ -42,6 +44,11 @@ public struct Information: View {
|
|
|
42
44
|
self.showIconClose = showIconClose
|
|
43
45
|
self.onPressClose = onPressClose
|
|
44
46
|
self.highlightText = highlightText
|
|
47
|
+
self.accessibilityLabel = accessibilityLabel
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private var automationId: String {
|
|
51
|
+
accessibilityLabel ?? "Information\(title.map { "/\($0)" } ?? "")"
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
private var borderColor: Color {
|
|
@@ -80,20 +87,23 @@ public struct Information: View {
|
|
|
80
87
|
VStack(alignment: .leading, spacing: 0) {
|
|
81
88
|
HStack(alignment: .top, spacing: 0) {
|
|
82
89
|
if let image {
|
|
83
|
-
ImageView(image)
|
|
90
|
+
ImageView(image, contentMode: .fill)
|
|
84
91
|
.frame(width: 40, height: 40)
|
|
85
92
|
.clipShape(RoundedRectangle(cornerRadius: Radius.S))
|
|
93
|
+
.accessibilityIdentifier("\(automationId)|img")
|
|
86
94
|
.padding(.trailing, Spacing.S)
|
|
87
95
|
} else if showIcon {
|
|
88
96
|
Icon(source: iconSource, size: 16, color: accentColor)
|
|
97
|
+
.accessibilityIdentifier("\(automationId)|icon")
|
|
89
98
|
.padding(.trailing, Spacing.S)
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
VStack(alignment: .leading, spacing: 0) {
|
|
93
102
|
if let title {
|
|
94
|
-
MomoText(title, typography: .labelDefaultMedium)
|
|
103
|
+
MomoText(title, typography: .labelDefaultMedium, accessibilityId: "\(automationId)|title-text")
|
|
95
104
|
}
|
|
96
105
|
MomoText(description, typography: .descriptionDefaultRegular, highlightText: highlightText)
|
|
106
|
+
.accessibilityIdentifier("\(automationId)|description-text")
|
|
97
107
|
}
|
|
98
108
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
99
109
|
|
|
@@ -101,15 +111,21 @@ public struct Information: View {
|
|
|
101
111
|
Icon(source: "navigation_close", size: 16, color: theme.colors.text.default)
|
|
102
112
|
.contentShape(Rectangle())
|
|
103
113
|
.onTapGesture(perform: onPressClose)
|
|
114
|
+
.accessibilityAddTraits(.isButton)
|
|
115
|
+
.accessibilityIdentifier("\(automationId)|close-icon-touch")
|
|
104
116
|
}
|
|
105
117
|
}
|
|
106
118
|
|
|
107
119
|
if let action, let onPressAction {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
HStack(spacing: 0) {
|
|
121
|
+
MomoText(action, typography: .actionXsBold, color: accentColor, accessibilityId: "\(automationId)|cta-text")
|
|
122
|
+
}
|
|
123
|
+
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
124
|
+
.contentShape(Rectangle())
|
|
125
|
+
.onTapGesture(perform: onPressAction)
|
|
126
|
+
.accessibilityElement(children: .contain)
|
|
127
|
+
.accessibilityIdentifier("\(automationId)|cta-touch")
|
|
128
|
+
.padding(.top, Spacing.S)
|
|
113
129
|
}
|
|
114
130
|
}
|
|
115
131
|
.padding(Spacing.M)
|
|
@@ -119,8 +135,10 @@ public struct Information: View {
|
|
|
119
135
|
)
|
|
120
136
|
.overlay(
|
|
121
137
|
RoundedRectangle(cornerRadius: Radius.S)
|
|
122
|
-
.
|
|
138
|
+
.strokeBorder(borderColor, lineWidth: 0.5)
|
|
123
139
|
)
|
|
140
|
+
.accessibilityElement(children: .contain)
|
|
141
|
+
.accessibilityIdentifier(automationId)
|
|
124
142
|
}
|
|
125
143
|
}
|
|
126
144
|
|
package/ios/Input/Input.swift
CHANGED
|
@@ -37,8 +37,7 @@ public struct Input: View {
|
|
|
37
37
|
@State private var isFocused: Bool = false
|
|
38
38
|
@State private var isPasswordHidden: Bool = true
|
|
39
39
|
@FocusState private var isFieldFocused: Bool
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
|
|
42
41
|
public init(
|
|
43
42
|
text: Binding<String>,
|
|
44
43
|
placeholder: String = "",
|
|
@@ -112,142 +111,115 @@ public struct Input: View {
|
|
|
112
111
|
}
|
|
113
112
|
)
|
|
114
113
|
|
|
115
|
-
VStack(alignment: .leading, spacing:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
MomoText("*", typography: .labelSMedium, color: theme.colors.error.primary)
|
|
123
|
-
}
|
|
124
|
-
if !floatingIcon.isEmpty {
|
|
125
|
-
Icon(source: floatingIcon, size: 16, color: getFloatingIconColor())
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
.padding(.horizontal, Spacing.S)
|
|
129
|
-
.background(
|
|
130
|
-
GeometryReader { geo in
|
|
131
|
-
Color.clear.onAppear { floatingLabelHeight = geo.size.height }
|
|
132
|
-
}
|
|
133
|
-
)
|
|
134
|
-
.background(theme.colors.background.surface)
|
|
135
|
-
// Kotlin centers the label within the (box-height-tall) decorationBox
|
|
136
|
-
// by default, then applies `offsetY = -size.values.height / 2` — net
|
|
137
|
-
// effect: the label ends up centered exactly on the box's top border
|
|
138
|
-
// (spanning -labelHeight/2 ... +labelHeight/2 around y=0). Our ZStack
|
|
139
|
-
// anchors top-leading instead, so reproducing that requires offsetting
|
|
140
|
-
// by half the label's OWN measured height, not half the box height.
|
|
141
|
-
.offset(x: Spacing.S, y: -floatingLabelHeight / 2)
|
|
142
|
-
.zIndex(10)
|
|
114
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
115
|
+
// Input container
|
|
116
|
+
HStack(alignment: .center, spacing: 0) {
|
|
117
|
+
// Leading icon
|
|
118
|
+
if !leadingIcon.isEmpty {
|
|
119
|
+
Icon(source: leadingIcon, size: size == .small ? 24 : 32, color: leadingIconColor ?? theme.colors.text.hint)
|
|
120
|
+
.padding(.trailing, Spacing.M)
|
|
143
121
|
}
|
|
144
|
-
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
.
|
|
122
|
+
|
|
123
|
+
// Text input field
|
|
124
|
+
ZStack(alignment: .leading) {
|
|
125
|
+
if text.isEmpty {
|
|
126
|
+
MomoText(
|
|
127
|
+
placeholder + (required && floatingValue.isEmpty ? " *" : ""),
|
|
128
|
+
typography: fontWeight == .bold ? .actionSBold : .bodyDefaultRegular,
|
|
129
|
+
color: getPlaceholderColor()
|
|
130
|
+
)
|
|
151
131
|
}
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
fontSize: fontScale(14),
|
|
168
|
-
fontWeight: fontWeight == .bold ? .bold : .regular,
|
|
169
|
-
textColor: UIColor(getTextColor()),
|
|
170
|
-
cursorColor: UIColor(theme.colors.primary),
|
|
171
|
-
isDisabled: disabled || readOnly,
|
|
172
|
-
maxLength: maxLength,
|
|
173
|
-
autofocus: autofocus,
|
|
174
|
-
onFocusChange: { focused in
|
|
175
|
-
handleFocusChange(focused)
|
|
176
|
-
},
|
|
177
|
-
onChangeText: onChangeText
|
|
178
|
-
)
|
|
179
|
-
} else {
|
|
180
|
-
TextField("", text: textBinding, onEditingChanged: { focused in
|
|
132
|
+
|
|
133
|
+
// One UITextField for both states: swapping views on the eye toggle would drop focus.
|
|
134
|
+
if secureTextEntry {
|
|
135
|
+
SecureInputField(
|
|
136
|
+
text: $text,
|
|
137
|
+
isSecure: isPasswordHidden,
|
|
138
|
+
keyboardType: keyboardType,
|
|
139
|
+
fontSize: fontScale(14),
|
|
140
|
+
fontWeight: fontWeight == .bold ? .bold : .regular,
|
|
141
|
+
textColor: UIColor(getTextColor()),
|
|
142
|
+
cursorColor: UIColor(theme.colors.primary),
|
|
143
|
+
isDisabled: disabled || readOnly,
|
|
144
|
+
maxLength: maxLength,
|
|
145
|
+
autofocus: autofocus,
|
|
146
|
+
onFocusChange: { focused in
|
|
181
147
|
handleFocusChange(focused)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
.id(keyboardType.rawValue)
|
|
148
|
+
},
|
|
149
|
+
onChangeText: onChangeText
|
|
150
|
+
)
|
|
151
|
+
} else {
|
|
152
|
+
TextField("", text: textBinding, onEditingChanged: { focused in
|
|
153
|
+
handleFocusChange(focused)
|
|
154
|
+
})
|
|
155
|
+
.keyboardType(keyboardType)
|
|
156
|
+
.kitFont(fontWeight == .bold ? .actionSBold : .bodyDefaultRegular)
|
|
157
|
+
.ignoreBoldTextSetting()
|
|
158
|
+
.foregroundColor(getTextColor())
|
|
159
|
+
.disabled(disabled || readOnly)
|
|
160
|
+
.applyCursorColor(theme.colors.primary)
|
|
161
|
+
.focused($isFieldFocused)
|
|
162
|
+
.onAppear {
|
|
163
|
+
if autofocus { isFieldFocused = true }
|
|
199
164
|
}
|
|
165
|
+
// SwiftUI reads a TextField's UIKit input traits only when it
|
|
166
|
+
// builds the backing UITextField, so a later `keyboardType`
|
|
167
|
+
// change is silently dropped. The secure path can call
|
|
168
|
+
// `reloadInputViews()` in `updateUIView`; here the only lever
|
|
169
|
+
// is to force a rebuild.
|
|
170
|
+
.id(keyboardType.rawValue)
|
|
200
171
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Clear button (only show when focused and has text)
|
|
175
|
+
if isFocused && !text.isEmpty {
|
|
176
|
+
SwiftUI.Button(action: {
|
|
177
|
+
text = ""
|
|
178
|
+
onChangeText?("")
|
|
179
|
+
}) {
|
|
180
|
+
Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
|
|
181
|
+
.padding(.horizontal, Spacing.XS)
|
|
211
182
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
183
|
+
.accessibilityIdentifier("ic_clear")
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Loading indicator
|
|
187
|
+
if loading {
|
|
188
|
+
ActivityIndicator(isAnimating: .constant(true), style: .medium, color: getRightIconColor())
|
|
189
|
+
.frame(width: 16, height: 16)
|
|
190
|
+
.padding(.leading, Spacing.S)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Right icon: password toggle once there is text, otherwise the custom icon (as Compose)
|
|
194
|
+
if secureTextEntry && !text.isEmpty {
|
|
195
|
+
SwiftUI.Button(action: togglePasswordVisibility) {
|
|
196
|
+
Icon(
|
|
197
|
+
source: isPasswordHidden ? "24_security_eye_off" : "24_security_eye_open",
|
|
198
|
+
size: 24,
|
|
199
|
+
color: getRightIconColor()
|
|
200
|
+
)
|
|
201
|
+
.padding(.leading, Spacing.S)
|
|
218
202
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
Icon(
|
|
225
|
-
source: isPasswordHidden ? "24_security_eye_off" : "24_security_eye_open",
|
|
226
|
-
size: 24,
|
|
227
|
-
color: getRightIconColor()
|
|
228
|
-
)
|
|
229
|
-
.padding(.leading, Spacing.S).accessibility(identifier: "ic_show_hide")
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
} else if !rightIcon.isEmpty {
|
|
233
|
-
SwiftUI.Button(action: { onRightIconPressed?() }) {
|
|
234
|
-
Icon(source: rightIcon, size: 24, color: getRightIconColor())
|
|
235
|
-
.padding(.leading, Spacing.S)
|
|
236
|
-
}
|
|
203
|
+
.accessibilityIdentifier("ic_show_hide")
|
|
204
|
+
} else if !rightIcon.isEmpty {
|
|
205
|
+
SwiftUI.Button(action: { onRightIconPressed?() }) {
|
|
206
|
+
Icon(source: rightIcon, size: 24, color: getRightIconColor())
|
|
207
|
+
.padding(.leading, Spacing.S)
|
|
237
208
|
}
|
|
238
209
|
}
|
|
239
|
-
.padding(.horizontal, Spacing.M)
|
|
240
|
-
.frame(height: fontScale(size == .small ? 48 : 56))
|
|
241
|
-
.background(
|
|
242
|
-
RoundedRectangle(cornerRadius: Radius.S)
|
|
243
|
-
.fill(theme.colors.background.surface)
|
|
244
|
-
)
|
|
245
|
-
.overlay(
|
|
246
|
-
RoundedRectangle(cornerRadius: Radius.S)
|
|
247
|
-
.stroke(borderColor(), lineWidth: 1)
|
|
248
|
-
)
|
|
249
210
|
}
|
|
250
|
-
|
|
211
|
+
.padding(.horizontal, Spacing.M)
|
|
212
|
+
.frame(height: fontScale(size == .small ? 48 : 56))
|
|
213
|
+
.background(
|
|
214
|
+
RoundedRectangle(cornerRadius: Radius.S)
|
|
215
|
+
.fill(theme.colors.background.surface)
|
|
216
|
+
)
|
|
217
|
+
.overlay(
|
|
218
|
+
RoundedRectangle(cornerRadius: Radius.S)
|
|
219
|
+
.stroke(borderColor(), lineWidth: 1)
|
|
220
|
+
)
|
|
221
|
+
.overlay(alignment: .topLeading) { floatingLabel }
|
|
222
|
+
|
|
251
223
|
// Error or hint
|
|
252
224
|
ErrorView(
|
|
253
225
|
errorMessage: error,
|
|
@@ -263,10 +235,33 @@ public struct Input: View {
|
|
|
263
235
|
}
|
|
264
236
|
}
|
|
265
237
|
}
|
|
266
|
-
|
|
238
|
+
|
|
239
|
+
/// Centered on the box's top border, like Compose's `FloatingLabel` (offset by -height / 2).
|
|
240
|
+
@ViewBuilder
|
|
241
|
+
private var floatingLabel: some View {
|
|
242
|
+
if !floatingValue.isEmpty || !floatingIcon.isEmpty {
|
|
243
|
+
HStack(spacing: 0) {
|
|
244
|
+
MomoText(floatingValue, typography: .labelSMedium, color: getFloatingColor())
|
|
245
|
+
if required {
|
|
246
|
+
MomoText("*", typography: .labelSMedium, color: theme.colors.error.primary)
|
|
247
|
+
}
|
|
248
|
+
if !floatingIcon.isEmpty {
|
|
249
|
+
Icon(source: floatingIcon, size: 16, color: getFloatingIconColor())
|
|
250
|
+
.padding(.leading, Spacing.XS)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
.padding(.horizontal, Spacing.S)
|
|
254
|
+
.background(theme.colors.background.surface)
|
|
255
|
+
.alignmentGuide(.top) { $0[VerticalAlignment.center] }
|
|
256
|
+
.offset(x: Spacing.S)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
267
260
|
// MARK: - Helpers
|
|
268
|
-
|
|
261
|
+
|
|
269
262
|
private func handleFocusChange(_ focused: Bool) {
|
|
263
|
+
// The keyboard-hide fallback can report a blur the field already reported.
|
|
264
|
+
guard focused != isFocused else { return }
|
|
270
265
|
isFocused = focused
|
|
271
266
|
if focused {
|
|
272
267
|
onFocus?()
|
|
@@ -316,10 +311,6 @@ public struct Input: View {
|
|
|
316
311
|
return disabled ? theme.colors.text.disable : (floatingIconColor ?? theme.colors.text.default)
|
|
317
312
|
}
|
|
318
313
|
|
|
319
|
-
private func getLeadingIconColor() -> Color {
|
|
320
|
-
return disabled ? theme.colors.text.disable : (leadingIconColor ?? theme.colors.text.hint)
|
|
321
|
-
}
|
|
322
|
-
|
|
323
314
|
private func getRightIconColor() -> Color {
|
|
324
315
|
return disabled ? theme.colors.text.disable : (rightIconColor ?? theme.colors.text.default)
|
|
325
316
|
}
|
|
@@ -329,6 +320,7 @@ public struct Input: View {
|
|
|
329
320
|
// MARK: - Secure Input Field (UIViewRepresentable)
|
|
330
321
|
private struct SecureInputField: UIViewRepresentable {
|
|
331
322
|
@Binding var text: String
|
|
323
|
+
var isSecure: Bool
|
|
332
324
|
var keyboardType: UIKeyboardType
|
|
333
325
|
var fontSize: CGFloat
|
|
334
326
|
var fontWeight: UIFont.Weight
|
|
@@ -346,10 +338,19 @@ private struct SecureInputField: UIViewRepresentable {
|
|
|
346
338
|
|
|
347
339
|
func makeUIView(context: Context) -> UITextField {
|
|
348
340
|
let textField = UITextField()
|
|
349
|
-
textField.isSecureTextEntry =
|
|
341
|
+
textField.isSecureTextEntry = isSecure
|
|
342
|
+
// Stays a password field while revealed: keep it out of autocorrect / QuickType learning.
|
|
343
|
+
textField.autocorrectionType = .no
|
|
344
|
+
textField.autocapitalizationType = .none
|
|
345
|
+
textField.spellCheckingType = .no
|
|
350
346
|
textField.delegate = context.coordinator
|
|
351
347
|
textField.keyboardType = keyboardType
|
|
352
|
-
textField.font =
|
|
348
|
+
textField.font = momoUIFont(
|
|
349
|
+
size: fontSize,
|
|
350
|
+
weight: fontWeight,
|
|
351
|
+
family: context.environment.appTheme.font,
|
|
352
|
+
isWhiteList: context.environment.designSystemKitsConfigs.isWhiteList
|
|
353
|
+
)
|
|
353
354
|
textField.textColor = textColor
|
|
354
355
|
textField.isEnabled = !isDisabled
|
|
355
356
|
textField.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
@@ -374,6 +375,15 @@ private struct SecureInputField: UIViewRepresentable {
|
|
|
374
375
|
if textField.text != text {
|
|
375
376
|
textField.text = text
|
|
376
377
|
}
|
|
378
|
+
let font = momoUIFont(
|
|
379
|
+
size: fontSize,
|
|
380
|
+
weight: fontWeight,
|
|
381
|
+
family: context.environment.appTheme.font,
|
|
382
|
+
isWhiteList: context.environment.designSystemKitsConfigs.isWhiteList
|
|
383
|
+
)
|
|
384
|
+
if textField.font != font {
|
|
385
|
+
textField.font = font
|
|
386
|
+
}
|
|
377
387
|
textField.isEnabled = !isDisabled
|
|
378
388
|
textField.textColor = textColor
|
|
379
389
|
textField.tintColor = cursorColor
|
|
@@ -381,6 +391,12 @@ private struct SecureInputField: UIViewRepresentable {
|
|
|
381
391
|
textField.keyboardType = keyboardType
|
|
382
392
|
textField.reloadInputViews()
|
|
383
393
|
}
|
|
394
|
+
if textField.isSecureTextEntry != isSecure {
|
|
395
|
+
textField.isSecureTextEntry = isSecure
|
|
396
|
+
if textField.isFirstResponder {
|
|
397
|
+
context.coordinator.reinsertText(in: textField)
|
|
398
|
+
}
|
|
399
|
+
}
|
|
384
400
|
}
|
|
385
401
|
|
|
386
402
|
class Coordinator: NSObject, UITextFieldDelegate {
|
|
@@ -391,19 +407,21 @@ private struct SecureInputField: UIViewRepresentable {
|
|
|
391
407
|
self.parent = parent
|
|
392
408
|
}
|
|
393
409
|
|
|
394
|
-
|
|
410
|
+
// When a secure text field regains focus (or turns secure while editing),
|
|
411
|
+
// iOS marks the existing text as "committed" and the next keystroke would
|
|
412
|
+
// replace ALL of it. Re-inserting via insertText() puts it back into an
|
|
413
|
+
// "active editing" state (and moves the caret to the end).
|
|
414
|
+
func reinsertText(in textField: UITextField) {
|
|
395
415
|
let existingText = textField.text ?? ""
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
isResettingText = false
|
|
406
|
-
}
|
|
416
|
+
guard !existingText.isEmpty else { return }
|
|
417
|
+
isResettingText = true
|
|
418
|
+
textField.text = ""
|
|
419
|
+
textField.insertText(existingText)
|
|
420
|
+
isResettingText = false
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
func textFieldDidBeginEditing(_ textField: UITextField) {
|
|
424
|
+
reinsertText(in: textField)
|
|
407
425
|
parent.onFocusChange(true)
|
|
408
426
|
}
|
|
409
427
|
|
|
@@ -460,12 +478,17 @@ private extension View {
|
|
|
460
478
|
struct ActivityIndicator: UIViewRepresentable {
|
|
461
479
|
@Binding var isAnimating: Bool
|
|
462
480
|
let style: UIActivityIndicatorView.Style
|
|
481
|
+
/// Compose tints the spinner with the right-icon color; nil keeps the system gray.
|
|
482
|
+
var color: Color? = nil
|
|
463
483
|
|
|
464
484
|
func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
|
|
465
485
|
return UIActivityIndicatorView(style: style)
|
|
466
486
|
}
|
|
467
487
|
|
|
468
488
|
func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
|
|
489
|
+
if let color {
|
|
490
|
+
uiView.color = UIColor(color)
|
|
491
|
+
}
|
|
469
492
|
isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
|
|
470
493
|
}
|
|
471
494
|
}
|