@momo-kits/native-kits 0.112.1-beta.9 → 0.113.1-beta.0

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.
@@ -242,12 +242,10 @@ fun Header(
242
242
  }
243
243
  }
244
244
 
245
- if (inputSearchProps == null) {
246
- Box(Modifier.graphicsLayer {
247
- alpha = if (animatedHeader !== null) opacity ?: 1f else 1f
248
- }) {
249
- headerRight?.invoke()
250
- }
245
+ Box(Modifier.graphicsLayer {
246
+ alpha = if (animatedHeader !== null) opacity ?: 1f else 1f
247
+ }) {
248
+ headerRight?.invoke()
251
249
  }
252
250
  }
253
251
 
@@ -22,44 +22,49 @@ import vn.momo.kits.const.defaultTheme
22
22
  import vn.momo.kits.platform.disableOverscroll
23
23
  import vn.momo.kits.platform.getStatusBarHeight
24
24
 
25
+ val PlatformApi = staticCompositionLocalOf<Any?> { null }
26
+
25
27
  interface ComposeApi {
26
- fun request(funcName: String, params: Any?): String
27
- fun request(funcName: String, params: Any?, onResponse: ((String) -> Unit)?): String
28
- fun requestCallback(funcName: String, params: Any?, onResponse: ((String) -> Unit)?)
28
+ fun request(funcName: String, params: String?): String
29
+ fun request(funcName: String, params: String?, onResponse: ((String) -> Unit)?): String
30
+ fun requestCallback(funcName: String, params: String?, onResponse: ((String) -> Unit)?)
29
31
  fun removeCallback(id: String)
30
32
  }
31
33
 
34
+ val AppNavigator = staticCompositionLocalOf<Navigator> {
35
+ error("no navigator provided!")
36
+ }
37
+
32
38
  class Navigator {
33
- fun push(content: @Composable () -> Unit) {}
34
- fun present(content: @Composable () -> Unit) {}
35
- fun reset(content: @Composable () -> Unit) {}
36
- fun pop() {}
37
- fun replace(content: @Composable () -> Unit) {}
38
- fun popToTop() {}
39
+ fun push(content: @Composable () -> Unit) {
40
+ }
41
+
42
+ fun present(content: @Composable () -> Unit) {
43
+
44
+ }
45
+
46
+ fun reset(content: @Composable () -> Unit) {
47
+
48
+ }
49
+
50
+ fun pop() {
51
+ }
52
+
53
+ fun replace(content: @Composable () -> Unit) {
54
+ }
55
+
56
+ fun popToTop() {
57
+ }
58
+
39
59
  fun showModal(
40
60
  onClose: () -> Unit = {},
41
61
  canBackgroundClose: Boolean = true,
42
62
  content: @Composable () -> Unit,
43
- ) {}
44
- fun showBottomSheet(content: @Composable () -> Unit) {}
45
- }
46
-
47
- data class MiniAppContext(
48
- val icon: String = "",
49
- val name: String = "",
50
- val appId: String = "",
51
- val code: String = "",
52
- val description: String? = "",
53
- )
54
-
55
- val PlatformApi = staticCompositionLocalOf<Any?> { null }
56
-
57
- val AppNavigator = staticCompositionLocalOf<Navigator> {
58
- error("no navigator provided!")
59
- }
63
+ ) {
64
+ }
60
65
 
61
- val ApplicationContext = staticCompositionLocalOf<MiniAppContext?> {
62
- error("no context provided!")
66
+ fun showBottomSheet(content: @Composable () -> Unit) {
67
+ }
63
68
  }
64
69
 
65
70
  @Composable
@@ -67,8 +72,7 @@ fun ApplicationContainer(
67
72
  theme: Theme = defaultTheme,
68
73
  composeApi: ComposeApi? = null,
69
74
  statusBarHeight: Dp? = AppStatusBar.current ?: getStatusBarHeight(),
70
- applicationContext: MiniAppContext? = null,
71
- content: @Composable () -> Unit,
75
+ content: @Composable () -> Unit
72
76
  ) {
73
77
  var appTheme by remember { mutableStateOf(theme) }
74
78
  val coroutineScope = rememberCoroutineScope()
@@ -93,7 +97,8 @@ fun ApplicationContainer(
93
97
  }
94
98
  }
95
99
  }
96
- } catch (e: Exception) {
100
+ }
101
+ catch (e: Exception) {
97
102
  print("@@ == NavigationContainer fetch config error $e")
98
103
  }
99
104
  }
@@ -104,7 +109,6 @@ fun ApplicationContainer(
104
109
  PlatformApi provides composeApi,
105
110
  AppNavigator provides Navigator(),
106
111
  AppStatusBar provides statusBarHeight,
107
- ApplicationContext provides applicationContext,
108
112
  ) {
109
113
  content()
110
114
  }
