@momo-kits/native-kits 0.152.4-beta.1 → 0.152.4-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CODE_OF_CONDUCT.md +133 -0
  2. package/CONTRIBUTING.md +114 -0
  3. package/LICENSE +20 -0
  4. package/README.md +7 -0
  5. package/build.gradle.kts +32 -0
  6. package/compose/MoMoComposeKits.podspec +54 -0
  7. package/compose/build.gradle.kts +149 -0
  8. package/compose/src/androidMain/AndroidManifest.xml +2 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +105 -0
  10. package/compose/src/commonMain/composeResources/files/lottie_circle_loader.json +1 -0
  11. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  12. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  13. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  14. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  20. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  21. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  22. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  23. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  24. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +57 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +201 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +222 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +48 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +306 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +715 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +214 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +236 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +77 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +27 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +334 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +345 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +90 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +131 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +58 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +143 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +179 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +111 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +384 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +160 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +234 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +223 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +232 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +236 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +228 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +364 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +50 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +34 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +85 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +33 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +338 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +95 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +64 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +89 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +91 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +84 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +208 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +172 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +199 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +237 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +191 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +13 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +191 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +258 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +59 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +68 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +49 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +232 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +111 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +94 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +159 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +232 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScaleSizeScope.kt +17 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +459 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +169 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +216 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +180 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +251 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +80 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +306 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +31 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +385 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +38 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +62 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +88 -0
  108. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +144 -0
  109. package/gradle.properties +19 -0
  110. package/gradlew +240 -0
  111. package/gradlew.bat +91 -0
  112. package/ios/Application/ApplicationEnvironment.swift +50 -0
  113. package/ios/Application/Components.swift +263 -0
  114. package/ios/Application/ComposeApi.swift +22 -0
  115. package/ios/Application/FloatingButton.swift +172 -0
  116. package/ios/Application/HeaderRight.swift +271 -0
  117. package/ios/Application/Screen.swift +249 -0
  118. package/ios/Badge/Badge.swift +85 -0
  119. package/ios/Badge/BadgeDot.swift +31 -0
  120. package/ios/Badge/BadgeRibbon.swift +174 -0
  121. package/ios/Button/Button.swift +211 -0
  122. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +126 -0
  123. package/ios/Checkbox/Checkbox.swift +81 -0
  124. package/ios/Chip/Chip.swift +96 -0
  125. package/ios/Colors+Radius+Spacing/Colors.swift +172 -0
  126. package/ios/Colors+Radius+Spacing/Radius.swift +22 -0
  127. package/ios/Colors+Radius+Spacing/Spacing.swift +12 -0
  128. package/ios/Extensions/Color++.swift +25 -0
  129. package/ios/Icon/Icon.swift +51 -0
  130. package/ios/Image/Image.swift +70 -0
  131. package/ios/Input/Input.swift +207 -0
  132. package/ios/Input/InputPhoneNumber.swift +176 -0
  133. package/ios/Input/InputSearch.swift +238 -0
  134. package/ios/Input/InputTextArea.swift +242 -0
  135. package/ios/Lottie/LottieView.swift +86 -0
  136. package/ios/OTPKeyboard/KeyboardButton.swift +41 -0
  137. package/ios/OTPKeyboard/OTPKeyboard.swift +145 -0
  138. package/ios/Popup/PopupDisplay.swift +284 -0
  139. package/ios/Popup/PopupInput.swift +96 -0
  140. package/ios/Popup/PopupPromotion.swift +73 -0
  141. package/ios/PopupView/FullscreenPopup.swift +251 -0
  142. package/ios/PopupView/Modifiers.swift +158 -0
  143. package/ios/PopupView/PopupView.swift +289 -0
  144. package/ios/PopupView/Utils++.swift +281 -0
  145. package/ios/ScrollIndicator/ScrollIndicator.swift +110 -0
  146. package/ios/Swipeable/SwipeCell.swift +278 -0
  147. package/ios/Swipeable/SwipeCellModel.swift +86 -0
  148. package/ios/Switch/Switch.swift +44 -0
  149. package/ios/Template/Logo/Logo.swift +75 -0
  150. package/ios/Template/TrustBanner/TrustBanner.swift +120 -0
  151. package/ios/Theme.md +18 -0
  152. package/ios/Typography/Text.swift +140 -0
  153. package/ios/Typography/Typography.swift +95 -0
  154. package/ios/native-kits.podspec +18 -0
  155. package/package.json +6 -7
  156. package/settings.gradle.kts +25 -0
  157. package/shared/build.gradle.kts +0 -74
