@capgo/capacitor-pretty-toast 8.1.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.
Files changed (85) hide show
  1. package/CapgoCapacitorPrettyToast.podspec +17 -0
  2. package/LICENSE +373 -0
  3. package/Package.swift +28 -0
  4. package/README.md +341 -0
  5. package/android/build.gradle +71 -0
  6. package/android/src/main/AndroidManifest.xml +6 -0
  7. package/android/src/main/java/com/toast/PrettyToastPlugin.kt +197 -0
  8. package/android/src/main/java/com/toast/ToastOverlay.kt +495 -0
  9. package/android/src/main/java/com/toast/anim/CutoutMorphAnimator.kt +235 -0
  10. package/android/src/main/java/com/toast/anim/SlideAnimator.kt +64 -0
  11. package/android/src/main/java/com/toast/anim/ToastAnimator.kt +23 -0
  12. package/android/src/main/java/com/toast/backdrop/BackdropSampler.kt +142 -0
  13. package/android/src/main/java/com/toast/backdrop/OutlineController.kt +100 -0
  14. package/android/src/main/java/com/toast/cutout/CutoutDetector.kt +88 -0
  15. package/android/src/main/java/com/toast/cutout/CutoutInfo.kt +28 -0
  16. package/android/src/main/java/com/toast/gesture/ToastGestureHandler.kt +68 -0
  17. package/android/src/main/java/com/toast/ui/IconMapper.kt +26 -0
  18. package/android/src/main/java/com/toast/ui/PassThroughFrameLayout.kt +53 -0
  19. package/android/src/main/java/com/toast/ui/ToastViewFactory.kt +224 -0
  20. package/android/src/main/java/com/toast/util/Density.kt +17 -0
  21. package/android/src/main/java/com/toast/util/StatusBarController.kt +24 -0
  22. package/android/src/main/java/com/toast/util/ToastConstants.kt +36 -0
  23. package/android/src/main/res/.gitkeep +0 -0
  24. package/android/src/main/res/drawable/ic_arrow_downward.xml +9 -0
  25. package/android/src/main/res/drawable/ic_arrow_upward.xml +9 -0
  26. package/android/src/main/res/drawable/ic_cancel.xml +9 -0
  27. package/android/src/main/res/drawable/ic_check_circle.xml +9 -0
  28. package/android/src/main/res/drawable/ic_favorite.xml +9 -0
  29. package/android/src/main/res/drawable/ic_info.xml +9 -0
  30. package/android/src/main/res/drawable/ic_mail.xml +9 -0
  31. package/android/src/main/res/drawable/ic_notifications.xml +9 -0
  32. package/android/src/main/res/drawable/ic_touch_app.xml +9 -0
  33. package/android/src/main/res/drawable/ic_warning.xml +9 -0
  34. package/android/src/main/res/drawable/ic_wifi.xml +9 -0
  35. package/android/src/main/res/values/colors.xml +3 -0
  36. package/android/src/main/res/values/strings.xml +3 -0
  37. package/android/src/main/res/values/styles.xml +3 -0
  38. package/android/src/test/java/com/toast/PrettyToastPluginTest.kt +26 -0
  39. package/dist/docs.json +459 -0
  40. package/dist/esm/controller.d.ts +30 -0
  41. package/dist/esm/controller.js +271 -0
  42. package/dist/esm/controller.js.map +1 -0
  43. package/dist/esm/definitions.d.ts +144 -0
  44. package/dist/esm/definitions.js +2 -0
  45. package/dist/esm/definitions.js.map +1 -0
  46. package/dist/esm/driver.d.ts +19 -0
  47. package/dist/esm/driver.js +24 -0
  48. package/dist/esm/driver.js.map +1 -0
  49. package/dist/esm/icons.d.ts +14 -0
  50. package/dist/esm/icons.js +138 -0
  51. package/dist/esm/icons.js.map +1 -0
  52. package/dist/esm/index.d.ts +2 -0
  53. package/dist/esm/index.js +2 -0
  54. package/dist/esm/index.js.map +1 -0
  55. package/dist/esm/internal-plugin.d.ts +2 -0
  56. package/dist/esm/internal-plugin.js +5 -0
  57. package/dist/esm/internal-plugin.js.map +1 -0
  58. package/dist/esm/internal-types.d.ts +31 -0
  59. package/dist/esm/internal-types.js +2 -0
  60. package/dist/esm/internal-types.js.map +1 -0
  61. package/dist/esm/toast.d.ts +1 -0
  62. package/dist/esm/toast.js +5 -0
  63. package/dist/esm/toast.js.map +1 -0
  64. package/dist/esm/web-renderer.d.ts +36 -0
  65. package/dist/esm/web-renderer.js +296 -0
  66. package/dist/esm/web-renderer.js.map +1 -0
  67. package/dist/esm/web.d.ts +10 -0
  68. package/dist/esm/web.js +28 -0
  69. package/dist/esm/web.js.map +1 -0
  70. package/dist/plugin.cjs.js +770 -0
  71. package/dist/plugin.cjs.js.map +1 -0
  72. package/dist/plugin.js +773 -0
  73. package/dist/plugin.js.map +1 -0
  74. package/ios/Sources/PrettyToastPlugin/CustomHostingView.swift +13 -0
  75. package/ios/Sources/PrettyToastPlugin/PassThroughWindow.swift +143 -0
  76. package/ios/Sources/PrettyToastPlugin/PrettyToastColorParser.swift +94 -0
  77. package/ios/Sources/PrettyToastPlugin/PrettyToastPlugin.swift +138 -0
  78. package/ios/Sources/PrettyToastPlugin/PrettyToastView.swift +267 -0
  79. package/ios/Sources/PrettyToastPlugin/Toast.swift +29 -0
  80. package/ios/Sources/PrettyToastPlugin/ToastManager.swift +392 -0
  81. package/ios/Tests/PrettyToastPluginTests/PrettyToastPluginTests.swift +21 -0
  82. package/package.json +98 -0
  83. package/scripts/check-capacitor-plugin-wiring.mjs +254 -0
  84. package/scripts/deploy-example-capgo.mjs +86 -0
  85. package/scripts/test-ios.sh +14 -0
