@momo-kits/native-kits 0.117.1 → 0.117.2-beta.2

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.
@@ -0,0 +1,208 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.clickable
5
+ import androidx.compose.foundation.layout.Arrangement
6
+ import androidx.compose.foundation.layout.Box
7
+ import androidx.compose.foundation.layout.Column
8
+ import androidx.compose.foundation.layout.IntrinsicSize
9
+ import androidx.compose.foundation.layout.Row
10
+ import androidx.compose.foundation.layout.Spacer
11
+ import androidx.compose.foundation.layout.fillMaxHeight
12
+ import androidx.compose.foundation.layout.fillMaxWidth
13
+ import androidx.compose.foundation.layout.height
14
+ import androidx.compose.foundation.layout.padding
15
+ import androidx.compose.foundation.layout.size
16
+ import androidx.compose.foundation.layout.width
17
+ import androidx.compose.foundation.shape.RoundedCornerShape
18
+ import androidx.compose.runtime.Composable
19
+ import androidx.compose.ui.Alignment
20
+ import androidx.compose.ui.Modifier
21
+ import androidx.compose.ui.graphics.Color
22
+ import androidx.compose.ui.text.TextStyle
23
+ import androidx.compose.ui.text.font.FontWeight
24
+ import androidx.compose.ui.unit.dp
25
+ import androidx.compose.ui.unit.sp
26
+ import vn.momo.kits.const.AppTheme
27
+ import vn.momo.kits.const.Colors
28
+ import vn.momo.kits.const.Spacing
29
+ import vn.momo.kits.const.Typography
30
+
31
+ enum class TitleType { Card, Section }
32
+ enum class TitleSize { Small, Medium, Large }
33
+ enum class IconAlign { Top, Center, Bottom }
34
+ enum class ButtonSize { Small, Large }
35
+
36
+ @Composable
37
+ fun Title(
38
+ title: String,
39
+ type: TitleType = TitleType.Section,
40
+ size: TitleSize = TitleSize.Medium,
41
+ icon: String? = null,
42
+ iconColor: Color? = null,
43
+ iconAlign: IconAlign = IconAlign.Top,
44
+ showRightAction: Boolean = false,
45
+ showTrailingAction: Boolean = false,
46
+ badgeLabel: String? = null,
47
+ buttonTitle: String? = null,
48
+ buttonSize: ButtonSize = ButtonSize.Small,
49
+ onPressRightAction: () -> Unit = {},
50
+ onPressTrailingAction: () -> Unit = {},
51
+ textOnly: Boolean = false,
52
+ description: String? = null,
53
+ modifier: Modifier = Modifier
54
+ ) {
55
+ val theme = AppTheme.current
56
+ val textStyle: TextStyle = when (type) {
57
+ TitleType.Card -> when (size) {
58
+ TitleSize.Small -> TextStyle(fontSize = 14.sp, lineHeight = 20.sp)
59
+ TitleSize.Medium -> TextStyle(fontSize = 16.sp, lineHeight = 22.sp)
60
+ TitleSize.Large -> TextStyle(fontSize = 18.sp, lineHeight = 26.sp)
61
+ }
62
+
63
+ TitleType.Section -> when (size) {
64
+ TitleSize.Small -> TextStyle(
65
+ fontSize = 16.sp,
66
+ lineHeight = 24.sp,
67
+ fontWeight = FontWeight.Bold
68
+ )
69
+
70
+ TitleSize.Medium -> TextStyle(
71
+ fontSize = 18.sp,
72
+ lineHeight = 26.sp,
73
+ fontWeight = FontWeight.Bold
74
+ )
75
+
76
+ TitleSize.Large -> TextStyle(
77
+ fontSize = 20.sp,
78
+ lineHeight = 28.sp,
79
+ fontWeight = FontWeight.Bold
80
+ )
81
+ }
82
+ }
83
+
84
+ if (textOnly) {
85
+ Text(
86
+ text = title,
87
+ style = textStyle,
88
+ modifier = modifier
89
+ )
90
+ return
91
+ }
92
+
93
+ Row(
94
+ modifier = modifier
95
+ .fillMaxWidth()
96
+ .height(IntrinsicSize.Min)
97
+ .padding(8.dp),
98
+ verticalAlignment = Alignment.CenterVertically
99
+ ) {
100
+ icon?.let {
101
+ Column(
102
+ modifier = Modifier.fillMaxHeight().padding(end = Spacing.S),
103
+ verticalArrangement = when (iconAlign) {
104
+ IconAlign.Top -> Arrangement.Top
105
+ IconAlign.Center -> Arrangement.Center
106
+ IconAlign.Bottom -> Arrangement.Bottom
107
+ },
108
+ ) {
109
+ // Icon
110
+ Icon(
111
+ source = it,
112
+ color = iconColor ?: theme.colors.text.default,
113
+ modifier = Modifier.size(24.dp)
114
+ )
115
+ }
116
+ }
117
+
118
+ Column(
119
+ modifier = Modifier.weight(1f)
120
+ ) {
121
+ Row(
122
+ verticalAlignment = Alignment.CenterVertically
123
+ ) {
124
+ Row(
125
+ verticalAlignment = Alignment.CenterVertically,
126
+ modifier = if (showTrailingAction) Modifier else Modifier.weight(1f),
127
+ ) {
128
+ Text(
129
+ text = title,
130
+ style = textStyle,
131
+ maxLines = if (showTrailingAction || badgeLabel != null) 1 else 2,
132
+ )
133
+
134
+ // Badge
135
+ badgeLabel?.let {
136
+ Spacer(modifier = Modifier.width(4.dp))
137
+ Badge(
138
+ label = it
139
+ )
140
+ }
141
+
142
+ }
143
+
144
+ // Trailing action (icon)
145
+ if (showTrailingAction && !showRightAction) {
146
+ Spacer(modifier = Modifier.width(8.dp))
147
+ Box(
148
+ modifier = Modifier
149
+ .size(24.dp)
150
+ .background(
151
+ color = if (type == TitleType.Section) Colors.black_06.copy(0.6f) else Colors.black_06.copy(
152
+ 0.3f
153
+ ),
154
+ shape = RoundedCornerShape(12.dp)
155
+ )
156
+ .clickable { onPressTrailingAction() },
157
+ contentAlignment = Alignment.Center
158
+ ) {
159
+ Icon(
160
+ source = "arrow_chevron_right_small",
161
+ size = 18.dp
162
+ )
163
+ }
164
+ }
165
+ }
166
+
167
+ // Description
168
+ description?.let {
169
+ Spacer(modifier = Modifier.height(4.dp))
170
+ Text(
171
+ text = it,
172
+ style = Typography.descriptionDefaultRegular,
173
+ color = theme.colors.text.secondary
174
+ )
175
+ }
176
+ }
177
+
178
+ // Right action (button or icon)
179
+ if (showRightAction && !showTrailingAction) {
180
+ Spacer(modifier = Modifier.width(8.dp))
181
+ if (buttonTitle != null) {
182
+ Text(
183
+ text = buttonTitle,
184
+ style = if (buttonSize === ButtonSize.Small) Typography.actionXsBold else Typography.actionSBold,
185
+ color = theme.colors.primary,
186
+ modifier = Modifier.clickable { onPressRightAction() }
187
+ )
188
+ } else {
189
+ Box(
190
+ modifier = Modifier
191
+ .size(24.dp)
192
+ .background(
193
+ color = Colors.pink_03.copy(alpha = 0.1f),
194
+ shape = RoundedCornerShape(12.dp)
195
+ )
196
+ .clickable { onPressRightAction() },
197
+ contentAlignment = Alignment.Center
198
+ ) {
199
+ Icon(
200
+ source = "arrow_chevron_right_small",
201
+ modifier = Modifier.size(22.dp),
202
+ color = theme.colors.primary
203
+ )
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
@@ -46,15 +46,15 @@ fun getFont(font: String): FontFamily {
46
46
  fun getFontFamily(fontFamily: String, fontWeight: FontWeight? = FontWeight.Normal): FontFamily {
47
47
  val key = "$fontFamily-${fontWeight?.weight}"
48
48
  val fontMap = mapOf(
49
- "SFProText-100" to "sfprotext_thin.otf",
50
- "SFProText-200" to "sfprotext_ultralight.otf",
49
+ "SFProText-100" to "sfprotext_thin.ttf",
50
+ "SFProText-200" to "sfprotext_ultralight.ttf",
51
51
  "SFProText-300" to "sfprotext_light.ttf",
52
52
  "SFProText-400" to "sfprotext_regular.ttf",
53
53
  "SFProText-500" to "sfprotext_medium.ttf",
54
54
  "SFProText-600" to "sfprotext_semibold.ttf",
55
55
  "SFProText-700" to "sfprotext_bold.ttf",
56
56
  "SFProText-800" to "sfprotext_heavy.ttf",
57
- "SFProText-900" to "sfprotext_black.otf",
57
+ "SFProText-900" to "sfprotext_black.ttf",
58
58
 
59
59
  "MoMoSignature-100" to "momosignature.otf",
60
60
  "MoMoSignature-200" to "momosignature.otf",
@@ -45,13 +45,24 @@ func safeAreaTopInset() -> CGFloat {
45
45
  public struct HeaderBackground: View {
46
46
  var headerType: HeaderType = .default
47
47
  var scrollState: CGFloat
48
+ var isTransparent: Bool = false
49
+
50
+ public init(
51
+ headerType: HeaderType = .default,
52
+ scrollState: CGFloat = 0,
53
+ isTransparent: Bool = false
54
+ ) {
55
+ self.headerType = headerType
56
+ self.scrollState = scrollState
57
+ self.isTransparent = isTransparent
58
+ }
48
59
 
49
60
  public var body: some View {
50
61
 
51
62
  let statusBarHeight = safeAreaTopInset()
52
63
 
53
64
  let opacity = 1 - CGFloat(scrollState) / 200
54
- let backgroundColor = scrollState == 0 ? Color.clear : Colors.black01
65
+ let backgroundColor = isTransparent ? Color.clear : Colors.black01
55
66
 
56
67
  Group {
57
68
  if headerType == .default {
@@ -62,7 +73,9 @@ public struct HeaderBackground: View {
62
73
  .fill(backgroundColor)
63
74
  .frame(height: height)
64
75
  .shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
65
- .overlay(Rectangle()
76
+ .overlay(
77
+ isTransparent ? nil :
78
+ Rectangle()
66
79
  .fill(LinearGradient(
67
80
  gradient: Gradient(colors: [
68
81
  Color(red: 1, green: 0.8, blue: 0.87),
@@ -72,7 +85,9 @@ public struct HeaderBackground: View {
72
85
  endPoint: .bottom))
73
86
  .opacity(opacity)
74
87
  .frame(height: height))
75
- .overlay(Rectangle()
88
+ .overlay(
89
+ isTransparent ? nil :
90
+ Rectangle()
76
91
  .fill(Colors.black04)
77
92
  .frame(height: 1)
78
93
  .frame(maxWidth: .infinity)
@@ -111,6 +126,7 @@ public struct Header: View {
111
126
  var animatedHeader: AnimatedHeader? = nil
112
127
  var scrollState: CGFloat = 0
113
128
  var inputSearchProps: InputSearchProps? = nil
129
+ var tintColor: Color? = nil
114
130
 
115
131
  @State private var colorFraction: CGFloat = 0
116
132
 
@@ -123,7 +139,8 @@ public struct Header: View {
123
139
  opacity: CGFloat? = nil,
124
140
  animatedHeader: AnimatedHeader? = nil,
125
141
  scrollState: CGFloat = 0,
126
- inputSearchProps: InputSearchProps? = nil
142
+ inputSearchProps: InputSearchProps? = nil,
143
+ tintColor: Color? = nil
127
144
  ) {
128
145
  self.headerType = headerType
129
146
  self.titlePosition = titlePosition
@@ -134,12 +151,13 @@ public struct Header: View {
134
151
  self.animatedHeader = animatedHeader
135
152
  self.scrollState = scrollState
136
153
  self.inputSearchProps = inputSearchProps
154
+ self.tintColor = tintColor
137
155
  }
138
156
 
139
157
  public var body: some View {
140
158
 
141
- let tintIconColor = Colors.black17
142
- let backgroundButton = Colors.black01.opacity(0.6)
159
+ let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
160
+ let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
143
161
 
144
162
  if headerType != .none {
145
163
  VStack(spacing: 0) {
@@ -148,11 +166,11 @@ public struct Header: View {
148
166
  if let goBack = goBack {
149
167
  SwiftUI.Button(action: goBack) {
150
168
  Circle()
151
- .stroke(Color.black.opacity(0.2), lineWidth: 0.2)
152
- .background(Circle().fill(backgroundButton))
169
+ .stroke(borderColor, lineWidth: 0.2)
170
+ .background(Circle().fill(backgroundButtonColor))
153
171
  .frame(width: 28, height: 28)
154
172
  .overlay(
155
- Icon(source: "arrow-back", size: 20, color: Colors.black17)
173
+ Icon(source: "arrow-back", size: 20, color: tintColor)
156
174
  )
157
175
  }
158
176
 
@@ -196,8 +214,9 @@ public struct Header: View {
196
214
  }
197
215
  } else {
198
216
  Text(title)
199
- .font(Font.system(size: 17).weight(.bold)) .lineSpacing(22)
200
- .foregroundColor(tintIconColor)
217
+ .font(Font.system(size: 17).weight(.bold))
218
+ .lineSpacing(22)
219
+ .foregroundColor(tintColor)
201
220
  .frame(maxWidth: titlePosition == .center ? UIScreen.main.bounds.width - 252 : nil)
202
221
  .frame(maxWidth: titlePosition == .center ? .infinity : UIScreen.main.bounds.width - 172, alignment: titlePosition == .center ? .center : .leading)
203
222
  .lineLimit(1)
@@ -75,20 +75,23 @@ struct NavigationButtonConfig {
75
75
 
76
76
  // MARK: - Components
77
77
  public struct HeaderRight: View {
78
- public init(headerRight: HeaderRightData? = nil) {
78
+ public init(headerRight: HeaderRightData? = nil, tintColor: Color? = nil) {
79
79
  self.headerRight = headerRight
80
+ self.tintColor = tintColor
80
81
  }
81
82
  var headerRight: HeaderRightData?
83
+ var tintColor: Color? = nil
82
84
 
83
85
  public var body: some View {
84
86
  HStack(alignment: .center) {
85
- ToolkitHeaderRight(headerRight: headerRight)
87
+ ToolkitHeaderRight(headerRight: headerRight, tintColor: tintColor)
86
88
  }
87
89
  }
88
90
  }
89
91
 
90
92
  struct ToolkitHeaderRight: View {
91
93
  var headerRight: HeaderRightData?
94
+ var tintColor: Color?
92
95
  @State private var isFavorite: Bool = false
93
96
  @State private var isLoading: Bool = false
94
97
  @EnvironmentObject private var environment: ApplicationEnvironment
@@ -176,44 +179,47 @@ struct ToolkitHeaderRight: View {
176
179
  }
177
180
 
178
181
  var body: some View {
182
+ let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
183
+ let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
179
184
  let navButtonConfig = getNavigationButtonConfig()
180
185
  let showBadge = headerRight?.tools.contains { group in
181
186
  group.items.contains { $0.showBadge }
182
187
  } ?? false
183
188
 
184
- HStack(alignment: .center) {
189
+ HStack(alignment: .center, spacing: 0) {
185
190
  if headerRight?.useShortcut == true {
186
191
  NavigationButton(
187
192
  disabled: isLoading,
188
193
  icon: navButtonConfig.icon,
189
194
  showBadge: showBadge,
190
- onClick: navButtonConfig.onPress
195
+ onClick: navButtonConfig.onPress,
196
+ tintColor: tintColor
191
197
  )
192
198
  }
193
199
 
194
200
  HStack(alignment: .center, spacing: 0) {
195
- Icon(source: "help_center", size: 20)
201
+ Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
196
202
  .padding(4)
197
203
  .onTapGesture {
198
204
  onPressHelpCenter()
199
205
  }
200
206
 
201
207
  Rectangle()
202
- .fill(Colors.black20)
208
+ .fill(tintColor ?? Colors.black20)
203
209
  .frame(width: 0.5, height: 12)
204
210
 
205
- Icon(source: "16_navigation_close_circle", size: 20, color: Colors.black17)
211
+ Icon(source: "16_navigation_close_circle", size: 20, color: tintColor ?? Colors.black17)
206
212
  .padding(4)
207
213
  .onTapGesture {
208
214
  onPressClose()
209
215
  }
210
216
  }
211
217
  .frame(width: 65, height: 28)
212
- .background(Color.white.opacity(0.6))
218
+ .background(backgroundButtonColor)
213
219
  .cornerRadius(14)
214
220
  .overlay(
215
221
  RoundedRectangle(cornerRadius: 14)
216
- .stroke(Color.black.opacity(0.2), lineWidth: 0.2)
222
+ .stroke(borderColor, lineWidth: 0.2)
217
223
  )
218
224
  .padding(.leading, Spacing.S)
219
225
  }
@@ -225,17 +231,21 @@ struct NavigationButton: View {
225
231
  var icon: String
226
232
  var showBadge: Bool
227
233
  var onClick: () -> Void
234
+ var tintColor: Color?
228
235
 
229
236
  var body: some View {
237
+ let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
238
+ let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
239
+
230
240
  ZStack {
231
241
  Circle()
232
- .fill(Color.white.opacity(0.6))
242
+ .fill(backgroundButtonColor)
233
243
  .overlay(
234
244
  Circle()
235
- .stroke(Color.black.opacity(0.2), lineWidth: 0.2)
245
+ .stroke(borderColor, lineWidth: 0.2)
236
246
  )
237
247
 
238
- Icon(source: icon, size: 16)
248
+ Icon(source: icon, size: 16, color: tintColor)
239
249
 
240
250
  if showBadge {
241
251
  BadgeDot(size: .small)
@@ -9,6 +9,8 @@ import SwiftUI
9
9
 
10
10
  public struct Screen<Content: View>: View {
11
11
  var backgroundColor: Color?
12
+ var headerTransparent: Bool = false
13
+ var tintColor: Color? = Colors.black17
12
14
  var isBack: Bool
13
15
  var headerType: HeaderType
14
16
  var verticalAlignment: VerticalAlignment
@@ -29,6 +31,8 @@ public struct Screen<Content: View>: View {
29
31
 
30
32
  public init(
31
33
  backgroundColor: Color? = nil,
34
+ headerTransparent: Bool = false,
35
+ tintColor: Color = Colors.black17,
32
36
  isBack: Bool = true,
33
37
  headerType: HeaderType = .default,
34
38
  verticalAlignment: VerticalAlignment = .top,
@@ -48,6 +52,8 @@ public struct Screen<Content: View>: View {
48
52
  @ViewBuilder content: @escaping () -> Content
49
53
  ) {
50
54
  self.backgroundColor = backgroundColor
55
+ self.headerTransparent = headerTransparent
56
+ self.tintColor = tintColor
51
57
  self.isBack = isBack
52
58
  self.headerType = headerType
53
59
  self.verticalAlignment = verticalAlignment
@@ -70,6 +76,9 @@ public struct Screen<Content: View>: View {
70
76
  @State private var scrollOffset: CGFloat = 0
71
77
 
72
78
  public var body: some View {
79
+
80
+ let headerRightInstance = {HeaderRight(tintColor: tintColor)}
81
+
73
82
  GeometryReader { geometry in
74
83
  let topInset = geometry.safeAreaInsets.top
75
84
  let keyboardHeight: CGFloat = 0
@@ -79,7 +88,8 @@ public struct Screen<Content: View>: View {
79
88
  let screenHeight = height + topInset + bottomInset
80
89
 
81
90
  ZStack {
82
- (backgroundColor ?? Color.white).edgesIgnoringSafeArea(.all)
91
+ (backgroundColor ?? Color.white)
92
+ .edgesIgnoringSafeArea(.top)
83
93
 
84
94
  VStack(spacing: 0) {
85
95
  ZStack(alignment: .top) {
@@ -95,14 +105,15 @@ public struct Screen<Content: View>: View {
95
105
  headerType: headerType,
96
106
  titlePosition: titlePosition,
97
107
  title: title,
98
- headerRight: headerRight,
108
+ headerRight: headerRight ?? headerRightInstance,
99
109
  goBack: goBack,
100
110
  opacity: min(scrollOffset / 114, 1),
101
111
  animatedHeader: animatedHeader,
102
112
  scrollState: scrollOffset,
103
- inputSearchProps: inputSearchProps
113
+ inputSearchProps: inputSearchProps,
114
+ tintColor: tintColor
104
115
  )
105
- .background(HeaderBackground(headerType: headerType, scrollState: scrollOffset).edgesIgnoringSafeArea(.top))
116
+ .background(HeaderBackground(headerType: headerType, scrollState: scrollOffset, isTransparent: headerTransparent).edgesIgnoringSafeArea(.top))
106
117
  .offset(y: topInset)
107
118
 
108
119
  }
@@ -174,6 +185,8 @@ struct Footer: View {
174
185
  VStack {
175
186
  footerView
176
187
  }
188
+ .padding(.vertical, Spacing.S)
189
+ .padding(.horizontal, Spacing.M)
177
190
  .padding(.bottom, keyboardSize)
178
191
  .background(Colors.black01)
179
192
  }
@@ -42,7 +42,7 @@ public struct Button: View {
42
42
  SwiftUI.Button(action: action) {
43
43
  HStack {
44
44
  iconLeft
45
- renderTitle(title: title, type: type, size: size)
45
+ renderTitle(title: title, type: type, size: size).lineLimit(1).truncationMode(.tail)
46
46
  iconRight
47
47
  }
48
48
  .buttonSize(size)
@@ -123,15 +123,15 @@ private extension View {
123
123
  case .large:
124
124
  return self
125
125
  .frame(maxWidth: .infinity, minHeight: 48, maxHeight: 48)
126
- .padding([.leading, .trailing], Spacing.XS)
126
+ .padding([.leading, .trailing], Spacing.L)
127
127
  case .medium:
128
128
  return self
129
129
  .frame(minWidth: 80, maxWidth: .infinity, minHeight: 36, maxHeight: 36)
130
- .padding([.leading, .trailing], Spacing.XS)
130
+ .padding([.leading, .trailing], Spacing.M)
131
131
  case .small:
132
132
  return self
133
133
  .frame(minWidth: 56, maxWidth: 76, minHeight: 28, maxHeight: 28)
134
- .padding([.leading, .trailing], Spacing.XS)
134
+ .padding([.leading, .trailing], Spacing.S)
135
135
  }
136
136
  }
137
137
  }
@@ -27,7 +27,7 @@ public struct ImageView: View {
27
27
  error = false
28
28
  }
29
29
  .resizable()
30
- .renderingMode(.original)
30
+ .renderingMode(color != nil ? .template : .original)
31
31
  .placeholder {
32
32
  Rectangle().foregroundColor(.gray)
33
33
  VStack(alignment: .center, content: {
@@ -49,7 +49,7 @@ public struct ImageView: View {
49
49
  .scaledToFit()
50
50
  .transition(.fade)
51
51
  .aspectRatio(aspectRatio, contentMode: contentMode)
52
- .modifier(ColorMultiplyModifier(color: color))
52
+ .foregroundColor(color)
53
53
  }
54
54
 
55
55
  // MARK: Internal