@@ -0,0 +1,86 @@
1
+ //
2
+ // LottieView.swift
3
+ // MoMoPlatform
4
+ //
5
+ // Created by thanhdat on 16/01/2023.
6
+ // Copyright © 2023 Facebook. All rights reserved.
7
+ //
8
+
9
+ import SwiftUI
10
+ import Lottie
11
+
12
+ struct LottieView: UIViewRepresentable {
13
+ var name: String = ""
14
+ var url: String?
15
+ var loopMode: LottieLoopMode = .playOnce
16
+ var contentMode : UIView.ContentMode = .scaleAspectFit
17
+ var onDone: (() -> Void)?
18
+
19
+ // Add static cache for animations
20
+ private static var animationCache = NSCache<NSString, LottieAnimation>()
21
+
22
+ func makeUIView(context: UIViewRepresentableContext<LottieView>) -> UIView {
23
+ let view = UIView(frame: .zero)
24
+
25
+ // Configure Lottie with optimized settings
26
+ let configuration = LottieConfiguration(
27
+ renderingEngine: .coreAnimation,
28
+ decodingStrategy: .dictionaryBased
29
+ )
30
+
31
+ let animationView = LottieAnimationView(configuration: configuration)
32
+
33
+ if let url = url {
34
+ // Use cached animation if available
35
+ if let cachedAnimation = Self.animationCache.object(forKey: url as NSString) {
36
+ animationView.animation = cachedAnimation
37
+ setupAnimationView(animationView, view)
38
+ } else {
39
+ LottieAnimation.loadedFrom(url: URL(string: url)!, closure: { animation in
40
+ if let animation = animation {
41
+ Self.animationCache.setObject(animation, forKey: url as NSString)
42
+ animationView.animation = animation
43
+ setupAnimationView(animationView, view)
44
+ }
45
+ }, animationCache: nil)
46
+ }
47
+ } else {
48
+ // Use cached animation if available
49
+ if let cachedAnimation = Self.animationCache.object(forKey: name as NSString) {
50
+ animationView.animation = cachedAnimation
51
+ setupAnimationView(animationView, view)
52
+ } else if let animation = LottieAnimation.named(name) {
53
+ Self.animationCache.setObject(animation, forKey: name as NSString)
54
+ animationView.animation = animation
55
+ setupAnimationView(animationView, view)
56
+ }
57
+ }
58
+
59
+ return view
60
+ }
61
+
62
+ private func setupAnimationView(_ animationView: LottieAnimationView, _ containerView: UIView) {
63
+ animationView.contentMode = contentMode
64
+ animationView.loopMode = loopMode
65
+ animationView.backgroundBehavior = loopMode == .loop ? .pauseAndRestore : .pause
66
+
67
+ // Optimize rendering
68
+ animationView.shouldRasterizeWhenIdle = true
69
+
70
+ animationView.translatesAutoresizingMaskIntoConstraints = false
71
+ containerView.addSubview(animationView)
72
+
73
+ NSLayoutConstraint.activate([
74
+ animationView.widthAnchor.constraint(equalTo: containerView.widthAnchor),
75
+ animationView.heightAnchor.constraint(equalTo: containerView.heightAnchor)
76
+ ])
77
+
78
+ animationView.play { finished in
79
+ if finished {
80
+ onDone?()
81
+ }
82
+ }
83
+ }
84
+
85
+ func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LottieView>) {}
86
+ }
@@ -0,0 +1,41 @@
1
+ import Foundation
2
+ import SwiftUI
3
+
4
+ typealias SwiftButton = SwiftUI.Button
5
+
6
+ // MARK: - KeyboardButton
7
+
8
+ public struct KeyboardButton: View {
9
+ // MARK: Lifecycle
10
+
11
+ public init(_ key: String, action: @escaping (_ key: String)->Void, width: CGFloat = .infinity, height: CGFloat = 48, color: Color = Colors.card, textColor: Color = Colors.text) {
12
+ self.key = key
13
+ self.action = action
14
+ self.width = width
15
+ self.height = height
16
+ self.color = color
17
+ self.textColor = textColor
18
+ }
19
+
20
+ // MARK: Public
21
+
22
+ public var body: some View {
23
+ SwiftUI.Button(action: { self.action(key) }) {
24
+ HStack {
25
+ Text(key).font(.system(size: 24, weight: .bold))
26
+ }.frame(maxWidth: CGFloat(width), minHeight: height)
27
+ .background(color)
28
+ .foregroundColor(textColor)
29
+ .clipShape(RoundedRectangle(cornerRadius: 8))
30
+ }
31
+ }
32
+
33
+ // MARK: Internal
34
+
35
+ var key: String = ""
36
+ var action: (_ key: String)->Void
37
+ var width: CGFloat
38
+ var height: CGFloat
39
+ var color: Color
40
+ var textColor: Color
41
+ }
@@ -0,0 +1,145 @@
1
+
2
+ import Foundation
3
+ import SwiftUI
4
+
5
+ // MARK: - OTPKeyboard
6
+
7
+ public struct OTPKeyboard: View {
8
+ // MARK: Lifecycle
9
+
10
+ public init(value: Binding<String>, length: Int, content: AnyView? = nil, onChange: ((String) -> Void)? = nil, onClose: @escaping () -> Void) {
11
+ self._value = value
12
+ self.length = length
13
+ self.content = content
14
+ self.onChange = onChange
15
+ self.onClose = onClose
16
+ }
17
+
18
+ // MARK: Public
19
+
20
+ public var body: some View {
21
+ return VStack {
22
+ VStack {
23
+ HStack(){
24
+ SwiftUI.Button(action: onClose) {
25
+ Image(systemName: "xmark")
26
+ .foregroundColor(Colors.text)
27
+ }
28
+ Spacer()
29
+ }
30
+
31
+ ZStack(alignment: .trailing) {
32
+ ValueView(value, valueLength: length).padding(.trailing, 12)
33
+ SwiftUI.Button(action: onClear) {
34
+ Image(systemName: "xmark.circle.fill")
35
+ .resizable()
36
+ .frame(width: 12, height: 12)
37
+ .foregroundColor(Colors.text)
38
+ }.offset(x: -12)
39
+ }.frame(minWidth: 148, maxWidth: 180, minHeight: 40)
40
+ .background(Colors.card)
41
+ .clipShape(Capsule())
42
+ .padding(.bottom, Spacing.M)
43
+ }
44
+
45
+ content
46
+
47
+ HStack {
48
+ KeyboardButton("7", action: self.onPressKey)
49
+ KeyboardButton("8", action: self.onPressKey)
50
+ KeyboardButton("9", action: self.onPressKey)
51
+ }
52
+
53
+ HStack {
54
+ KeyboardButton("4", action: self.onPressKey)
55
+ KeyboardButton("5", action: self.onPressKey)
56
+ KeyboardButton("6", action: self.onPressKey)
57
+ }
58
+
59
+ HStack {
60
+ KeyboardButton("1", action: self.onPressKey)
61
+ KeyboardButton("2", action: self.onPressKey)
62
+ KeyboardButton("3", action: self.onPressKey)
63
+ }
64
+
65
+ HStack {
66
+ KeyboardButton("", action: self.onPressKey).disabled(true).opacity(0)
67
+ KeyboardButton("0", action: self.onPressKey)
68
+ SwiftUI.Button(action: onPressBack) {
69
+ HStack {
70
+ Image(
71
+ systemName: "delete.backward"
72
+ ).foregroundColor(Colors.text)
73
+ }
74
+ }.frame(maxWidth: .infinity, minHeight: 48)
75
+ .clipShape(RoundedRectangle(cornerRadius: 8))
76
+ }
77
+ }
78
+ .background(Colors.background)
79
+ .valueChanged(value: value) { otp in
80
+ if onChange != nil {
81
+ self.onChange!(otp)
82
+ }
83
+ }
84
+ }
85
+
86
+ public func buildContent(_ content: @escaping () -> AnyView) -> OTPKeyboard {
87
+ var newSelf = self
88
+ newSelf.content = content()
89
+ return newSelf
90
+ }
91
+
92
+ // MARK: Internal
93
+
94
+ @Binding var value: String
95
+ var length: Int
96
+ var content: AnyView?
97
+ var onChange: ((String) -> Void)?
98
+ var onClose: () -> Void
99
+
100
+ func onPressKey(key: String) {
101
+ if value.count < length {
102
+ value.append(key)
103
+ }
104
+ }
105
+
106
+ func onPressBack() {
107
+ if !value.isEmpty {
108
+ value.removeLast()
109
+ }
110
+ }
111
+
112
+ func onClear() {
113
+ value.removeAll()
114
+ }
115
+ }
116
+
117
+ // MARK: - ValueView
118
+
119
+ private struct ValueView: View {
120
+ // MARK: Lifecycle
121
+
122
+ init(_ value: String, valueLength: Int) {
123
+ self.value = value
124
+ self.valueLength = valueLength
125
+ }
126
+
127
+ // MARK: Public
128
+
129
+ public var body: some View {
130
+ return HStack {
131
+ ForEach(0 ..< valueLength, id: \.self) { i in
132
+ if i < value.count {
133
+ Circle().foregroundColor(Colors.text).frame(width: 8, height: 8)
134
+ } else {
135
+ Circle().foregroundColor(Colors.textSecondary).frame(width: 8, height: 8)
136
+ }
137
+ }
138
+ }.frame(maxWidth: .infinity, minHeight: 40)
139
+ }
140
+
141
+ // MARK: Internal
142
+
143
+ var value: String = ""
144
+ var valueLength: Int
145
+ }
@@ -0,0 +1,284 @@
1
+ import SDWebImageSwiftUI
2
+ import SwiftUI
3
+
4
+ // MARK: - PopupButtonDirection
5
+
6
+ public enum PopupButtonDirection {
7
+ case row
8
+ case column
9
+ case auto
10
+ }
11
+
12
+ // MARK: - PopupButtonType
13
+
14
+ public enum PopupButtonType {
15
+ case text
16
+ case button
17
+ }
18
+
19
+ extension View {
20
+ func measureLineHeights(font: Font,
21
+ oneLine: Binding<CGFloat>,
22
+ twoLines: Binding<CGFloat>) -> some View {
23
+ overlay(
24
+ VStack(spacing: 0) {
25
+ Text("A")
26
+ .font(font)
27
+ .lineLimit(1)
28
+ .fixedSize()
29
+ .background(
30
+ GeometryReader { g in
31
+ Color.clear.onAppear { oneLine.wrappedValue = g.size.height }
32
+ }
33
+ )
34
+ .hidden()
35
+
36
+ Text("A\nA")
37
+ .font(font)
38
+ .lineLimit(2)
39
+ .fixedSize(horizontal: false, vertical: true)
40
+ .background(
41
+ GeometryReader { g in
42
+ Color.clear.onAppear { twoLines.wrappedValue = g.size.height }
43
+ }
44
+ )
45
+ .hidden()
46
+ }
47
+ .frame(width: 0, height: 0)
48
+ )
49
+ }
50
+ }
51
+
52
+ // MARK: - PopupDisplay
53
+
54
+ public struct PopupDisplay: View {
55
+ // MARK: Lifecycle
56
+
57
+ 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) {
58
+ self._isPresented = isPresented
59
+ self.title = title
60
+ self.description = description
61
+ self.url = url
62
+ self.buttonDirection = buttonDirection
63
+ self.buttonType = buttonType
64
+ self.onPressAction = onPressAction
65
+ self.onPressCloseButton = onPressCloseButton
66
+ self.onClose = onClose
67
+ self.closeButtonTitle = closeButtonTitle
68
+ self.actionButtonTitle = actionButtonTitle
69
+ self.errorCode = errorCode
70
+ self.isShowCloseIcon = isShowCloseIcon
71
+ }
72
+
73
+ // MARK: Public
74
+
75
+ @State private var oneLineH: CGFloat = 0
76
+ @State private var twoLineH: CGFloat = 0
77
+
78
+ private var fallbackLH: CGFloat { UIFont.preferredFont(forTextStyle: .body).lineHeight }
79
+ private var perLine: CGFloat { max(twoLineH - oneLineH, oneLineH > 0 ? oneLineH : fallbackLH) }
80
+
81
+ private func heightForLines(lines: CGFloat) -> CGFloat {
82
+ guard oneLineH > 0 && twoLineH > 0 else { return ceil(fallbackLH * lines) }
83
+ return ceil(oneLineH + max(0, lines - 1) * (twoLineH - oneLineH))
84
+ }
85
+ private var maxHeight8_5: CGFloat { heightForLines(lines: 8.5) }
86
+
87
+
88
+ @State private var textHeight: CGFloat = .zero
89
+ @State private var isScrollable = false
90
+
91
+ public var body: some View {
92
+ var shouldUseColumn: Bool {
93
+ buttonDirection == .column || (buttonDirection == .auto && (closeButtonTitle.count > 12 || actionButtonTitle.count > 12))
94
+ }
95
+
96
+ VStack(spacing: 12) {
97
+ ZStack(alignment: .topTrailing) {
98
+ if(isShowCloseIcon) {
99
+ SwiftUI.Button(action: onClose) {
100
+ Image(systemName: "xmark.circle.fill")
101
+ .resizable()
102
+ .frame(width: 22, height: 22)
103
+ .overlay(RoundedRectangle(cornerRadius: Radius.L).stroke(Colors.black01, lineWidth: 2))
104
+ }.foregroundColor(Colors.black20)
105
+ .background(Colors.black01.cornerRadius(15))
106
+ .padding(.trailing, -11)
107
+ .padding(.top, -11)
108
+ .zIndex(1)
109
+ .accessibility(identifier: "ic_popup_close")
110
+ }
111
+
112
+ VStack(spacing: 0) {
113
+ if(!url.isEmpty) {
114
+ WebImage(url: URL(string: url), isAnimating: .constant(true))
115
+ .resizable()
116
+ .placeholder {
117
+ Rectangle()
118
+ .fill(Color.gray.opacity(0.3))
119
+ .frame(maxWidth: .infinity, maxHeight: 184)
120
+ }
121
+ .aspectRatio(1.777, contentMode: .fit)
122
+ .frame(maxWidth: .infinity, alignment: .center)
123
+ .clipShape(RoundedCorner(radius: 15, corners: [.topLeft, .topRight]))
124
+ .clipped()
125
+ }
126
+ VStack(alignment: .leading, spacing: 0) {
127
+ Text(title)
128
+ .foregroundColor(.black)
129
+ .font(.header_default_semibold)
130
+ .padding(.top, 24)
131
+ .padding(.bottom, 8)
132
+ .lineLimit(2)
133
+ .accessibility(identifier: "title_popup_permission")
134
+
135
+ Group {
136
+ if isScrollable {
137
+ ScrollView(showsIndicators: false) {
138
+ Text(description)
139
+ .font(.body_default_regular)
140
+ .foregroundColor(Colors.black17)
141
+ .multilineTextAlignment(.leading)
142
+ .background(GeometryReader { geo in
143
+ Color.clear.onAppear { textHeight = geo.size.height }
144
+ })
145
+ .measureLineHeights(font: .body_default_regular,
146
+ oneLine: $oneLineH,
147
+ twoLines: $twoLineH)
148
+ }
149
+ // Cap the visible height to ~8.5 lines
150
+ .frame(height: min(maxHeight8_5, textHeight))
151
+ } else {
152
+ Text(description)
153
+ .font(.body_default_regular)
154
+ .foregroundColor(Colors.black17)
155
+ .multilineTextAlignment(.leading)
156
+ .background(GeometryReader { geo in
157
+ Color.clear.onAppear {
158
+ textHeight = geo.size.height
159
+ // Trigger scroll at ~8.5 lines
160
+ isScrollable = textHeight > maxHeight8_5
161
+ }
162
+ })
163
+ .measureLineHeights(font: .body_default_regular,
164
+ oneLine: $oneLineH,
165
+ twoLines: $twoLineH)
166
+ }
167
+ }
168
+ .padding(.bottom, 8)
169
+
170
+ if(!errorCode.isEmpty) {
171
+ Text("Mã lỗi: " + errorCode)
172
+ .foregroundColor(Colors.black12)
173
+ .font(.description_xs_regular)
174
+ .lineLimit(1)
175
+ .padding(.bottom, 8)
176
+ }
177
+ }
178
+ .frame(maxWidth: .infinity, alignment: .leading)
179
+ .padding(.horizontal, 24)
180
+ .padding(.bottom, 16)
181
+
182
+ if shouldUseColumn {
183
+ if buttonType == .text {
184
+ ColumnTextButtons
185
+ } else {
186
+ ColumnButtons
187
+ }
188
+ } else {
189
+ if buttonType == .text {
190
+ RowTextButtons
191
+ } else {
192
+ RowButtons
193
+ }
194
+ }
195
+ }
196
+ .fixedSize(horizontal: false, vertical: true)
197
+ }
198
+ }
199
+ .background(Color.white.cornerRadius(15))
200
+ .padding(.horizontal, 12)
201
+ .accessibilityElement(children: .ignore)
202
+ .accessibility(identifier: "popup_notify")
203
+ }
204
+
205
+ // MARK: Internal
206
+
207
+ @Binding var isPresented: Bool
208
+ var title: String
209
+ var description: String
210
+ var url: String
211
+ var buttonDirection: PopupButtonDirection
212
+ var buttonType: PopupButtonType
213
+ var onPressAction: () -> Void
214
+ var onPressCloseButton: () -> Void
215
+ var onClose: () -> Void
216
+ var actionButtonTitle: String
217
+ var closeButtonTitle: String
218
+ var errorCode: String
219
+ var isShowCloseIcon: Bool
220
+
221
+ var RowTextButtons: some View {
222
+ HStack {
223
+ SwiftUI.Button(action: onPressCloseButton) {
224
+ Text(closeButtonTitle)
225
+ .font(.action2)
226
+ }.foregroundColor(Colors.black09)
227
+ .padding(.trailing, 12)
228
+
229
+ SwiftUI.Button(action: onPressAction) {
230
+ Text(actionButtonTitle)
231
+ .font(.action2)
232
+ }.foregroundColor(Colors.primary)
233
+ }
234
+ .frame(maxWidth: .infinity, alignment: .trailing)
235
+ .padding(EdgeInsets(top: 0, leading: 0, bottom: 24, trailing: 24))
236
+ }
237
+
238
+ var ColumnTextButtons: some View {
239
+ VStack(alignment: .trailing) {
240
+ SwiftUI.Button(action: onPressAction) {
241
+ Text(actionButtonTitle)
242
+ .font(.action2)
243
+ }.foregroundColor(Colors.primary)
244
+ .padding(.bottom, 6)
245
+
246
+ SwiftUI.Button(action: onPressCloseButton) {
247
+ Text(closeButtonTitle)
248
+ .font(.action2)
249
+ }.foregroundColor(Colors.black09)
250
+ }
251
+ .frame(maxWidth: .infinity, alignment: .trailing)
252
+ .padding(EdgeInsets(top: 0, leading: 0, bottom: 24, trailing: 24))
253
+ }
254
+
255
+ var RowButtons: some View {
256
+ HStack {
257
+ if(!closeButtonTitle.isEmpty) {
258
+ Button(title: closeButtonTitle, action: onPressCloseButton, type: .text, size: .medium).accessibility(identifier: "btn_popup_cancel")
259
+ }
260
+ Button(title: actionButtonTitle, action: onPressAction, size: .medium)
261
+ .accessibility(identifier: "btn_popup_allow")
262
+ }
263
+ .frame(maxWidth: .infinity, alignment: .center)
264
+ .padding(.horizontal, 24)
265
+ .padding(.bottom, 24)
266
+ }
267
+
268
+ var ColumnButtons: some View {
269
+ VStack(alignment: .trailing) {
270
+ Button(
271
+ title: actionButtonTitle,
272
+ action: onPressAction,
273
+ size: .medium
274
+ ).accessibility(identifier: "btn_popup_allow")
275
+ if(!closeButtonTitle.isEmpty) {
276
+ Button(title: closeButtonTitle, action: onPressCloseButton, type: .text, size: .medium).accessibility(identifier: "btn_popup_cancel")
277
+ }
278
+ }
279
+ .frame(maxWidth: .infinity, alignment: .trailing)
280
+ .padding(.horizontal, 24)
281
+ .padding(.bottom, 24)
282
+ }
283
+
284
+ }
@@ -0,0 +1,96 @@
1
+ import SDWebImageSwiftUI
2
+ import SwiftUI
3
+
4
+ public struct PopupInput: View {
5
+ // MARK: Lifecycle
6
+
7
+ public init(isPresented: Binding<Bool>, title: String = "", description: String = "", numberOfButtons: Int = 1, actionButtonTitle: String = "", closeButtonTitle: String = "", onPressAction: @escaping () -> Void = {}, onPressCloseButton: @escaping () -> Void = {}, onClose: @escaping () -> Void = {}, inputPlaceholder: String = "", inputValue: Binding<String> = Binding.constant(""), onTextChange: ((String) -> Void)? = nil) {
8
+ self._isPresented = isPresented
9
+ self._inputValue = inputValue
10
+ self.title = title
11
+ self.description = description
12
+ self.onPressAction = onPressAction
13
+ self.onPressCloseButton = onPressCloseButton
14
+ self.onClose = onClose
15
+ self.inputPlaceholder = inputPlaceholder
16
+ self.onTextChange = onTextChange
17
+ self.numberOfButtons = numberOfButtons
18
+ self.closeButtonTitle = closeButtonTitle
19
+ self.actionButtonTitle = actionButtonTitle
20
+ }
21
+
22
+ // MARK: Public
23
+
24
+ public var body: some View {
25
+ VStack(spacing: 12) {
26
+ ZStack(alignment: .topTrailing) {
27
+ SwiftUI.Button(action: onPressClose) {
28
+ Image(systemName: "xmark.circle.fill")
29
+ .resizable()
30
+ .frame(width: 16, height: 16)
31
+ }.foregroundColor(Colors.black20)
32
+ .background(Colors.black01.cornerRadius(8))
33
+ .offset(x: 6, y: -6)
34
+ .zIndex(1)
35
+
36
+ VStack {
37
+ VStack(alignment: .leading) {
38
+ Text(title)
39
+ .foregroundColor(.black)
40
+ .font(.headerText1)
41
+ .padding(.top, 12)
42
+ .padding(.bottom, 24)
43
+
44
+ Text(description)
45
+ .foregroundColor(.black)
46
+ .font(.paragraph)
47
+ .opacity(0.6)
48
+ .multilineTextAlignment(.leading)
49
+ .padding(.bottom, 20)
50
+ }.padding(.horizontal, 24)
51
+
52
+ Input($inputValue, placeholder: inputPlaceholder, onChange: onTextChange)
53
+ .padding(.horizontal, 24)
54
+
55
+ if numberOfButtons == 1 {
56
+ Button(title: actionButtonTitle, action: onPressAction)
57
+ .padding(.horizontal, 24)
58
+ .padding(.bottom, 24)
59
+ } else {
60
+ HStack {
61
+ Button(title: closeButtonTitle, action: onPressCloseButton, type: .secondary)
62
+ Button(title: actionButtonTitle, action: onPressAction)
63
+ }.padding(.horizontal, 24)
64
+ .padding(.bottom, 24)
65
+ }
66
+
67
+ }.clipped()
68
+ }
69
+ }
70
+ .background(Color.white.cornerRadius(8))
71
+ .shadow(color: .black.opacity(0.08), radius: 2, x: 0, y: 0)
72
+ .shadow(color: .black.opacity(0.16), radius: 24, x: 0, y: 0)
73
+ .padding(.horizontal, 40)
74
+ }
75
+
76
+ // MARK: Internal
77
+
78
+ @Binding var isPresented: Bool
79
+ @Binding var inputValue: String
80
+ var title: String
81
+ var description: String
82
+ var onPressAction: () -> Void
83
+ var onPressCloseButton: () -> Void
84
+ var onClose: () -> Void
85
+ var inputPlaceholder: String
86
+ var onTextChange: ((String) -> Void)?
87
+ var numberOfButtons: Int
88
+ var actionButtonTitle: String
89
+ var closeButtonTitle: String
90
+
91
+
92
+ func onPressClose(){
93
+ isPresented = false
94
+ onClose()
95
+ }
96
+ }