@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.
- package/CapgoCapacitorPrettyToast.podspec +17 -0
- package/LICENSE +373 -0
- package/Package.swift +28 -0
- package/README.md +341 -0
- package/android/build.gradle +71 -0
- package/android/src/main/AndroidManifest.xml +6 -0
- package/android/src/main/java/com/toast/PrettyToastPlugin.kt +197 -0
- package/android/src/main/java/com/toast/ToastOverlay.kt +495 -0
- package/android/src/main/java/com/toast/anim/CutoutMorphAnimator.kt +235 -0
- package/android/src/main/java/com/toast/anim/SlideAnimator.kt +64 -0
- package/android/src/main/java/com/toast/anim/ToastAnimator.kt +23 -0
- package/android/src/main/java/com/toast/backdrop/BackdropSampler.kt +142 -0
- package/android/src/main/java/com/toast/backdrop/OutlineController.kt +100 -0
- package/android/src/main/java/com/toast/cutout/CutoutDetector.kt +88 -0
- package/android/src/main/java/com/toast/cutout/CutoutInfo.kt +28 -0
- package/android/src/main/java/com/toast/gesture/ToastGestureHandler.kt +68 -0
- package/android/src/main/java/com/toast/ui/IconMapper.kt +26 -0
- package/android/src/main/java/com/toast/ui/PassThroughFrameLayout.kt +53 -0
- package/android/src/main/java/com/toast/ui/ToastViewFactory.kt +224 -0
- package/android/src/main/java/com/toast/util/Density.kt +17 -0
- package/android/src/main/java/com/toast/util/StatusBarController.kt +24 -0
- package/android/src/main/java/com/toast/util/ToastConstants.kt +36 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/android/src/main/res/drawable/ic_arrow_downward.xml +9 -0
- package/android/src/main/res/drawable/ic_arrow_upward.xml +9 -0
- package/android/src/main/res/drawable/ic_cancel.xml +9 -0
- package/android/src/main/res/drawable/ic_check_circle.xml +9 -0
- package/android/src/main/res/drawable/ic_favorite.xml +9 -0
- package/android/src/main/res/drawable/ic_info.xml +9 -0
- package/android/src/main/res/drawable/ic_mail.xml +9 -0
- package/android/src/main/res/drawable/ic_notifications.xml +9 -0
- package/android/src/main/res/drawable/ic_touch_app.xml +9 -0
- package/android/src/main/res/drawable/ic_warning.xml +9 -0
- package/android/src/main/res/drawable/ic_wifi.xml +9 -0
- package/android/src/main/res/values/colors.xml +3 -0
- package/android/src/main/res/values/strings.xml +3 -0
- package/android/src/main/res/values/styles.xml +3 -0
- package/android/src/test/java/com/toast/PrettyToastPluginTest.kt +26 -0
- package/dist/docs.json +459 -0
- package/dist/esm/controller.d.ts +30 -0
- package/dist/esm/controller.js +271 -0
- package/dist/esm/controller.js.map +1 -0
- package/dist/esm/definitions.d.ts +144 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/driver.d.ts +19 -0
- package/dist/esm/driver.js +24 -0
- package/dist/esm/driver.js.map +1 -0
- package/dist/esm/icons.d.ts +14 -0
- package/dist/esm/icons.js +138 -0
- package/dist/esm/icons.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal-plugin.d.ts +2 -0
- package/dist/esm/internal-plugin.js +5 -0
- package/dist/esm/internal-plugin.js.map +1 -0
- package/dist/esm/internal-types.d.ts +31 -0
- package/dist/esm/internal-types.js +2 -0
- package/dist/esm/internal-types.js.map +1 -0
- package/dist/esm/toast.d.ts +1 -0
- package/dist/esm/toast.js +5 -0
- package/dist/esm/toast.js.map +1 -0
- package/dist/esm/web-renderer.d.ts +36 -0
- package/dist/esm/web-renderer.js +296 -0
- package/dist/esm/web-renderer.js.map +1 -0
- package/dist/esm/web.d.ts +10 -0
- package/dist/esm/web.js +28 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +770 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +773 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/PrettyToastPlugin/CustomHostingView.swift +13 -0
- package/ios/Sources/PrettyToastPlugin/PassThroughWindow.swift +143 -0
- package/ios/Sources/PrettyToastPlugin/PrettyToastColorParser.swift +94 -0
- package/ios/Sources/PrettyToastPlugin/PrettyToastPlugin.swift +138 -0
- package/ios/Sources/PrettyToastPlugin/PrettyToastView.swift +267 -0
- package/ios/Sources/PrettyToastPlugin/Toast.swift +29 -0
- package/ios/Sources/PrettyToastPlugin/ToastManager.swift +392 -0
- package/ios/Tests/PrettyToastPluginTests/PrettyToastPluginTests.swift +21 -0
- package/package.json +98 -0
- package/scripts/check-capacitor-plugin-wiring.mjs +254 -0
- package/scripts/deploy-example-capgo.mjs +86 -0
- 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,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
|
+
}
|