@@ -0,0 +1,68 @@
1
+ package com.toast.gesture
2
+
3
+ import android.view.MotionEvent
4
+ import android.widget.LinearLayout
5
+ import com.toast.anim.ToastAnimator
6
+ import com.toast.util.Density
7
+ import kotlin.math.abs
8
+
9
+ /**
10
+ * Installs an OnTouchListener on the pill that routes drag updates to
11
+ * the active [ToastAnimator] and decides between dismiss / tap / snap-back
12
+ * on release.
13
+ *
14
+ * Thresholds:
15
+ * - Any upward drag > 15dp → dismiss.
16
+ * - Finger movement ≤ 8dp in both axes → tap (`onPress`).
17
+ * - Anything else → snap-back.
18
+ */
19
+ class ToastGestureHandler(
20
+ private val animator: ToastAnimator,
21
+ private val density: Density,
22
+ private val enableSwipeDismiss: Boolean,
23
+ private val onDismissRequested: () -> Unit,
24
+ private val onPress: () -> Unit,
25
+ ) {
26
+ private var touchStartX = 0f
27
+ private var touchStartY = 0f
28
+ private var pillTranslationYOnDown = 0f
29
+
30
+ fun install(pill: LinearLayout) {
31
+ pill.setOnTouchListener { _, event ->
32
+ when (event.action) {
33
+ MotionEvent.ACTION_DOWN -> {
34
+ touchStartX = event.rawX
35
+ touchStartY = event.rawY
36
+ pillTranslationYOnDown = pill.translationY
37
+ true
38
+ }
39
+ MotionEvent.ACTION_MOVE -> {
40
+ if (enableSwipeDismiss) {
41
+ val dy = event.rawY - touchStartY
42
+ animator.applyDrag(dy, pillTranslationYOnDown)
43
+ }
44
+ true
45
+ }
46
+ MotionEvent.ACTION_UP -> {
47
+ val dy = event.rawY - touchStartY
48
+ val dx = event.rawX - touchStartX
49
+
50
+ if (enableSwipeDismiss && dy < -density.dp(15f)) {
51
+ onDismissRequested()
52
+ } else if (abs(dy) < density.dp(8f) && abs(dx) < density.dp(8f)) {
53
+ onPress()
54
+ animator.snapBack()
55
+ } else {
56
+ animator.snapBack()
57
+ }
58
+ true
59
+ }
60
+ MotionEvent.ACTION_CANCEL -> {
61
+ animator.snapBack()
62
+ true
63
+ }
64
+ else -> false
65
+ }
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,26 @@
1
+ package com.toast.ui
2
+
3
+ import android.graphics.Color
4
+ import com.toast.R
5
+
6
+ /**
7
+ * Resolves an iOS-style SF Symbol name (e.g. "checkmark.circle.fill") to a
8
+ * bundled drawable resource + tint color. Mapping is deliberately loose
9
+ * (substring matching) so the JS side can pass any symbol that contains
10
+ * the relevant keyword.
11
+ */
12
+ object IconMapper {
13
+ fun map(symbolName: String): Pair<Int, Int> = when {
14
+ symbolName.contains("checkmark") -> R.drawable.ic_check_circle to Color.parseColor("#4CAF50")
15
+ symbolName.contains("xmark") -> R.drawable.ic_cancel to Color.parseColor("#F44336")
16
+ symbolName.contains("exclamation") -> R.drawable.ic_warning to Color.parseColor("#FF9800")
17
+ symbolName.contains("info") -> R.drawable.ic_info to Color.parseColor("#2196F3")
18
+ symbolName.contains("heart") -> R.drawable.ic_favorite to Color.parseColor("#E91E63")
19
+ symbolName.contains("arrow.down") -> R.drawable.ic_arrow_downward to Color.parseColor("#2196F3")
20
+ symbolName.contains("arrow") -> R.drawable.ic_arrow_upward to Color.parseColor("#2196F3")
21
+ symbolName.contains("envelope") || symbolName.contains("mail") -> R.drawable.ic_mail to Color.parseColor("#2196F3")
22
+ symbolName.contains("wifi") -> R.drawable.ic_wifi to Color.WHITE
23
+ symbolName.contains("hand") || symbolName.contains("tap") -> R.drawable.ic_touch_app to Color.WHITE
24
+ else -> R.drawable.ic_notifications to Color.GRAY
25
+ }
26
+ }
@@ -0,0 +1,53 @@
1
+ package com.toast.ui
2
+
3
+ import android.content.Context
4
+ import android.graphics.Rect
5
+ import android.view.MotionEvent
6
+ import android.view.View
7
+ import android.widget.FrameLayout
8
+
9
+ /**
10
+ * FrameLayout that passes through touches outside the pill area so the
11
+ * host activity still receives taps underneath the transparent overlay.
12
+ */
13
+ class PassThroughFrameLayout(context: Context) : FrameLayout(context) {
14
+ var pillView: View? = null
15
+
16
+ /**
17
+ * Tracks whether the in-flight gesture started on the pill. Once a DOWN
18
+ * landed on the pill, every subsequent MOVE / UP / CANCEL of that gesture
19
+ * must be forwarded — even if the finger moves outside the pill's (now
20
+ * shrinking) bounds — otherwise the drag morph freezes and ACTION_UP
21
+ * never fires.
22
+ */
23
+ private var isTrackingPillGesture = false
24
+ private val pillRect = Rect()
25
+ private val location = IntArray(2)
26
+
27
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
28
+ val pill = pillView ?: return false
29
+
30
+ when (ev.actionMasked) {
31
+ MotionEvent.ACTION_DOWN -> {
32
+ pill.getLocationOnScreen(location)
33
+ pillRect.set(
34
+ location[0],
35
+ location[1],
36
+ location[0] + pill.width,
37
+ location[1] + pill.height
38
+ )
39
+ isTrackingPillGesture = pillRect.contains(ev.rawX.toInt(), ev.rawY.toInt())
40
+ return if (isTrackingPillGesture) super.dispatchTouchEvent(ev) else false
41
+ }
42
+ MotionEvent.ACTION_MOVE -> {
43
+ return if (isTrackingPillGesture) super.dispatchTouchEvent(ev) else false
44
+ }
45
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
46
+ val wasTracking = isTrackingPillGesture
47
+ isTrackingPillGesture = false
48
+ return if (wasTracking) super.dispatchTouchEvent(ev) else false
49
+ }
50
+ }
51
+ return if (isTrackingPillGesture) super.dispatchTouchEvent(ev) else false
52
+ }
53
+ }
@@ -0,0 +1,224 @@
1
+ package com.toast.ui
2
+
3
+ import android.app.Activity
4
+ import android.graphics.Color
5
+ import android.graphics.Typeface
6
+ import android.graphics.drawable.GradientDrawable
7
+ import android.util.TypedValue
8
+ import android.view.Gravity
9
+ import android.view.ViewGroup
10
+ import android.widget.FrameLayout
11
+ import android.widget.ImageView
12
+ import android.widget.LinearLayout
13
+ import android.widget.TextView
14
+ import com.toast.cutout.CutoutInfo
15
+ import com.toast.util.Density
16
+ import kotlin.math.sqrt
17
+
18
+ /**
19
+ * Builds the full toast view hierarchy (container → pill → content →
20
+ * icon + title/message) and computes the position/size/corner-radius
21
+ * geometry up-front so the animator has stable values to work with.
22
+ */
23
+ class ToastViewFactory(
24
+ private val activity: Activity,
25
+ private val density: Density,
26
+ ) {
27
+
28
+ /** Everything the overlay needs to animate & populate the toast. */
29
+ data class Built(
30
+ val container: PassThroughFrameLayout,
31
+ val pill: LinearLayout,
32
+ val content: LinearLayout,
33
+ val icon: ImageView,
34
+ val title: TextView,
35
+ val message: TextView,
36
+ val actionButton: TextView,
37
+ /** Resting corner radius of the expanded pill (px). */
38
+ val expandedCornerRadius: Float,
39
+ /** Mutable background so the outline stroke can be crossfaded. */
40
+ val pillBackground: GradientDrawable,
41
+ /** Stroke line width (px), mirroring iOS's 2 pt. */
42
+ val strokeWidthPx: Int,
43
+ )
44
+
45
+ fun build(info: CutoutInfo): Built {
46
+ val screenWidth = activity.resources.displayMetrics.widthPixels
47
+
48
+ val container = PassThroughFrameLayout(activity).apply {
49
+ layoutParams = FrameLayout.LayoutParams(
50
+ FrameLayout.LayoutParams.MATCH_PARENT,
51
+ FrameLayout.LayoutParams.MATCH_PARENT
52
+ )
53
+ visibility = ViewGroup.GONE
54
+ }
55
+
56
+ val strokeWidthPx = density.dpInt(2f)
57
+ val pillBackground = GradientDrawable().apply {
58
+ setColor(Color.BLACK)
59
+ cornerRadius = density.dp(30f)
60
+ // Reserve space for the outline from the start — stroke colour
61
+ // is updated later by the backdrop sampler. Start transparent so
62
+ // the initial frame has no visible outline.
63
+ setStroke(strokeWidthPx, Color.TRANSPARENT)
64
+ }
65
+ val pill = LinearLayout(activity).apply {
66
+ orientation = LinearLayout.VERTICAL
67
+ background = pillBackground
68
+ elevation = density.dp(8f)
69
+ }
70
+
71
+ val topMargin = computeTopMargin(info)
72
+ val pillMargin = computePillMargin(info.screenCornerRadius, topMargin)
73
+ val pillWidth = screenWidth - pillMargin * 2
74
+
75
+ val expandedCornerRadius = computeExpandedCornerRadius(info, pillMargin, topMargin)
76
+ pillBackground.cornerRadius = expandedCornerRadius
77
+
78
+ pill.layoutParams = FrameLayout.LayoutParams(
79
+ pillWidth,
80
+ FrameLayout.LayoutParams.WRAP_CONTENT
81
+ ).apply {
82
+ gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
83
+ this.topMargin = topMargin
84
+ }
85
+
86
+ val content = buildContent(info, topMargin)
87
+ val icon = buildIcon()
88
+ val textContainer = buildTextContainer()
89
+ val title = buildTitle()
90
+ val message = buildMessage()
91
+ val actionButton = buildActionButton()
92
+
93
+ textContainer.addView(title)
94
+ textContainer.addView(message)
95
+ content.addView(icon)
96
+ content.addView(textContainer)
97
+ content.addView(actionButton)
98
+ pill.addView(content)
99
+ container.addView(pill)
100
+ container.pillView = pill
101
+
102
+ return Built(
103
+ container = container,
104
+ pill = pill,
105
+ content = content,
106
+ icon = icon,
107
+ title = title,
108
+ message = message,
109
+ actionButton = actionButton,
110
+ expandedCornerRadius = expandedCornerRadius,
111
+ pillBackground = pillBackground,
112
+ strokeWidthPx = strokeWidthPx,
113
+ )
114
+ }
115
+
116
+ /** Aligns the pill's top with the cutout (or below the status bar). */
117
+ private fun computeTopMargin(info: CutoutInfo): Int {
118
+ val rect = info.rect
119
+ return if (info.hasCutout && rect != null) {
120
+ if (rect.top > 0) {
121
+ rect.top
122
+ } else {
123
+ // Cutout flush against the screen edge — nudge down a touch
124
+ // so the pill doesn't sit literally at y=0.
125
+ (rect.centerY() / 3).coerceAtLeast(density.dpInt(4f))
126
+ }
127
+ } else {
128
+ info.statusBarHeight + density.dpInt(10f)
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Computes horizontal inset so the pill's top corners stay inside the
134
+ * rounded screen corners. Using the circle equation:
135
+ * x = r − sqrt(r² − (r − y)²)
136
+ * gives the horizontal distance from the screen edge at vertical
137
+ * offset `y` from the top, for a corner of radius `r`.
138
+ */
139
+ private fun computePillMargin(screenCornerRadius: Int, topMargin: Int): Int {
140
+ val defaultMargin = density.dpInt(10f)
141
+ if (screenCornerRadius <= 0 || topMargin >= screenCornerRadius) {
142
+ return defaultMargin
143
+ }
144
+ val r = screenCornerRadius.toDouble()
145
+ val y = topMargin.toDouble()
146
+ val horizontalInset = (r - sqrt(r * r - (r - y) * (r - y))).toInt()
147
+ return (horizontalInset + density.dpInt(4f)).coerceAtLeast(defaultMargin)
148
+ }
149
+
150
+ /** Concentric corner radius so the pill hugs the screen's rounded corners. */
151
+ private fun computeExpandedCornerRadius(info: CutoutInfo, pillMargin: Int, topMargin: Int): Float {
152
+ if (!info.hasCutout || info.screenCornerRadius <= 0) {
153
+ return density.dp(30f)
154
+ }
155
+ val inset = maxOf(pillMargin, topMargin)
156
+ return (info.screenCornerRadius - inset).toFloat().coerceAtLeast(density.dp(20f))
157
+ }
158
+
159
+ private fun buildContent(info: CutoutInfo, topMargin: Int): LinearLayout {
160
+ val hPad = density.dpInt(20f)
161
+ val vPad = density.dpInt(14f)
162
+ val topPad = if (info.hasCutout && info.rect != null) {
163
+ // Push content below the camera hole: camera bottom - pill top + gap.
164
+ (info.rect.bottom - topMargin + density.dpInt(6f)).coerceAtLeast(vPad)
165
+ } else {
166
+ vPad
167
+ }
168
+ return LinearLayout(activity).apply {
169
+ orientation = LinearLayout.HORIZONTAL
170
+ gravity = Gravity.CENTER_VERTICAL
171
+ setPadding(hPad, topPad, hPad, vPad)
172
+ }
173
+ }
174
+
175
+ private fun buildIcon(): ImageView = ImageView(activity).apply {
176
+ layoutParams = LinearLayout.LayoutParams(density.dpInt(50f), density.dpInt(35f))
177
+ scaleType = ImageView.ScaleType.CENTER_INSIDE
178
+ }
179
+
180
+ private fun buildTextContainer(): LinearLayout = LinearLayout(activity).apply {
181
+ orientation = LinearLayout.VERTICAL
182
+ layoutParams = LinearLayout.LayoutParams(
183
+ 0,
184
+ LinearLayout.LayoutParams.WRAP_CONTENT,
185
+ 1f
186
+ ).apply {
187
+ marginStart = density.dpInt(10f)
188
+ }
189
+ }
190
+
191
+ private fun buildTitle(): TextView = TextView(activity).apply {
192
+ setTextColor(Color.WHITE)
193
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f)
194
+ typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
195
+ }
196
+
197
+ private fun buildMessage(): TextView = TextView(activity).apply {
198
+ setTextColor(Color.argb(153, 255, 255, 255))
199
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
200
+ }
201
+
202
+ private fun buildActionButton(): TextView = TextView(activity).apply {
203
+ visibility = ViewGroup.GONE
204
+ setTextColor(Color.WHITE)
205
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
206
+ typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
207
+ val hPad = density.dpInt(12f)
208
+ val vPad = density.dpInt(6f)
209
+ setPadding(hPad, vPad, hPad, vPad)
210
+ val bg = GradientDrawable().apply {
211
+ setColor(Color.argb(30, 255, 255, 255))
212
+ cornerRadius = density.dp(20f)
213
+ }
214
+ background = bg
215
+ layoutParams = LinearLayout.LayoutParams(
216
+ LinearLayout.LayoutParams.WRAP_CONTENT,
217
+ LinearLayout.LayoutParams.WRAP_CONTENT
218
+ ).apply {
219
+ marginStart = density.dpInt(8f)
220
+ }
221
+ isClickable = true
222
+ isFocusable = true
223
+ }
224
+ }
@@ -0,0 +1,17 @@
1
+ package com.toast.util
2
+
3
+ import android.content.res.Resources
4
+
5
+ /**
6
+ * Caches the display density so `dp(...)` doesn't re-enter
7
+ * `Resources.displayMetrics` on every conversion.
8
+ */
9
+ class Density(val scale: Float) {
10
+ fun dp(v: Float): Float = v * scale
11
+ fun dpInt(v: Float): Int = (v * scale).toInt()
12
+
13
+ companion object {
14
+ fun from(resources: Resources): Density =
15
+ Density(resources.displayMetrics.density)
16
+ }
17
+ }
@@ -0,0 +1,24 @@
1
+ package com.toast.util
2
+
3
+ import android.app.Activity
4
+ import android.view.WindowManager
5
+
6
+ /**
7
+ * Toggles the legacy FULLSCREEN flag on the activity window. Used to hide
8
+ * the status bar while a cutout-morph toast is visible so the pill can sit
9
+ * flush against the camera hole without a competing status bar tint.
10
+ */
11
+ object StatusBarController {
12
+ @Suppress("DEPRECATION")
13
+ fun hide(activity: Activity) {
14
+ activity.window?.setFlags(
15
+ WindowManager.LayoutParams.FLAG_FULLSCREEN,
16
+ WindowManager.LayoutParams.FLAG_FULLSCREEN
17
+ )
18
+ }
19
+
20
+ @Suppress("DEPRECATION")
21
+ fun show(activity: Activity) {
22
+ activity.window?.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
23
+ }
24
+ }
@@ -0,0 +1,36 @@
1
+ package com.toast.util
2
+
3
+ import android.view.animation.PathInterpolator
4
+
5
+ /**
6
+ * Shared animation/morph constants for the toast overlay.
7
+ */
8
+ object ToastConstants {
9
+ /**
10
+ * Material standard "emphasized" ease-in-out: gentle start, quick
11
+ * mid-phase, soft landing. Feels closer to iOS's .bouncy curve than
12
+ * the default decelerate/accelerate interpolators.
13
+ */
14
+ val MORPH_EASING = PathInterpolator(0.2f, 0f, 0f, 1f)
15
+
16
+ /**
17
+ * Android's DisplayCutout.boundingRect is the *reserved* safe-area
18
+ * around the camera — on most phones 1.5–2× the visible punch hole.
19
+ * Shrinking by this factor produces a pill that tightly hugs the
20
+ * actual camera dot rather than the padded safe-area.
21
+ */
22
+ const val COLLAPSED_CUTOUT_FACTOR = 0.55f
23
+
24
+ /**
25
+ * Duration of the main scale/translation/corner morph. Matches iOS's
26
+ * `.bouncy(duration: 0.3)` in PrettyToastView.swift.
27
+ */
28
+ const val MORPH_DURATION_MS: Long = 300L
29
+
30
+ /**
31
+ * Buffer between morph animation end and the onDismiss callback.
32
+ * Matches iOS's `asyncAfter(deadline: .now() + 0.35)` in ToastManager.swift
33
+ * (0.35s total = 0.30s morph + 0.05s buffer).
34
+ */
35
+ const val DISMISS_CALLBACK_BUFFER_MS: Long = 50L
36
+ }
File without changes
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="35dp"
3
+ android:height="35dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FFFFFF"
8
+ android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="35dp"
3
+ android:height="35dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FFFFFF"
8
+ android:pathData="M4,12l1.41,1.41L11,7.83V20h2V7.83l5.58,5.59L20,12l-8,-8 -8,8z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="35dp"
3
+ android:height="35dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FFFFFF"
8
+ android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="35dp"
3
+ android:height="35dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FFFFFF"
8
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="35dp"
3
+ android:height="35dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FFFFFF"
8
+ android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="35dp"
3
+ android:height="35dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FFFFFF"
8
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2V7h2v2z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="35dp"
3
+ android:height="35dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FFFFFF"
8
+ android:pathData="M20,4H4C2.9,4 2.01,4.9 2.01,6L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5V6l8,5 8,-5v2z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="35dp"
3
+ android:height="35dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FFFFFF"
8
+ android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32V4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="35dp"
3
+ android:height="35dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FFFFFF"
8
+ android:pathData="M9,11.24V7.5C9,6.12 10.12,5 11.5,5S14,6.12 14,7.5v3.74c1.21,-0.81 2,-2.18 2,-3.74C16,5.01 13.99,3 11.5,3S7,5.01 7,7.5c0,1.56 0.79,2.93 2,3.74zM18.84,15.87l-4.54,-2.26c-0.17,-0.07 -0.35,-0.11 -0.54,-0.11H13v-6C13,6.67 12.33,6 11.5,6S10,6.67 10,7.5v10.74l-3.43,-0.72c-0.08,-0.01 -0.15,-0.03 -0.24,-0.03 -0.31,0 -0.59,0.13 -0.79,0.33l-0.79,0.8 4.94,4.94c0.27,0.27 0.65,0.44 1.06,0.44h6.79c0.75,0 1.33,-0.55 1.44,-1.28l0.75,-5.27c0.01,-0.07 0.02,-0.14 0.02,-0.2 0,-0.62 -0.38,-1.16 -0.91,-1.38z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="35dp"
3
+ android:height="35dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FFFFFF"
8
+ android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="35dp"
3
+ android:height="35dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FFFFFF"
8
+ android:pathData="M1,9l2,2c4.97,-4.97 13.03,-4.97 18,0l2,-2C16.93,2.93 7.08,2.93 1,9zM9,17l3,3 3,-3c-1.65,-1.66 -4.34,-1.66 -6,0zM5,13l2,2c2.76,-2.76 7.24,-2.76 10,0l2,-2C15.14,9.14 8.87,9.14 5,13z"/>
9
+ </vector>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <resources>
3
+ </resources>
@@ -0,0 +1,3 @@
1
+ <resources>
2
+ <string name="pretty_toast_name">Pretty Toast</string>
3
+ </resources>
@@ -0,0 +1,3 @@
1
+ <resources>
2
+
3
+ </resources>
@@ -0,0 +1,26 @@
1
+ package com.toast
2
+
3
+ import com.getcapacitor.PluginMethod
4
+ import org.junit.Assert.assertEquals
5
+ import org.junit.Assert.assertNotNull
6
+ import org.junit.Test
7
+
8
+ class PrettyToastPluginTest {
9
+ @Test
10
+ fun pluginClassExists() {
11
+ assertNotNull(PrettyToastPlugin())
12
+ }
13
+
14
+ @Test
15
+ fun pluginMethodsAreAnnotated() {
16
+ val methodNames = PrettyToastPlugin::class.java.declaredMethods
17
+ .filter { it.getAnnotation(PluginMethod::class.java) != null }
18
+ .map { it.name }
19
+ .sorted()
20
+
21
+ assertEquals(
22
+ listOf("dismissCurrentToast", "showCurrentToast", "updateCurrentToast"),
23
+ methodNames,
24
+ )
25
+ }
26
+ }