@momo-kits/native-kits 0.152.4-beta.11 → 0.152.4-beta.13
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/compose/MoMoComposeKits.podspec +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +2 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +12 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -1
- package/ios/Badge/Badge.swift +85 -0
- package/ios/Badge/BadgeRibbon.swift +174 -0
- package/ios/Button/Button.swift +7 -7
- package/package.json +1 -1
|
@@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.width
|
|
|
14
14
|
import androidx.compose.foundation.shape.CircleShape
|
|
15
15
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
16
16
|
import androidx.compose.runtime.Composable
|
|
17
|
+
import androidx.compose.runtime.Stable
|
|
17
18
|
import androidx.compose.runtime.getValue
|
|
18
19
|
import androidx.compose.runtime.mutableStateOf
|
|
19
20
|
import androidx.compose.runtime.remember
|
|
@@ -32,6 +33,7 @@ import vn.momo.kits.components.Icon
|
|
|
32
33
|
import vn.momo.kits.const.AppTheme
|
|
33
34
|
import vn.momo.kits.const.Colors
|
|
34
35
|
|
|
36
|
+
@Stable
|
|
35
37
|
data class HeaderRightData(
|
|
36
38
|
val useShortcut: Boolean = false,
|
|
37
39
|
val useMore: Boolean = false,
|
|
@@ -85,7 +85,6 @@ import vn.momo.kits.modifier.shadow
|
|
|
85
85
|
import vn.momo.kits.utils.getAppStatusBarHeight
|
|
86
86
|
import kotlin.math.max
|
|
87
87
|
|
|
88
|
-
@Deprecated("Use NavigationContainer(StackScreen) instead", ReplaceWith("NavigationContainer(StackScreen)"))
|
|
89
88
|
@Composable
|
|
90
89
|
fun LiteScreen(
|
|
91
90
|
scrollable: Boolean = true,
|
|
@@ -352,7 +351,8 @@ private class LiteScreenHeaderPolicy(
|
|
|
352
351
|
val realConstraints = constraints.copy(
|
|
353
352
|
minWidth = 0,
|
|
354
353
|
minHeight = 0,
|
|
355
|
-
maxWidth = constraints.maxWidth - spacing12 * 2
|
|
354
|
+
maxWidth = (constraints.maxWidth - spacing12 * 2)
|
|
355
|
+
.coerceAtLeast(0),
|
|
356
356
|
)
|
|
357
357
|
val backIconPlaceable =
|
|
358
358
|
measurables.find { it.layoutId == HeaderId.BACK_ID }?.measure(realConstraints)
|
|
@@ -13,6 +13,8 @@ import androidx.compose.ui.semantics.semantics
|
|
|
13
13
|
import androidx.compose.ui.unit.Dp
|
|
14
14
|
import androidx.compose.ui.unit.dp
|
|
15
15
|
import coil3.compose.AsyncImage
|
|
16
|
+
import coil3.compose.LocalPlatformContext
|
|
17
|
+
import coil3.request.ImageRequest
|
|
16
18
|
import vn.momo.kits.const.AppTheme
|
|
17
19
|
import vn.momo.kits.utils.Icons
|
|
18
20
|
import vn.momo.kits.utils.noThemeIcons
|
|
@@ -24,6 +26,9 @@ fun Icon(
|
|
|
24
26
|
color: Color? = AppTheme.current.colors.text.default,
|
|
25
27
|
modifier: Modifier = Modifier,
|
|
26
28
|
) {
|
|
29
|
+
// decode image without downscaling it
|
|
30
|
+
val context = LocalPlatformContext.current
|
|
31
|
+
|
|
27
32
|
val iconUrl = remember(source) {
|
|
28
33
|
if (source.contains("https")) {
|
|
29
34
|
source
|
|
@@ -49,7 +54,13 @@ fun Icon(
|
|
|
49
54
|
) {
|
|
50
55
|
AsyncImage(
|
|
51
56
|
modifier = Modifier.matchParentSize(),
|
|
52
|
-
model =
|
|
57
|
+
model = ImageRequest.Builder(context)
|
|
58
|
+
.data(iconUrl)
|
|
59
|
+
.size(
|
|
60
|
+
coil3.size.Dimension.Undefined,
|
|
61
|
+
coil3.size.Dimension.Undefined
|
|
62
|
+
)
|
|
63
|
+
.build(),
|
|
53
64
|
contentDescription = null,
|
|
54
65
|
contentScale = ContentScale.Fit,
|
|
55
66
|
colorFilter = colorFilter,
|
|
@@ -6,7 +6,6 @@ import androidx.compose.foundation.ScrollState
|
|
|
6
6
|
import androidx.compose.foundation.background
|
|
7
7
|
import androidx.compose.foundation.gestures.ScrollableState
|
|
8
8
|
import androidx.compose.foundation.layout.Box
|
|
9
|
-
import androidx.compose.foundation.layout.BoxScope
|
|
10
9
|
import androidx.compose.foundation.layout.Column
|
|
11
10
|
import androidx.compose.foundation.layout.ColumnScope
|
|
12
11
|
import androidx.compose.foundation.layout.Spacer
|
|
@@ -5,7 +5,6 @@ import androidx.compose.animation.core.tween
|
|
|
5
5
|
import androidx.compose.foundation.layout.Box
|
|
6
6
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
7
7
|
import androidx.compose.foundation.layout.offset
|
|
8
|
-
import androidx.compose.foundation.layout.padding
|
|
9
8
|
import androidx.compose.runtime.Composable
|
|
10
9
|
import androidx.compose.runtime.DisposableEffect
|
|
11
10
|
import androidx.compose.runtime.LaunchedEffect
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
public struct Badge: View {
|
|
4
|
+
let label: String
|
|
5
|
+
let backgroundColor: Color?
|
|
6
|
+
|
|
7
|
+
private var primaryColors: [Color] {
|
|
8
|
+
[
|
|
9
|
+
Colors.black03,
|
|
10
|
+
Colors.pink03,
|
|
11
|
+
Colors.violet03,
|
|
12
|
+
Colors.indigo03,
|
|
13
|
+
Colors.blue03,
|
|
14
|
+
Colors.mint03,
|
|
15
|
+
Colors.green03,
|
|
16
|
+
Colors.lime03,
|
|
17
|
+
Colors.yellow03,
|
|
18
|
+
Colors.gold03,
|
|
19
|
+
Colors.orange03,
|
|
20
|
+
Colors.red03
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public init(label: String = "Label", backgroundColor: Color? = nil) {
|
|
25
|
+
self.label = label
|
|
26
|
+
self.backgroundColor = backgroundColor
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private func isNumber(_ text: String) -> Bool {
|
|
30
|
+
return text.range(of: "^\\d+$", options: .regularExpression) != nil
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private func formatTitle(_ text: String) -> String {
|
|
34
|
+
if isNumber(text), let number = Int(text), number > 99 {
|
|
35
|
+
return "99+"
|
|
36
|
+
}
|
|
37
|
+
return text
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private var badgeColor: Color {
|
|
41
|
+
// If a custom background color is provided, use it
|
|
42
|
+
if let bgColor = backgroundColor {
|
|
43
|
+
return bgColor
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Otherwise, use default colors based on label type
|
|
47
|
+
if isNumber(label) {
|
|
48
|
+
return Colors.red03 // error.primary
|
|
49
|
+
} else {
|
|
50
|
+
return Colors.orange03 // warning.primary
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public var body: some View {
|
|
55
|
+
Text(formatTitle(label))
|
|
56
|
+
.font(.system(size: scaleSize(10), weight: .bold))
|
|
57
|
+
.foregroundColor(Colors.black01)
|
|
58
|
+
.padding(.horizontal, Spacing.XS)
|
|
59
|
+
.frame(minWidth: scaleSize(16), minHeight: scaleSize(16))
|
|
60
|
+
.background(badgeColor)
|
|
61
|
+
.cornerRadius(Radius.M)
|
|
62
|
+
.overlay(
|
|
63
|
+
RoundedRectangle(cornerRadius: Radius.M)
|
|
64
|
+
.stroke(Colors.black01, lineWidth: 1)
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// MARK: - Preview
|
|
70
|
+
#if DEBUG
|
|
71
|
+
struct Badge_Previews: PreviewProvider {
|
|
72
|
+
static var previews: some View {
|
|
73
|
+
VStack(spacing: 20) {
|
|
74
|
+
Badge(label: "5")
|
|
75
|
+
Badge(label: "99")
|
|
76
|
+
Badge(label: "100")
|
|
77
|
+
Badge(label: "New")
|
|
78
|
+
Badge(label: "Hot", backgroundColor: Colors.pink03)
|
|
79
|
+
}
|
|
80
|
+
.padding()
|
|
81
|
+
.previewLayout(.sizeThatFits)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
#endif
|
|
85
|
+
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
public enum RibbonPosition {
|
|
4
|
+
case topLeft
|
|
5
|
+
case topRight
|
|
6
|
+
case bottomLeft
|
|
7
|
+
case bottomRight
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public struct BadgeRibbon: View {
|
|
11
|
+
let position: RibbonPosition
|
|
12
|
+
let label: String
|
|
13
|
+
let isRound: Bool
|
|
14
|
+
let backgroundColor: Color
|
|
15
|
+
|
|
16
|
+
public init(
|
|
17
|
+
position: RibbonPosition = .topRight,
|
|
18
|
+
label: String = "Label",
|
|
19
|
+
isRound: Bool = false,
|
|
20
|
+
backgroundColor: Color? = nil
|
|
21
|
+
) {
|
|
22
|
+
self.position = position
|
|
23
|
+
self.label = label
|
|
24
|
+
self.isRound = isRound
|
|
25
|
+
self.backgroundColor = backgroundColor ?? Colors.orange03
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private var rotation: Angle {
|
|
29
|
+
switch position {
|
|
30
|
+
case .topRight, .bottomRight:
|
|
31
|
+
return .degrees(180)
|
|
32
|
+
case .topLeft, .bottomLeft:
|
|
33
|
+
return .zero
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private var useUpTail: Bool {
|
|
38
|
+
position == .bottomLeft || position == .topRight
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private var verticalAlignment: VerticalAlignment {
|
|
42
|
+
switch position {
|
|
43
|
+
case .topLeft, .bottomRight:
|
|
44
|
+
return .top
|
|
45
|
+
case .bottomLeft, .topRight:
|
|
46
|
+
return .bottom
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public var body: some View {
|
|
51
|
+
HStack(alignment: verticalAlignment, spacing: 0) {
|
|
52
|
+
if useUpTail {
|
|
53
|
+
upTail
|
|
54
|
+
} else {
|
|
55
|
+
downTail
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if isRound {
|
|
59
|
+
roundContent
|
|
60
|
+
} else {
|
|
61
|
+
skewContent
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
.frame(height: RibbonConstants.ribbonHeight)
|
|
65
|
+
.rotationEffect(rotation)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private var roundContent: some View {
|
|
69
|
+
HStack(spacing: 0) {
|
|
70
|
+
Text(label)
|
|
71
|
+
.font(.system(size: 12, weight: .medium))
|
|
72
|
+
.foregroundColor(Colors.black01)
|
|
73
|
+
.lineLimit(1)
|
|
74
|
+
.rotationEffect(rotation)
|
|
75
|
+
}
|
|
76
|
+
.frame(height: RibbonConstants.roundHeight)
|
|
77
|
+
.padding(.trailing, RibbonConstants.roundPaddingEnd)
|
|
78
|
+
.background(
|
|
79
|
+
RoundedRectangle(cornerRadius: Radius.M)
|
|
80
|
+
.fill(backgroundColor)
|
|
81
|
+
.mask(
|
|
82
|
+
HStack(spacing: 0) {
|
|
83
|
+
Rectangle()
|
|
84
|
+
RoundedRectangle(cornerRadius: Radius.M)
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private var skewContent: some View {
|
|
91
|
+
HStack(spacing: 0) {
|
|
92
|
+
Text(label)
|
|
93
|
+
.font(.system(size: 12, weight: .medium))
|
|
94
|
+
.foregroundColor(Colors.black01)
|
|
95
|
+
.lineLimit(1)
|
|
96
|
+
.rotationEffect(rotation)
|
|
97
|
+
.frame(height: RibbonConstants.skewBodyHeight)
|
|
98
|
+
.padding(.horizontal, 8)
|
|
99
|
+
.background(backgroundColor)
|
|
100
|
+
|
|
101
|
+
rightTail
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private var upTail: some View {
|
|
106
|
+
ImageView(
|
|
107
|
+
"https://static.momocdn.net/app/img/kits/utils/Head_down_4x.png",
|
|
108
|
+
placeholder: AnyView(Color.clear)
|
|
109
|
+
)
|
|
110
|
+
.frame(width: RibbonConstants.headTailWidth, height: RibbonConstants.headTailHeight)
|
|
111
|
+
.rotationEffect(.degrees(180))
|
|
112
|
+
.offset(x: 1)
|
|
113
|
+
.clipped()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private var downTail: some View {
|
|
117
|
+
ImageView(
|
|
118
|
+
"https://static.momocdn.net/app/img/kits/utils/Head_4x.png",
|
|
119
|
+
placeholder: AnyView(Color.clear)
|
|
120
|
+
)
|
|
121
|
+
.frame(width: RibbonConstants.headTailWidth, height: RibbonConstants.headTailHeight)
|
|
122
|
+
.offset(x: 1)
|
|
123
|
+
.clipped()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private var rightTail: some View {
|
|
127
|
+
ImageView(
|
|
128
|
+
"https://static.momocdn.net/app/img/kits/utils/Tail_4x.png",
|
|
129
|
+
placeholder: AnyView(Color.clear)
|
|
130
|
+
)
|
|
131
|
+
.frame(width: RibbonConstants.skewTailWidth, height: RibbonConstants.skewTailHeight)
|
|
132
|
+
.offset(x: -2)
|
|
133
|
+
.clipped()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// MARK: - Constants
|
|
138
|
+
private struct RibbonConstants {
|
|
139
|
+
static let ribbonHeight: CGFloat = 20
|
|
140
|
+
static let roundHeight: CGFloat = 16
|
|
141
|
+
static let skewBodyHeight: CGFloat = 16
|
|
142
|
+
static let roundRightRadius: CGFloat = 12
|
|
143
|
+
static let roundPaddingEnd: CGFloat = 6
|
|
144
|
+
static let skewTailWidth: CGFloat = 8
|
|
145
|
+
static let skewTailHeight: CGFloat = 16
|
|
146
|
+
static let headTailWidth: CGFloat = 5
|
|
147
|
+
static let headTailHeight: CGFloat = 20
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// MARK: - String Extension for Text Size
|
|
151
|
+
extension String {
|
|
152
|
+
func size(withAttributes attributes: [NSAttributedString.Key: Any]?) -> CGSize {
|
|
153
|
+
let size = (self as NSString).size(withAttributes: attributes)
|
|
154
|
+
return size
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// MARK: - Preview
|
|
159
|
+
#if DEBUG
|
|
160
|
+
struct BadgeRibbon_Previews: PreviewProvider {
|
|
161
|
+
static var previews: some View {
|
|
162
|
+
VStack(spacing: 30) {
|
|
163
|
+
// Basic ribbon examples
|
|
164
|
+
BadgeRibbon(position: .topRight, label: "New")
|
|
165
|
+
BadgeRibbon(position: .topLeft, label: "Hot", isRound: true)
|
|
166
|
+
BadgeRibbon(position: .bottomLeft, label: "Sale")
|
|
167
|
+
BadgeRibbon(position: .bottomRight, label: "50% OFF", isRound: true)
|
|
168
|
+
}
|
|
169
|
+
.padding()
|
|
170
|
+
.previewLayout(.sizeThatFits)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
#endif
|
|
174
|
+
|
package/ios/Button/Button.swift
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
-
import SwiftUI
|
|
3
2
|
import Lottie
|
|
3
|
+
import SwiftUI
|
|
4
4
|
|
|
5
5
|
// MARK: - ButtonType
|
|
6
6
|
|
|
@@ -33,7 +33,7 @@ public extension ButtonType {
|
|
|
33
33
|
func backgroundColor(loading: Bool) -> Color {
|
|
34
34
|
switch self {
|
|
35
35
|
case .disabled: return Colors.black05.opacity(loading ? 0.75 : 1)
|
|
36
|
-
case .primary:
|
|
36
|
+
case .primary: return Colors.primary.opacity(loading ? 0.75 : 1)
|
|
37
37
|
case .secondary: return Colors.card.opacity(loading ? 0.75 : 1)
|
|
38
38
|
case .outline: return Colors.card.opacity(loading ? 0.75 : 1)
|
|
39
39
|
case .tonal: return Colors.primaryLight.opacity(loading ? 0.75 : 1)
|
|
@@ -116,13 +116,13 @@ extension ButtonSize {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
public struct Button: View {
|
|
119
|
-
|
|
120
119
|
var title: String
|
|
121
120
|
var action: () -> Void
|
|
122
121
|
var type: ButtonType
|
|
123
122
|
var size: ButtonSize
|
|
124
123
|
var iconLeft: AnyView?
|
|
125
124
|
var iconRight: AnyView?
|
|
125
|
+
var isFull: Bool
|
|
126
126
|
var loading: Bool
|
|
127
127
|
|
|
128
128
|
public init(
|
|
@@ -132,6 +132,7 @@ public struct Button: View {
|
|
|
132
132
|
size: ButtonSize = .large,
|
|
133
133
|
iconLeft: AnyView? = nil,
|
|
134
134
|
iconRight: AnyView? = nil,
|
|
135
|
+
isFull: Bool = true,
|
|
135
136
|
loading: Bool = false
|
|
136
137
|
) {
|
|
137
138
|
self.title = title
|
|
@@ -140,6 +141,7 @@ public struct Button: View {
|
|
|
140
141
|
self.size = size
|
|
141
142
|
self.iconLeft = iconLeft
|
|
142
143
|
self.iconRight = iconRight
|
|
144
|
+
self.isFull = isFull
|
|
143
145
|
self.loading = loading
|
|
144
146
|
}
|
|
145
147
|
|
|
@@ -156,7 +158,6 @@ public struct Button: View {
|
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
public var body: some View {
|
|
159
|
-
|
|
160
161
|
let loadingOnLeft = shouldLoadingOnLeft(iconLeft, iconRight)
|
|
161
162
|
|
|
162
163
|
let bg = type.backgroundColor(loading: loading)
|
|
@@ -165,12 +166,11 @@ public struct Button: View {
|
|
|
165
166
|
let borderWidth = type.borderWidth
|
|
166
167
|
|
|
167
168
|
SwiftUI.Button(action: {
|
|
168
|
-
if !loading
|
|
169
|
+
if !loading, type != .disabled {
|
|
169
170
|
action()
|
|
170
171
|
}
|
|
171
172
|
}) {
|
|
172
173
|
HStack(spacing: size.iconSpacing) {
|
|
173
|
-
|
|
174
174
|
if loading && loadingOnLeft {
|
|
175
175
|
LottieView(name: "lottie_circle_loader", loopMode: .loop)
|
|
176
176
|
.frame(width: size.iconSize, height: size.iconSize)
|
|
@@ -195,7 +195,7 @@ public struct Button: View {
|
|
|
195
195
|
iconRight.frame(width: size.iconSize, height: size.iconSize)
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
-
.frame(maxWidth: .infinity)
|
|
198
|
+
.frame(maxWidth: isFull ? .infinity : nil)
|
|
199
199
|
.padding(.horizontal, size.padding)
|
|
200
200
|
.frame(height: size.height)
|
|
201
201
|
.background(bg)
|