@momo-kits/native-kits 0.156.8-sp.1-debug → 0.156.8-sp.2-debug

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.156.8-sp.1-debug"
43
+ version = "0.156.8-sp.2-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -0,0 +1,576 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.Canvas
4
+ import androidx.compose.foundation.background
5
+ import androidx.compose.foundation.border
6
+ import androidx.compose.foundation.clickable
7
+ import androidx.compose.foundation.interaction.MutableInteractionSource
8
+ import androidx.compose.foundation.layout.Arrangement
9
+ import androidx.compose.foundation.layout.Box
10
+ import androidx.compose.foundation.layout.Column
11
+ import androidx.compose.foundation.layout.Row
12
+ import androidx.compose.foundation.layout.Spacer
13
+ import androidx.compose.foundation.layout.offset
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.layout.widthIn
18
+ import androidx.compose.foundation.layout.wrapContentSize
19
+ import androidx.compose.foundation.shape.RoundedCornerShape
20
+ import androidx.compose.runtime.Composable
21
+ import androidx.compose.runtime.LaunchedEffect
22
+ import androidx.compose.runtime.Stable
23
+ import androidx.compose.runtime.getValue
24
+ import androidx.compose.runtime.mutableStateOf
25
+ import androidx.compose.runtime.remember
26
+ import androidx.compose.runtime.setValue
27
+ import androidx.compose.ui.Alignment
28
+ import androidx.compose.ui.Modifier
29
+ import androidx.compose.ui.draw.clip
30
+ import androidx.compose.ui.graphics.Paint
31
+ import androidx.compose.ui.graphics.Path
32
+ import androidx.compose.ui.graphics.PathEffect
33
+ import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
34
+ import androidx.compose.ui.platform.LocalDensity
35
+ import androidx.compose.ui.text.style.TextOverflow
36
+ import androidx.compose.ui.unit.IntOffset
37
+ import androidx.compose.ui.unit.IntRect
38
+ import androidx.compose.ui.unit.IntSize
39
+ import androidx.compose.ui.unit.LayoutDirection
40
+ import androidx.compose.ui.unit.dp
41
+ import androidx.compose.ui.window.Popup
42
+ import androidx.compose.ui.window.PopupPositionProvider
43
+ import androidx.compose.ui.window.PopupProperties
44
+ import vn.momo.kits.application.IsShowBaseLineDebug
45
+ import vn.momo.kits.const.Colors
46
+ import vn.momo.kits.const.Radius
47
+ import vn.momo.kits.const.Spacing
48
+ import vn.momo.kits.const.Typography
49
+ import vn.momo.kits.const.scaleSize
50
+ import vn.momo.kits.modifier.activeOpacityClickable
51
+ import vn.momo.kits.modifier.conditional
52
+
53
+
54
+ // region Types
55
+
56
+ /**
57
+ * Tooltip placement relative to the anchor element.
58
+ */
59
+ enum class TooltipPlacement {
60
+ TOP,
61
+ BOTTOM,
62
+ LEFT,
63
+ RIGHT,
64
+ }
65
+
66
+ /**
67
+ * Cross-axis alignment of the tooltip relative to the anchor.
68
+ */
69
+ enum class TooltipAlign {
70
+ START,
71
+ CENTER,
72
+ END,
73
+ }
74
+
75
+ /**
76
+ * Describes a single action button displayed in the tooltip.
77
+ *
78
+ * @param title Text label for the button (used for text buttons).
79
+ * @param icon Icon source for the button (used for icon buttons).
80
+ * @param onPress Callback invoked when the button is pressed.
81
+ */
82
+ data class TooltipButton(
83
+ val title: String? = null,
84
+ val icon: String? = null,
85
+ val onPress: (() -> Unit)? = null,
86
+ )
87
+
88
+
89
+ /**
90
+ * State holder for controlling tooltip visibility imperatively.
91
+ * Equivalent to the React Native `useImperativeHandle` ref pattern.
92
+ */
93
+ @Stable
94
+ class TooltipState {
95
+ var isVisible by mutableStateOf(false)
96
+ private set
97
+
98
+ /** Shows the tooltip. */
99
+ fun show() {
100
+ isVisible = true
101
+ }
102
+
103
+ /** Hides the tooltip. */
104
+ fun hide() {
105
+ isVisible = false
106
+ }
107
+
108
+ /** Toggles the tooltip visibility. */
109
+ fun toggle() {
110
+ isVisible = !isVisible
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Creates and remembers a [TooltipState].
116
+ */
117
+ @Composable
118
+ fun rememberTooltipState(): TooltipState {
119
+ return remember { TooltipState() }
120
+ }
121
+ private val TOOLTIP_OFFSET = Spacing.S
122
+ private val ARROW_SIZE = 6.dp
123
+ /**
124
+ * Custom [PopupPositionProvider] that positions the tooltip relative to the anchor.
125
+ *
126
+ * Mirrors the React Native `placementStyle` calculation:
127
+ * - TOP: tooltip bottom edge sits at `anchorTop - TOOLTIP_OFFSET`
128
+ * - BOTTOM: tooltip top edge sits at `anchorBottom + TOOLTIP_OFFSET`
129
+ * - LEFT: tooltip right edge sits at `anchorLeft - TOOLTIP_OFFSET`
130
+ * - RIGHT: tooltip left edge sits at `anchorRight + TOOLTIP_OFFSET`
131
+ *
132
+ * Cross-axis alignment:
133
+ * - start: tooltip aligns to anchor start edge
134
+ * - center: tooltip centers on anchor
135
+ * - end: tooltip aligns to anchor end edge
136
+ */
137
+ private class TooltipPositionProvider(
138
+ private val placement: TooltipPlacement,
139
+ private val align: TooltipAlign,
140
+ private val offsetPx: Int,
141
+ ) : PopupPositionProvider {
142
+
143
+ override fun calculatePosition(
144
+ anchorBounds: IntRect,
145
+ windowSize: IntSize,
146
+ layoutDirection: LayoutDirection,
147
+ popupContentSize: IntSize,
148
+ ): IntOffset {
149
+ val anchorX = anchorBounds.left
150
+ val anchorY = anchorBounds.top
151
+ val anchorW = anchorBounds.width
152
+ val anchorH = anchorBounds.height
153
+ val popW = popupContentSize.width
154
+ val popH = popupContentSize.height
155
+
156
+ var x: Int
157
+ var y: Int
158
+
159
+ when (placement) {
160
+ TooltipPlacement.TOP -> {
161
+ y = anchorY - popH - offsetPx
162
+ x = alignHorizontal(anchorX, anchorW, popW)
163
+ }
164
+
165
+ TooltipPlacement.BOTTOM -> {
166
+ y = anchorY + anchorH + offsetPx
167
+ x = alignHorizontal(anchorX, anchorW, popW)
168
+ }
169
+
170
+ TooltipPlacement.LEFT -> {
171
+ x = anchorX - popW - offsetPx
172
+ y = alignVertical(anchorY, anchorH, popH)
173
+ }
174
+
175
+ TooltipPlacement.RIGHT -> {
176
+ x = anchorX + anchorW + offsetPx
177
+ y = alignVertical(anchorY, anchorH, popH)
178
+ }
179
+ }
180
+
181
+ return IntOffset(x, y)
182
+ }
183
+ private fun alignHorizontal(anchorX: Int, anchorW: Int, popW: Int): Int {
184
+ return when (align) {
185
+ TooltipAlign.START -> anchorX
186
+ TooltipAlign.END -> anchorX + anchorW - popW
187
+ TooltipAlign.CENTER -> anchorX + (anchorW - popW) / 2
188
+ }
189
+ }
190
+ private fun alignVertical(anchorY: Int, anchorH: Int, popH: Int): Int {
191
+ return when (align) {
192
+ TooltipAlign.START -> anchorY
193
+ TooltipAlign.END -> anchorY + anchorH - popH
194
+ TooltipAlign.CENTER -> anchorY + (anchorH - popH) / 2
195
+ }
196
+ }
197
+ }
198
+
199
+ // endregion
200
+
201
+ // region Tooltip Composable
202
+
203
+ /**
204
+ * A tooltip component that wraps [content] (the anchor) and shows a positioned
205
+ * tooltip popup with title, description, close button, and action buttons.
206
+ *
207
+ * Port of the React Native `Tooltip` component.
208
+ *
209
+ * @param state Controls visibility (use [rememberTooltipState]).
210
+ * @param title Title text displayed in the tooltip header.
211
+ * @param description Description text shown under the title.
212
+ * @param buttons Action buttons rendered at the bottom.
213
+ * @param placement Tooltip position relative to the anchor (default TOP).
214
+ * @param align Cross-axis alignment (default CENTER).
215
+ * @param onVisibleChange Callback when visibility changes.
216
+ * @param onPressClose Callback when the close button (X) is pressed.
217
+ * @param modifier Modifier for the anchor wrapper.
218
+ * @param content The anchor element that the tooltip is attached to.
219
+ */
220
+ @Composable
221
+ fun Tooltip(
222
+ state: TooltipState,
223
+ title: String? = null,
224
+ description: String? = null,
225
+ buttons: List<TooltipButton> = emptyList(),
226
+ placement: TooltipPlacement = TooltipPlacement.TOP,
227
+ align: TooltipAlign = TooltipAlign.CENTER,
228
+ onVisibleChange: ((Boolean) -> Unit)? = null,
229
+ onPressClose: (() -> Unit)? = null,
230
+ modifier: Modifier = Modifier,
231
+ content: @Composable () -> Unit,
232
+ ) {
233
+ val density = LocalDensity.current
234
+ val offsetPx = with(density) { (TOOLTIP_OFFSET + ARROW_SIZE).roundToPx() }
235
+
236
+ LaunchedEffect(state.isVisible) {
237
+ onVisibleChange?.invoke(state.isVisible)
238
+ }
239
+
240
+ Box(modifier = modifier) {
241
+ content()
242
+
243
+ if (state.isVisible) {
244
+ val positionProvider = remember(placement, align, offsetPx) {
245
+ TooltipPositionProvider(
246
+ placement = placement,
247
+ align = align,
248
+ offsetPx = offsetPx,
249
+ )
250
+ }
251
+
252
+ Popup(
253
+ popupPositionProvider = positionProvider,
254
+ onDismissRequest = { state.hide() },
255
+ properties = PopupProperties(clippingEnabled = false),
256
+ ) {
257
+ TooltipPopupContent(
258
+ title = title,
259
+ description = description,
260
+ buttons = buttons,
261
+ placement = placement,
262
+ align = align,
263
+ onPressClose = onPressClose ?: { state.hide() },
264
+ )
265
+ }
266
+ }
267
+ }
268
+ }
269
+ @Composable
270
+ private fun TooltipPopupContent(
271
+ title: String?,
272
+ description: String?,
273
+ buttons: List<TooltipButton>,
274
+ placement: TooltipPlacement,
275
+ align: TooltipAlign,
276
+ onPressClose: () -> Unit,
277
+ ) {
278
+ val tooltipMaxWidth = 300.dp
279
+ val tooltipShape = remember { RoundedCornerShape(Radius.S) }
280
+
281
+ Box(modifier = Modifier.wrapContentSize()) {
282
+ Column(
283
+ modifier = Modifier
284
+ .widthIn(max = tooltipMaxWidth)
285
+ .background(Colors.black_17, tooltipShape)
286
+ .clip(tooltipShape)
287
+ .conditional(IsShowBaseLineDebug) {
288
+ border(1.dp, Colors.blue_03)
289
+ }
290
+ .padding(Spacing.M)
291
+ ) {
292
+ Row {
293
+ Column(modifier = Modifier.weight(1f, fill = false)) {
294
+ if (!title.isNullOrEmpty()) {
295
+ Text(
296
+ text = title,
297
+ style = Typography.headerSSemibold,
298
+ color = Colors.black_01,
299
+ maxLines = 1,
300
+ modifier = Modifier.padding(bottom = Spacing.XS),
301
+ overflow = TextOverflow.Ellipsis,
302
+ )
303
+ }
304
+ if (!description.isNullOrEmpty()) {
305
+ Text(
306
+ text = description,
307
+ style = Typography.descriptionDefaultRegular,
308
+ color = Colors.black_01,
309
+ maxLines = 2,
310
+ modifier = Modifier.padding(bottom = Spacing.M),
311
+ overflow = TextOverflow.Ellipsis,
312
+ )
313
+ }
314
+ }
315
+ Spacer(Modifier.width(Spacing.M))
316
+ Box(
317
+ modifier = Modifier
318
+ .size(20.dp)
319
+ .activeOpacityClickable {
320
+ onPressClose.invoke()
321
+ }
322
+ ) {
323
+ Icon(
324
+ source = "navigation_close",
325
+ size = 20.dp,
326
+ color = Colors.black_01,
327
+ )
328
+ }
329
+ }
330
+ if (buttons.isNotEmpty()) {
331
+ TooltipButtons(
332
+ buttons = buttons,
333
+ modifier = Modifier.align(Alignment.End),
334
+ )
335
+ }
336
+ }
337
+
338
+ TooltipArrow(
339
+ placement = placement,
340
+ align = align,
341
+ modifier = Modifier.matchParentSize(),
342
+ )
343
+ }
344
+ }
345
+ @Composable
346
+ private fun TooltipArrow(
347
+ placement: TooltipPlacement,
348
+ align: TooltipAlign,
349
+ modifier: Modifier = Modifier,
350
+ ) {
351
+ val arrowWidth = ARROW_SIZE * 2
352
+ val arrowHeight = ARROW_SIZE
353
+ val arrowEdgeMargin = Spacing.M
354
+
355
+ val boxAlignment = when (placement) {
356
+ TooltipPlacement.TOP -> when (align) {
357
+ TooltipAlign.START -> Alignment.BottomStart
358
+ TooltipAlign.CENTER -> Alignment.BottomCenter
359
+ TooltipAlign.END -> Alignment.BottomEnd
360
+ }
361
+
362
+ TooltipPlacement.BOTTOM -> when (align) {
363
+ TooltipAlign.START -> Alignment.TopStart
364
+ TooltipAlign.CENTER -> Alignment.TopCenter
365
+ TooltipAlign.END -> Alignment.TopEnd
366
+ }
367
+
368
+ TooltipPlacement.LEFT -> when (align) {
369
+ TooltipAlign.START -> Alignment.TopEnd
370
+ TooltipAlign.CENTER -> Alignment.CenterEnd
371
+ TooltipAlign.END -> Alignment.BottomEnd
372
+ }
373
+
374
+ TooltipPlacement.RIGHT -> when (align) {
375
+ TooltipAlign.START -> Alignment.TopStart
376
+ TooltipAlign.CENTER -> Alignment.CenterStart
377
+ TooltipAlign.END -> Alignment.BottomStart
378
+ }
379
+ }
380
+
381
+ val canvasWidth = when (placement) {
382
+ TooltipPlacement.TOP, TooltipPlacement.BOTTOM -> arrowWidth
383
+ TooltipPlacement.LEFT, TooltipPlacement.RIGHT -> arrowHeight
384
+ }
385
+ val canvasHeight = when (placement) {
386
+ TooltipPlacement.TOP, TooltipPlacement.BOTTOM -> arrowHeight
387
+ TooltipPlacement.LEFT, TooltipPlacement.RIGHT -> arrowWidth
388
+ }
389
+
390
+ val arrowOverlap = 1.dp
391
+ val offsetX = when (placement) {
392
+ TooltipPlacement.LEFT -> arrowHeight - arrowOverlap
393
+ TooltipPlacement.RIGHT -> -arrowHeight + arrowOverlap
394
+ else -> when (align) {
395
+ TooltipAlign.START -> arrowEdgeMargin
396
+ TooltipAlign.END -> -arrowEdgeMargin
397
+ TooltipAlign.CENTER -> 0.dp
398
+ }
399
+ }
400
+ val offsetY = when (placement) {
401
+ TooltipPlacement.TOP -> arrowHeight - arrowOverlap
402
+ TooltipPlacement.BOTTOM -> -arrowHeight + arrowOverlap
403
+ else -> when (align) {
404
+ TooltipAlign.START -> arrowEdgeMargin
405
+ TooltipAlign.END -> -arrowEdgeMargin
406
+ TooltipAlign.CENTER -> 0.dp
407
+ }
408
+ }
409
+
410
+ val arrowColor = Colors.black_17
411
+
412
+ Box(modifier = modifier) {
413
+ Canvas(
414
+ modifier = Modifier
415
+ .align(boxAlignment)
416
+ .offset(x = offsetX, y = offsetY)
417
+ .size(width = canvasWidth, height = canvasHeight)
418
+ ) {
419
+ val w = size.width
420
+ val h = size.height
421
+ val path = Path().apply {
422
+ when (placement) {
423
+ TooltipPlacement.TOP -> {
424
+ moveTo(0f, 0f)
425
+ lineTo(w, 0f)
426
+ lineTo(w / 2f, h)
427
+ close()
428
+ }
429
+ TooltipPlacement.BOTTOM -> {
430
+ moveTo(0f, h)
431
+ lineTo(w, h)
432
+ lineTo(w / 2f, 0f)
433
+ close()
434
+ }
435
+ TooltipPlacement.LEFT -> {
436
+ moveTo(0f, 0f)
437
+ lineTo(0f, h)
438
+ lineTo(w, h / 2f)
439
+ close()
440
+ }
441
+ TooltipPlacement.RIGHT -> {
442
+ moveTo(w, 0f)
443
+ lineTo(w, h)
444
+ lineTo(0f, h / 2f)
445
+ close()
446
+ }
447
+ }
448
+ }
449
+ drawIntoCanvas { canvas ->
450
+ val paint = Paint().apply {
451
+ color = arrowColor
452
+ pathEffect = PathEffect.cornerPathEffect(2.dp.toPx())
453
+ }
454
+ canvas.drawPath(path, paint)
455
+ }
456
+ }
457
+ }
458
+ }
459
+ @Composable
460
+ private fun TooltipButtons(buttons: List<TooltipButton>, modifier: Modifier = Modifier) {
461
+ Row(
462
+ modifier = modifier,
463
+ horizontalArrangement = Arrangement.End,
464
+ verticalAlignment = Alignment.CenterVertically,
465
+ ) {
466
+
467
+ if (buttons.size == 1) {
468
+ val btn = buttons[0]
469
+ TooltipSingleButton(btn)
470
+ } else if (buttons.size == 2) {
471
+ val primary = buttons[0]
472
+ val secondary = buttons[1]
473
+ val bothIcons = primary.icon != null && secondary.icon != null
474
+
475
+ if (bothIcons) {
476
+ TooltipIconButton(
477
+ icon = secondary.icon,
478
+ onPress = secondary.onPress ?: {},
479
+ )
480
+ Spacer(modifier = Modifier.width(Spacing.S))
481
+ TooltipIconButton(
482
+ icon = primary.icon,
483
+ onPress = primary.onPress ?: {},
484
+ )
485
+ } else {
486
+ TooltipSecondaryButton(
487
+ title = secondary.title ?: "",
488
+ onPress = secondary.onPress ?: {},
489
+ )
490
+ Spacer(modifier = Modifier.width(Spacing.S))
491
+ TooltipPrimaryButton(
492
+ title = primary.title ?: "",
493
+ onPress = primary.onPress ?: {},
494
+ )
495
+ }
496
+ } else {
497
+ buttons.forEachIndexed { index, btn ->
498
+ if (index > 0) {
499
+ Spacer(modifier = Modifier.width(Spacing.XXS))
500
+ }
501
+ TooltipSingleButton(btn)
502
+ }
503
+ }
504
+ }
505
+ }
506
+
507
+ @Composable
508
+ private fun TooltipSingleButton(btn: TooltipButton) {
509
+ if (btn.icon != null) {
510
+ TooltipIconButton(
511
+ icon = btn.icon,
512
+ onPress = btn.onPress ?: {},
513
+ )
514
+ } else {
515
+ TooltipPrimaryButton(
516
+ title = btn.title ?: "",
517
+ onPress = btn.onPress ?: {},
518
+ )
519
+ }
520
+ }
521
+ @Composable
522
+ private fun TooltipPrimaryButton(
523
+ title: String,
524
+ onPress: () -> Unit,
525
+ ) {
526
+ Button(
527
+ onClick = onPress,
528
+ title = title,
529
+ type = ButtonType.SECONDARY,
530
+ size = Size.MEDIUM,
531
+ isFull = false,
532
+ )
533
+ }
534
+ @Composable
535
+ private fun TooltipSecondaryButton(
536
+ title: String,
537
+ onPress: () -> Unit,
538
+ ) {
539
+ Box(
540
+ modifier = Modifier
541
+ .clickable(
542
+ interactionSource = remember { MutableInteractionSource() },
543
+ indication = null,
544
+ onClick = onPress,
545
+ )
546
+ .padding(horizontal = Spacing.M, vertical = Spacing.S),
547
+ ) {
548
+ Text(
549
+ text = title,
550
+ color = Colors.black_01,
551
+ style = Typography.actionSBold,
552
+ )
553
+ }
554
+ }
555
+ @Composable
556
+ private fun TooltipIconButton(
557
+ icon: String,
558
+ onPress: () -> Unit,
559
+ ) {
560
+ val iconButtonSize = scaleSize(36f).dp
561
+
562
+ Box(
563
+ modifier = Modifier
564
+ .size(iconButtonSize)
565
+ .background(Colors.black_01, RoundedCornerShape(Radius.XL))
566
+ .clip(RoundedCornerShape(Radius.XL))
567
+ .clickable(
568
+ interactionSource = remember { MutableInteractionSource() },
569
+ indication = null,
570
+ onClick = onPress,
571
+ ),
572
+ contentAlignment = Alignment.Center,
573
+ ) {
574
+ Icon(source = icon)
575
+ }
576
+ }
package/gradle.properties CHANGED
@@ -18,7 +18,7 @@ kotlin.apple.xcodeCompatibility.nowarn=true
18
18
  name="ComposeKits"
19
19
  group=vn.momo.kits
20
20
  artifact.id=kits
21
- version=0.156.8-sp.1
21
+ version=0.156.8-sp.2
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.156.8-sp.1-debug",
3
+ "version": "0.156.8-sp.2-debug",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},
package/publish.sh CHANGED
@@ -185,7 +185,32 @@ phase_publish_maven() {
185
185
 
186
186
  ./gradlew :compose:publishAllPublicationsToGitLabPackagesRepository
187
187
 
188
- echo "✅ Maven publishing completed successfully!"
188
+ echo "✅ compose Maven publishing completed successfully!"
189
+
190
+ echo "📦 Publishing sample/shared KMP artifacts to Maven (version ${VERSION})..."
191
+
192
+ # Temporarily remove composeResources and compose { resources { ... } } block
193
+ echo "📦 Backing up sample/shared composeResources and build.gradle.kts..."
194
+ if [ -d "sample/shared/src/commonMain/composeResources" ]; then
195
+ mv sample/shared/src/commonMain/composeResources sample/shared/src/commonMain/composeResources.backup
196
+ fi
197
+ cp sample/shared/build.gradle.kts sample/shared/build.gradle.kts.backup
198
+ if [[ "$OSTYPE" == "darwin"* ]]; then
199
+ sed -i '' '/^compose {$/,/^}$/d' sample/shared/build.gradle.kts
200
+ else
201
+ sed -i '/^compose {$/,/^}$/d' sample/shared/build.gradle.kts
202
+ fi
203
+
204
+ ./gradlew :sample:shared:publishAllPublicationsToGitLabPackagesRepository
205
+
206
+ # Restore composeResources and build.gradle.kts
207
+ echo "📦 Restoring sample/shared composeResources and build.gradle.kts..."
208
+ mv sample/shared/build.gradle.kts.backup sample/shared/build.gradle.kts
209
+ if [ -d "sample/shared/src/commonMain/composeResources.backup" ]; then
210
+ mv sample/shared/src/commonMain/composeResources.backup sample/shared/src/commonMain/composeResources
211
+ fi
212
+
213
+ echo "✅ sample/shared Maven publishing completed successfully!"
189
214
  echo "✅ PHASE 2 COMPLETED"
190
215
  echo ""
191
216
  }