@@ -62,7 +62,7 @@ fun Screen(
62
62
  onContentLayout: ((LayoutCoordinates) -> Unit)? = null,
63
63
  useAvoidKeyboard: Boolean = true,
64
64
  footer: @Composable (() -> Unit)? = null,
65
- headerRight: @Composable (() -> Unit)? = { HeaderRight() },
65
+ headerRight: @Composable (() -> Unit)? = null,
66
66
  fabProps: FabProps? = null,
67
67
  animatedHeader: AnimatedHeader? = null,
68
68
  layoutOffset: Dp = 56.dp,
@@ -17,9 +17,9 @@ enum class DotSize(val size: Int) {
17
17
  }
18
18
 
19
19
  @Composable
20
- fun BadgeDot(size: DotSize = DotSize.Large, modifier: Modifier = Modifier) {
20
+ fun BadgeDot(size: DotSize = DotSize.Large) {
21
21
  Box(
22
- modifier = modifier
22
+ modifier = Modifier
23
23
  .size(size.size.dp)
24
24
  .border(width = 1.dp, color = Colors.black_01, shape = RoundedCornerShape(Radius.S))
25
25
  .background(color = Colors.red_03, shape = RoundedCornerShape(Radius.S))
@@ -22,10 +22,10 @@ fun Icon(
22
22
  color: Color? = AppTheme.current.colors.text.default,
23
23
  modifier: Modifier = Modifier,
24
24
  ) {
25
- val icon = when {
26
- source.startsWith("data:") -> source
27
- source.contains("https") -> source
28
- else -> Icons[source] ?: ""
25
+ val icon = if (source.contains("https")) {
26
+ source
27
+ } else {
28
+ Icons[source] ?: ""
29
29
  }
30
30
  Box(modifier = modifier.size(size).semantics {contentDescription = "img|$icon"}) {
31
31
  AsyncImage(
@@ -75,6 +75,7 @@ fun OTPCaret() {
75
75
 
76
76
  @Composable
77
77
  fun InputOTP(
78
+ value: String = "",
78
79
  length: Int? = null,
79
80
  errorMessage: String = "",
80
81
  errorSpacing: Boolean = false,
@@ -85,9 +86,9 @@ fun InputOTP(
85
86
  onBlur: () -> Unit = {},
86
87
  dataType: String = "number",
87
88
  accessibilityLabel: String? = null,
89
+ modifier: Modifier = Modifier
88
90
  ) {
89
91
  val MAX_LENGTH = 10
90
- var value by remember { mutableStateOf("") }
91
92
  var isFocused by remember { mutableStateOf(false) }
92
93
  var isBlurred = false
93
94
 
@@ -96,7 +97,6 @@ fun InputOTP(
96
97
  (dataType != "number" || !text.any { !it.isDigit() }) &&
97
98
  (text.length < value.length || value.length < text.length)
98
99
  ) {
99
- value = text
100
100
  onChangeText(text)
101
101
  }
102
102
  }
@@ -108,7 +108,7 @@ fun InputOTP(
108
108
  onValueChange = handleChangeText,
109
109
  singleLine = true,
110
110
  keyboardOptions = KeyboardOptions.Default.copy(keyboardType = if (dataType == "number") KeyboardType.Number else KeyboardType.Ascii),
111
- modifier = Modifier.height(56.dp).onFocusChanged {
111
+ modifier = modifier.height(56.dp).onFocusChanged {
112
112
  isFocused = it.isFocused
113
113
  if (it.isFocused) {
114
114
  onFocus()
@@ -39,15 +39,15 @@ fun getFont(font: String): FontFamily {
39
39
  fun getFontFamily(fontFamily: String, fontWeight: FontWeight? = FontWeight.Normal): FontFamily {
40
40
  val key = "$fontFamily-${fontWeight?.weight}"
41
41
  val fontMap = mapOf(
42
- "SFProText-100" to "sfprotext_thin.ttf",
43
- "SFProText-200" to "sfprotext_ultralight.ttf",
42
+ "SFProText-100" to "sfprotext_thin.otf",
43
+ "SFProText-200" to "sfprotext_ultralight.otf",
44
44
  "SFProText-300" to "sfprotext_light.ttf",
45
45
  "SFProText-400" to "sfprotext_regular.ttf",
46
46
  "SFProText-500" to "sfprotext_medium.ttf",
47
- "SFProText-600" to "sfprotext_medium.ttf",
48
- "SFProText-700" to "sfprotext_semibold.ttf",
49
- "SFProText-800" to "sfprotext_bold.ttf",
50
- "SFProText-900" to "sfprotext_heavy.ttf",
47
+ "SFProText-600" to "sfprotext_semibold.ttf",
48
+ "SFProText-700" to "sfprotext_bold.ttf",
49
+ "SFProText-800" to "sfprotext_heavy.ttf",
50
+ "SFProText-900" to "sfprotext_black.otf",
51
51
 
52
52
  "MoMoSignature-100" to "momosignature.otf",
53
53
  "MoMoSignature-200" to "momosignature.otf",
@@ -1318,10 +1318,5 @@ val Icons = mapOf(
1318
1318
  "ic_checked" to "https://img.mservice.com.vn/app/img/kits/checked_ic.png",
1319
1319
  "ic_minus" to "https://img.mservice.com.vn/app/img/kits/minus.png",
1320
1320
  "ic_navigation" to "https://static.momocdn.net/app/img/kits/ic_navigation.png",
1321
- "ic_money_bag" to "https://static.momocdn.net/app/img/kits/ic_money_bag.png",
1322
- "ic_support" to "https://static.momocdn.net/app/img/kits/ic_support.png",
1323
- "navigation_more_icon" to "https://static.momocdn.net/app/img/kits/navigation_more_icon.png",
1324
- "help_center" to "https://static.momocdn.net/app/img/kits/help-center.png",
1325
- "pin_star" to "https://static.momocdn.net/app/img/kits/pin_star.png",
1326
- "pin_star_checked" to "https://static.momocdn.net/app/img/kits/pin_star_checked.png"
1321
+ "ic_money_bag" to "https://static.momocdn.net/app/img/kits/ic_money_bag.png"
1327
1322
  )
@@ -0,0 +1,223 @@
1
+ //
2
+ // Components.swift
3
+ // Pods
4
+ //
5
+ // Created by sophia on 24/1/25.
6
+ //
7
+
8
+ import SwiftUI
9
+
10
+ public enum TitlePosition {
11
+ case left, center
12
+ }
13
+
14
+ public enum HeaderType {
15
+ case `default`, extended, none
16
+ }
17
+
18
+
19
+ public struct AnimatedHeader {
20
+ public var type: HeaderType = .default
21
+ public var composable: (_ scrollState: Int) -> AnyView = { _ in AnyView(EmptyView()) }
22
+
23
+ public init(
24
+ type: HeaderType = .default,
25
+ composable: @escaping (_ scrollState: Int) -> AnyView = { _ in AnyView(EmptyView()) }
26
+ ) {
27
+ self.type = type
28
+ self.composable = composable
29
+ }
30
+ }
31
+
32
+ func safeAreaTopInset() -> CGFloat {
33
+ if #available(iOS 15.0, *) {
34
+ return UIApplication.shared.connectedScenes
35
+ .compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.top }
36
+ .first ?? 0
37
+ } else {
38
+ return UIApplication.shared.connectedScenes
39
+ .compactMap { ($0 as? UIWindowScene)?.windows.first?.safeAreaInsets.top }
40
+ .first ?? 0
41
+ }
42
+ }
43
+
44
+
45
+ public struct HeaderBackground: View {
46
+ var headerType: HeaderType = .default
47
+ var scrollState: CGFloat
48
+
49
+ public var body: some View {
50
+
51
+ let statusBarHeight = safeAreaTopInset()
52
+
53
+ let opacity = 1 - CGFloat(scrollState) / 200
54
+ let backgroundColor = scrollState == 0 ? Color.clear : Colors.black01
55
+
56
+ Group {
57
+ if headerType == .default {
58
+ let height = statusBarHeight + 52
59
+ ZStack {
60
+
61
+ Rectangle()
62
+ .fill(backgroundColor)
63
+ .frame(height: height)
64
+ .shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
65
+ .overlay(Rectangle()
66
+ .fill(LinearGradient(
67
+ gradient: Gradient(colors: [
68
+ Color(red: 1, green: 0.8, blue: 0.87),
69
+ Color(red: 1, green: 0.8, blue: 0.87).opacity(0.5)
70
+ ]),
71
+ startPoint: .top,
72
+ endPoint: .bottom))
73
+ .opacity(opacity)
74
+ .frame(height: height))
75
+ .overlay(Rectangle()
76
+ .fill(Colors.black04)
77
+ .frame(height: 1)
78
+ .frame(maxWidth: .infinity)
79
+ .alignmentGuide(.bottom) { _ in 0 }, alignment: .bottom
80
+ )
81
+ }
82
+ .frame(maxWidth: .infinity)
83
+
84
+ } else if headerType == .extended {
85
+ ZStack {
86
+ Rectangle()
87
+ .fill(LinearGradient(
88
+ gradient: Gradient(colors: [
89
+ Color(red: 1, green: 0.8, blue: 0.87),
90
+ Color(red: 1, green: 0.8, blue: 0.87).opacity(0)
91
+ ]),
92
+ startPoint: .top,
93
+ endPoint: .bottom))
94
+ .opacity(opacity)
95
+ .frame(height: 154)
96
+
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ }
103
+
104
+ public struct Header: View {
105
+ var headerType: HeaderType = .default
106
+ var titlePosition: TitlePosition
107
+ var title: String
108
+ var headerRight: (() -> AnyView)?
109
+ var goBack: (() -> Void)?
110
+ var opacity: CGFloat? = nil
111
+ var animatedHeader: AnimatedHeader? = nil
112
+ var scrollState: CGFloat = 0
113
+ var inputSearchProps: InputSearchProps? = nil
114
+
115
+ @State private var colorFraction: CGFloat = 0
116
+
117
+ public init(
118
+ headerType: HeaderType = .default,
119
+ titlePosition: TitlePosition,
120
+ title: String,
121
+ headerRight: (() -> AnyView)? = nil,
122
+ goBack: (() -> Void)? = nil,
123
+ opacity: CGFloat? = nil,
124
+ animatedHeader: AnimatedHeader? = nil,
125
+ scrollState: CGFloat = 0,
126
+ inputSearchProps: InputSearchProps? = nil
127
+ ) {
128
+ self.headerType = headerType
129
+ self.titlePosition = titlePosition
130
+ self.title = title
131
+ self.headerRight = headerRight
132
+ self.goBack = goBack
133
+ self.opacity = opacity
134
+ self.animatedHeader = animatedHeader
135
+ self.scrollState = scrollState
136
+ self.inputSearchProps = inputSearchProps
137
+ }
138
+
139
+ public var body: some View {
140
+
141
+ let statusBarHeight = safeAreaTopInset()
142
+ let tintIconColor = Colors.black17
143
+ let backgroundButton = Colors.black01.opacity(0.6)
144
+
145
+ if headerType != .none {
146
+ VStack(spacing: 0) {
147
+ ZStack {
148
+ HStack {
149
+ if let goBack = goBack {
150
+ SwiftUI.Button(action: goBack) {
151
+ Circle()
152
+ .stroke(Color.black.opacity(0.2), lineWidth: 0.2)
153
+ .background(Circle().fill(backgroundButton))
154
+ .frame(width: 28, height: 28)
155
+ .overlay(
156
+ Image(systemName: "arrow.backward")
157
+ .foregroundColor(.black)
158
+ )
159
+ }
160
+
161
+ }
162
+
163
+ Spacer() // Pushes headerRight to the right
164
+
165
+ if let headerRight = headerRight {
166
+ headerRight()
167
+ }
168
+ }
169
+ .frame(height: 52)
170
+ .padding(.horizontal)
171
+
172
+
173
+
174
+ HStack {
175
+ if titlePosition == .left, goBack != nil {
176
+ Spacer().frame(width: 40)
177
+ }
178
+
179
+ if inputSearchProps != nil {
180
+ if let inputProps = inputSearchProps {
181
+ InputSearch(
182
+ text: inputProps.$text,
183
+ buttonText: inputProps.buttonText,
184
+ showButtonText: inputProps.showButtonText,
185
+ showBorder: inputProps.showBorder,
186
+ placeholder: inputProps.placeholder,
187
+ onChangeText: inputProps.onChangeText,
188
+ onPressButtonText: inputProps.onPressButtonText,
189
+ error: inputProps.error,
190
+ disabled: inputProps.disabled,
191
+ icon: inputProps.icon,
192
+ iconColor: inputProps.iconColor,
193
+ onRightIconPressed: inputProps.onRightIconPressed,
194
+ onFocus: inputProps.onFocus,
195
+ onBlur: inputProps.onBlur,
196
+ loading: inputProps.loading,
197
+ fontWeight: inputProps.fontWeight,
198
+ keyboardType: inputProps.keyboardType
199
+ )
200
+ }
201
+ } else {
202
+ Text(title)
203
+ .font(.headline)
204
+ .foregroundColor(tintIconColor)
205
+ .frame(maxWidth: titlePosition == .center ? .infinity : nil, alignment: titlePosition == .center ? .center : .leading)
206
+ }
207
+
208
+ Spacer()
209
+ }
210
+ .frame(height: 52)
211
+ .padding(.horizontal)
212
+
213
+ }
214
+ .background(Color.clear)
215
+ }
216
+ }
217
+ }
218
+
219
+ }
220
+
221
+
222
+
223
+
@@ -0,0 +1,148 @@
1
+ //
2
+ // FloatingButton.swift
3
+ // Pods
4
+ //
5
+ // Created by sophia on 6/2/25.
6
+ //
7
+
8
+ import SwiftUI
9
+ import Combine
10
+
11
+ public enum FABSize {
12
+ case small
13
+ case `default`
14
+ }
15
+
16
+ public enum FABPosition {
17
+ case end
18
+ case center
19
+ }
20
+
21
+ public struct FabProps {
22
+ let icon: String
23
+ let iconColor: Color?
24
+ let label: String?
25
+ let onClick: () -> Void
26
+ let size: FABSize
27
+ let bottom: CGFloat?
28
+ let scrollOffset: CGFloat?
29
+ let position: FABPosition?
30
+
31
+ public init(
32
+ icon: String,
33
+ iconColor: Color? = nil,
34
+ label: String? = nil,
35
+ onClick: @escaping () -> Void,
36
+ size: FABSize = .default,
37
+ bottom: CGFloat? = nil,
38
+ scrollOffset: CGFloat? = nil,
39
+ position: FABPosition? = .end
40
+ ) {
41
+ self.icon = icon
42
+ self.iconColor = iconColor
43
+ self.label = label
44
+ self.onClick = onClick
45
+ self.size = size
46
+ self.bottom = bottom
47
+ self.scrollOffset = scrollOffset
48
+ self.position = position
49
+ }
50
+ }
51
+
52
+ public struct FloatingButton: View {
53
+ @State private var lastScrollOffset: CGFloat = 0
54
+ @State private var isExpanded: Bool = false
55
+ let scrollOffset: CGFloat
56
+ let bottomPadding: CGFloat
57
+ let onClick: () -> Void
58
+ let containerColor: Color
59
+ let contentColor: Color
60
+ let icon: String
61
+ let iconColor: Color?
62
+ let text: String?
63
+ let size: FABSize
64
+ let position: FABPosition
65
+ let keyboardOffset: CGFloat
66
+
67
+ public init(
68
+ scrollOffset: CGFloat = 0,
69
+ bottomPadding: CGFloat = 0,
70
+ onClick: @escaping () -> Void,
71
+ containerColor: Color = .blue,
72
+ contentColor: Color = .white,
73
+ icon: String,
74
+ iconColor: Color? = nil,
75
+ text: String? = nil,
76
+ size: FABSize = .default,
77
+ position: FABPosition = .end,
78
+ keyboardOffset: CGFloat = 0
79
+ ) {
80
+ self.scrollOffset = scrollOffset
81
+ self.bottomPadding = bottomPadding
82
+ self.onClick = onClick
83
+ self.containerColor = containerColor
84
+ self.contentColor = contentColor
85
+ self.icon = icon
86
+ self.iconColor = iconColor
87
+ self.text = text
88
+ self.size = size
89
+ self.position = position
90
+ self.keyboardOffset = keyboardOffset
91
+ }
92
+
93
+ public var body: some View {
94
+ VStack {
95
+ Spacer()
96
+ HStack {
97
+ if position == .center {
98
+ Spacer()
99
+ }
100
+ FloatingButtonContent()
101
+ .padding(.trailing, position == .end ? 12 : 0)
102
+ .padding(.bottom, bottomPadding)
103
+ .offset(y: -keyboardOffset)
104
+ if position == .end {
105
+ Spacer()
106
+ }
107
+ }
108
+ }
109
+ .onReceive(Just(scrollOffset)) { newValue in
110
+ withAnimation(.easeInOut(duration: 0.3)) {
111
+ isExpanded = newValue > lastScrollOffset
112
+ lastScrollOffset = newValue
113
+ }
114
+ }
115
+ }
116
+
117
+ @ViewBuilder
118
+ private func FloatingButtonContent() -> some View {
119
+ SwiftUI.Button(action: onClick) {
120
+ HStack {
121
+ Image(systemName: icon)
122
+ .foregroundColor(iconColor ?? .white)
123
+ .font(size == .small ? .system(size: 12) : .system(size: 24))
124
+
125
+ if isExpanded, let text = text {
126
+ Text(text)
127
+ .foregroundColor(.white)
128
+ .transition(.asymmetric(insertion: expandAnimation, removal: collapseAnimation))
129
+ }
130
+ }
131
+ .padding(.horizontal, isExpanded ? 12 : 0)
132
+ .frame(width: isExpanded ? 80 : (size == .small ? 36 : 48), height: size == .small ? 36 : 48)
133
+ .background(containerColor)
134
+ .cornerRadius(24)
135
+ .shadow(radius: 4)
136
+ }
137
+ }
138
+
139
+ private var expandAnimation: AnyTransition {
140
+ AnyTransition.opacity.animation(.easeIn(duration: 0.2))
141
+ .combined(with: .move(edge: .leading))
142
+ }
143
+
144
+ private var collapseAnimation: AnyTransition {
145
+ AnyTransition.opacity.animation(.easeOut(duration: 0.5))
146
+ .combined(with: .move(edge: .trailing))
147
+ }
148
+ }
@@ -0,0 +1,175 @@
1
+ //
2
+ // Screen.swift
3
+ // Pods
4
+ //
5
+ // Created by sophia on 5/2/25.
6
+ //
7
+
8
+ import SwiftUI
9
+
10
+ public struct Screen<Content: View>: View {
11
+ var backgroundColor: Color?
12
+ var isBack: Bool
13
+ var headerType: HeaderType
14
+ var verticalAlignment: VerticalAlignment
15
+ var horizontalAlignment: HorizontalAlignment
16
+ var title: String
17
+ var titlePosition: TitlePosition
18
+ var goBack: (() -> Void)?
19
+ var scrollable: Bool
20
+ var onContentLayout: ((CGRect) -> Void)?
21
+ var useAvoidKeyboard: Bool
22
+ var footer: (() -> AnyView)?
23
+ var headerRight: (() -> AnyView)?
24
+ var animatedHeader: AnimatedHeader? = nil
25
+ var layoutOffset: CGFloat
26
+ var inputSearchProps: InputSearchProps? = nil
27
+ var fabProps: FabProps? = nil
28
+ var content: () -> Content
29
+
30
+ public init(
31
+ backgroundColor: Color? = nil,
32
+ isBack: Bool = true,
33
+ headerType: HeaderType = .default,
34
+ verticalAlignment: VerticalAlignment = .top,
35
+ horizontalAlignment: HorizontalAlignment = .leading,
36
+ title: String = "Stack",
37
+ titlePosition: TitlePosition = .center,
38
+ goBack: (() -> Void)? = nil,
39
+ scrollable: Bool = true,
40
+ onContentLayout: ((CGRect) -> Void)? = nil,
41
+ useAvoidKeyboard: Bool = true,
42
+ footer: (() -> AnyView)? = nil,
43
+ headerRight: (() -> AnyView)? = nil,
44
+ animatedHeader: AnimatedHeader? = nil,
45
+ layoutOffset: CGFloat = 56,
46
+ inputSearchProps: InputSearchProps? = nil,
47
+ fabProps: FabProps? = nil,
48
+ @ViewBuilder content: @escaping () -> Content
49
+ ) {
50
+ self.backgroundColor = backgroundColor
51
+ self.isBack = isBack
52
+ self.headerType = headerType
53
+ self.verticalAlignment = verticalAlignment
54
+ self.horizontalAlignment = horizontalAlignment
55
+ self.title = title
56
+ self.titlePosition = titlePosition
57
+ self.goBack = goBack
58
+ self.scrollable = scrollable
59
+ self.onContentLayout = onContentLayout
60
+ self.useAvoidKeyboard = useAvoidKeyboard
61
+ self.footer = footer
62
+ self.headerRight = headerRight
63
+ self.animatedHeader = animatedHeader
64
+ self.layoutOffset = layoutOffset
65
+ self.inputSearchProps = inputSearchProps
66
+ self.fabProps = fabProps
67
+ self.content = content
68
+ }
69
+
70
+ @State private var scrollOffset: CGFloat = 0
71
+
72
+ public var body: some View {
73
+ GeometryReader { geometry in
74
+ let safeAreaInsets = geometry.safeAreaInsets
75
+ let statusBarHeight = safeAreaInsets.top
76
+ let keyboardHeight: CGFloat = 0
77
+ let keyboardSize: CGFloat = useAvoidKeyboard ? max(keyboardHeight, 0) : 0
78
+
79
+ ZStack {
80
+ (backgroundColor ?? Color.white).edgesIgnoringSafeArea(.all)
81
+
82
+ VStack(spacing: 0) {
83
+ ZStack {
84
+ if animatedHeader == nil {
85
+ HeaderBackground(headerType: headerType, scrollState: scrollOffset)
86
+ } else {
87
+ Color.white.opacity(Double(min(scrollOffset / 114, 1)))
88
+ .overlay(HeaderBackground(headerType: headerType, scrollState: scrollOffset))
89
+
90
+ }
91
+
92
+ Header(
93
+ headerType: headerType,
94
+ titlePosition: titlePosition,
95
+ title: title,
96
+ headerRight: headerRight,
97
+ goBack: goBack,
98
+ opacity: min(scrollOffset / 114, 1),
99
+ animatedHeader: animatedHeader,
100
+ scrollState: scrollOffset,
101
+ inputSearchProps: inputSearchProps
102
+ ).padding(.bottom, headerType == .extended ? 0 : -safeAreaTopInset())
103
+ }.padding(.top, -safeAreaTopInset())
104
+
105
+
106
+ VStack(alignment: horizontalAlignment, spacing: 0) {
107
+ if animatedHeader != nil {
108
+ Spacer().frame(height: statusBarHeight + 52 + layoutOffset)
109
+ }
110
+
111
+ if scrollable {
112
+ ScrollView(.vertical, showsIndicators: false) {
113
+ VStack {
114
+ content()
115
+ }
116
+ .background(GeometryReader { geo -> Color in
117
+ DispatchQueue.main.async {
118
+ self.scrollOffset = -geo.frame(in: .global).origin.y
119
+ }
120
+ return Color.clear
121
+ })
122
+ }
123
+ } else {
124
+ content()
125
+ }
126
+ }
127
+ .padding(.bottom, keyboardSize)
128
+
129
+
130
+ if let footerView = footer?() {
131
+ ZStack(alignment: .top) {
132
+ Rectangle()
133
+ .fill(Colors.black20)
134
+ .frame(height: 4)
135
+ .shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
136
+
137
+ Footer(footerView: footerView, keyboardSize: keyboardSize)
138
+ }
139
+ }
140
+
141
+ if let fabProps = fabProps {
142
+ FloatingButton(
143
+ scrollOffset: scrollOffset,
144
+ bottomPadding: fabProps.bottom ?? 16,
145
+ onClick: fabProps.onClick,
146
+ containerColor: .blue,
147
+ contentColor: .white,
148
+ icon: fabProps.icon,
149
+ iconColor: fabProps.iconColor,
150
+ text: fabProps.label,
151
+ size: fabProps.size,
152
+ position: fabProps.position ?? .end,
153
+ keyboardOffset: keyboardSize
154
+ )
155
+ }
156
+ }
157
+
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ struct Footer: View {
164
+ var footerView: AnyView
165
+ var keyboardSize: CGFloat
166
+
167
+ var body: some View {
168
+ VStack {
169
+ footerView
170
+ }
171
+ .padding(.bottom, keyboardSize)
172
+ .background(Colors.black01)
173
+ }
174
+ }
175
+
@@ -37,6 +37,62 @@ struct RenderRightIconSearch: View {
37
37
  }
38
38
  }
39
39
 
40
+ public struct InputSearchProps {
41
+ @Binding var text: String
42
+ var buttonText: String = ""
43
+ var showButtonText: Bool = false
44
+ var showBorder: Bool = true
45
+ var placeholder: String = ""
46
+ var onChangeText: (String) -> Void = { _ in }
47
+ var onPressButtonText: () -> Void = {}
48
+ var error: String = ""
49
+ var disabled: Bool = false
50
+ var icon: String = ""
51
+ var iconColor: Color = Colors.text
52
+ var onRightIconPressed: () -> Void = {}
53
+ var onFocus: () -> Void = {}
54
+ var onBlur: () -> Void = {}
55
+ var loading: Bool = false
56
+ var fontWeight: Font.Weight = .regular
57
+ var keyboardType: UIKeyboardType = .default
58
+
59
+ public init(text: Binding<String>,
60
+ buttonText: String = "",
61
+ showButtonText: Bool = false,
62
+ showBorder: Bool = true,
63
+ placeholder: String = "",
64
+ onChangeText: @escaping (String) -> Void = { _ in },
65
+ onPressButtonText: @escaping () -> Void = {},
66
+ error: String = "",
67
+ disabled: Bool = false,
68
+ icon: String = "",
69
+ iconColor: Color = Colors.text,
70
+ onRightIconPressed: @escaping () -> Void = {},
71
+ onFocus: @escaping () -> Void = {},
72
+ onBlur: @escaping () -> Void = {},
73
+ loading: Bool = false,
74
+ fontWeight: Font.Weight = .regular,
75
+ keyboardType: UIKeyboardType = .default) {
76
+ self._text = text
77
+ self.buttonText = buttonText
78
+ self.showButtonText = showButtonText
79
+ self.showBorder = showBorder
80
+ self.placeholder = placeholder
81
+ self.onChangeText = onChangeText
82
+ self.onPressButtonText = onPressButtonText
83
+ self.error = error
84
+ self.disabled = disabled
85
+ self.icon = icon
86
+ self.iconColor = iconColor
87
+ self.onRightIconPressed = onRightIconPressed
88
+ self.onFocus = onFocus
89
+ self.onBlur = onBlur
90
+ self.loading = loading
91
+ self.fontWeight = fontWeight
92
+ self.keyboardType = keyboardType
93
+ }
94
+ }
95
+
40
96
  // MARK: - InputSearch
41
97
 
42
98
  public struct InputSearch: View {
package/local.properties CHANGED
@@ -4,5 +4,5 @@
4
4
  # Location of the SDK. This is only used by Gradle.
5
5
  # For customization when using a Version Control System, please read the
6
6
  # header note.
7
- #Tue Apr 23 16:40:15 ICT 2024
8
- sdk.dir=/Users/hung.dao1/Library/Android/sdk
7
+ #Wed Aug 21 14:20:12 ICT 2024
8
+ sdk.dir=/Users/huynhdung/Library/Android/sdk
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.112.1-beta.9",
3
+ "version": "0.113.1-beta.0",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},
@@ -1,234 +0,0 @@
1
- package vn.momo.kits.application
2
-
3
- import androidx.compose.foundation.background
4
- import androidx.compose.foundation.border
5
- import androidx.compose.foundation.clickable
6
- import androidx.compose.foundation.layout.Arrangement
7
- import androidx.compose.foundation.layout.Box
8
- import androidx.compose.foundation.layout.Row
9
- import androidx.compose.foundation.layout.height
10
- import androidx.compose.foundation.layout.offset
11
- import androidx.compose.foundation.layout.padding
12
- import androidx.compose.foundation.layout.size
13
- import androidx.compose.foundation.layout.width
14
- import androidx.compose.foundation.shape.CircleShape
15
- import androidx.compose.foundation.shape.RoundedCornerShape
16
- import androidx.compose.runtime.Composable
17
- import androidx.compose.runtime.getValue
18
- import androidx.compose.runtime.mutableStateOf
19
- import androidx.compose.runtime.remember
20
- import androidx.compose.runtime.setValue
21
- import androidx.compose.ui.Alignment
22
- import androidx.compose.ui.Modifier
23
- import androidx.compose.ui.draw.clip
24
- import androidx.compose.ui.graphics.Color
25
- import androidx.compose.ui.unit.dp
26
- import vn.momo.kits.components.BadgeDot
27
- import vn.momo.kits.components.DotSize
28
- import vn.momo.kits.components.Icon
29
- import vn.momo.kits.const.Colors
30
-
31
- data class HeaderRightData(
32
- val useOnBoarding: Boolean = false,
33
- val buttonOnBoarding: String? = null,
34
- val onPress: (() -> Unit)? = null,
35
- val tintColor: Color = Colors.black_20,
36
- val preventClose: Any? = null,
37
- val useShortcut: Boolean = false,
38
- val useMore: Boolean = false,
39
- val tools: List<ToolGroup> = emptyList(),
40
- val appId: String = ""
41
- )
42
-
43
- data class Tool(
44
- val key: String,
45
- val icon: String,
46
- val onPress: () -> Unit,
47
- val showBadge: Boolean = false
48
- )
49
-
50
- data class ToolGroup(
51
- val items: List<Tool>
52
- )
53
-
54
- data class NavigationButtonConfig(
55
- val icon: String,
56
- val onPress: () -> Unit
57
- )
58
-
59
- object Spacing {
60
- val M = 16.dp
61
- val S = 8.dp
62
- val XXS = 2.dp
63
- }
64
-
65
- @Composable
66
- fun HeaderRight(
67
- headerRight: HeaderRightData? = null,
68
- ) {
69
- Row(
70
- verticalAlignment = Alignment.CenterVertically,
71
- ) {
72
- ToolkitHeaderRight(headerRight = headerRight)
73
- }
74
- }
75
-
76
- @Composable
77
- fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
78
- var isFavorite by remember { mutableStateOf(false) }
79
- val isLoading by remember { mutableStateOf(false) }
80
- val api = PlatformApi.current as? ComposeApi
81
- val context = ApplicationContext.current
82
-
83
- fun onPressShortcut() {
84
- api?.request("onToolAction", mapOf("item" to mapOf("key" to "onFavorite"))) {
85
- isFavorite = !isFavorite
86
- }
87
- }
88
-
89
- fun onPressHelpCenter() {
90
- val paramMap = mapOf(
91
- "appId" to context?.appId,
92
- "code" to context?.code,
93
- "name" to context?.name,
94
- "icon" to context?.icon,
95
- "description" to context?.description
96
- )
97
- api?.request("showHelpCenter", paramMap, { _ -> })
98
- }
99
-
100
- fun onPressClose() {
101
- api?.request("dismiss", "")
102
- }
103
-
104
- fun onPressMore() {
105
- val params = listOf(
106
- headerRight?.tools ?: emptyList<ToolGroup>(),
107
- mapOf(
108
- "appId" to context?.appId,
109
- "code" to context?.code,
110
- "name" to context?.name,
111
- "icon" to context?.icon,
112
- "description" to context?.description
113
- )
114
- )
115
-
116
- api?.request("showTools", params, { _ -> })
117
- }
118
-
119
- fun getNavigationButtonConfig(
120
- tools: List<ToolGroup>? = emptyList(),
121
- useMore: Boolean? = false,
122
- isFavorite: Boolean = false,
123
- onPressShortcut: () -> Unit,
124
- onPressMore: () -> Unit
125
- ): NavigationButtonConfig {
126
- val totalTools = tools?.sumOf { it.items.size } ?: 0
127
- var icon = if (isFavorite) "pin_star_checked" else "pin_star"
128
- var onClickHandler = onPressShortcut
129
- if (totalTools > 1 || useMore == true) {
130
- icon = "navigation_more_icon"
131
- onClickHandler = onPressMore
132
- } else if (totalTools == 1 && !tools.isNullOrEmpty()) {
133
- val singleTool = tools.first().items.firstOrNull()
134
- if (singleTool != null) {
135
- icon = singleTool.icon
136
- onClickHandler = {
137
- singleTool.onPress()
138
- }
139
- }
140
- }
141
- return NavigationButtonConfig(icon, onClickHandler)
142
- }
143
-
144
- val navButtonConfig = getNavigationButtonConfig(
145
- headerRight?.tools,
146
- headerRight?.useMore,
147
- isFavorite,
148
- ::onPressShortcut,
149
- ::onPressMore
150
- )
151
-
152
- val showBadge = headerRight?.tools?.any { group ->
153
- group.items.any { it.showBadge }
154
- }
155
-
156
- Row(
157
- verticalAlignment = Alignment.CenterVertically
158
- ) {
159
- if (headerRight?.useShortcut == true) {
160
- NavigationButton(
161
- disabled = isLoading,
162
- icon = navButtonConfig.icon,
163
- showBadge = showBadge,
164
- onClick = navButtonConfig.onPress
165
- )
166
- }
167
- Row(
168
- verticalAlignment = Alignment.CenterVertically,
169
- horizontalArrangement = Arrangement.Center,
170
- modifier = Modifier
171
- .padding(start = Spacing.S)
172
- .border(0.2.dp, Color(0x33000000), shape = RoundedCornerShape(14.dp))
173
- .width(65.dp)
174
- .height(28.dp)
175
- .clip(shape = RoundedCornerShape(14.dp))
176
- .background(Color(0x99FFFFFF))
177
- ) {
178
- Icon(source = "help_center", size = 20.dp, modifier = Modifier.padding(4.dp).clickable {
179
- onPressHelpCenter()
180
- })
181
- Box(
182
- modifier = Modifier
183
- .width(0.5.dp)
184
- .height(12.dp)
185
- .background(Colors.black_20)
186
- )
187
- Icon(
188
- source = "16_navigation_close_circle",
189
- size = 20.dp,
190
- modifier = Modifier.padding(4.dp).clickable { onPressClose() }
191
- )
192
- }
193
- }
194
- }
195
-
196
- @Composable
197
- fun NavigationButton(
198
- disabled: Boolean,
199
- icon: String,
200
- showBadge: Boolean? = false,
201
- onClick: () -> Unit
202
- ) {
203
- Box(
204
- modifier = Modifier
205
- .size(28.dp)
206
- .clickable(enabled = !disabled, onClick = onClick)
207
- ) {
208
- Box(
209
- modifier = Modifier
210
- .matchParentSize()
211
- .clip(CircleShape)
212
- .background(Color(0x99FFFFFF))
213
- .border(0.2.dp, Color(0x33000000), CircleShape)
214
- )
215
- Box(
216
- modifier = Modifier.matchParentSize(),
217
- contentAlignment = Alignment.Center
218
- ) {
219
- Icon(
220
- source = icon,
221
- size = 16.dp
222
- )
223
- }
224
-
225
- if (showBadge != true) {
226
- BadgeDot(
227
- size = DotSize.Small,
228
- modifier = Modifier
229
- .align(Alignment.TopEnd)
230
- .offset(x = -Spacing.XXS, y = -Spacing.XXS)
231
- )
232
- }
233
- }
234
- }