@@ -33,11 +33,21 @@ package org.godotengine.godot.utils
33
33
import android.app.Activity
34
34
import android.app.AlertDialog
35
35
import android.content.DialogInterface
36
+ import android.os.Handler
37
+ import android.os.Looper
38
+ import android.view.Gravity
39
+ import android.view.LayoutInflater
40
+ import android.view.MotionEvent
41
+ import android.view.View
42
+ import android.view.ViewGroup
36
43
import android.widget.Button
37
44
import android.widget.EditText
38
45
import android.widget.LinearLayout
46
+ import android.widget.PopupWindow
47
+ import android.widget.TextView
39
48
40
49
import org.godotengine.godot.R
50
+ import kotlin.math.abs
41
51
42
52
/* *
43
53
* Utility class for managing dialogs.
@@ -183,5 +193,91 @@ internal class DialogUtils {
183
193
dialog.show()
184
194
}
185
195
}
196
+
197
+ /* *
198
+ * Displays a Snackbar with an optional action button.
199
+ *
200
+ * @param context The Context in which the Snackbar should be displayed.
201
+ * @param message The message to display in the Snackbar.
202
+ * @param duration The duration for which the Snackbar should be visible (in milliseconds).
203
+ * @param actionText (Optional) The text for the action button. If `null`, the button is hidden.
204
+ * @param actionCallback (Optional) A callback function to execute when the action button is clicked. If `null`, no action is performed.
205
+ */
206
+ fun showSnackbar (activity : Activity , message : String , duration : Long = 3000, actionText : String? = null, action : (() -> Unit )? = null) {
207
+ activity.runOnUiThread {
208
+ val bottomMargin = activity.resources.getDimensionPixelSize(R .dimen.snackbar_bottom_margin)
209
+ val inflater = LayoutInflater .from(activity)
210
+ val customView = inflater.inflate(R .layout.snackbar, null )
211
+
212
+ val popupWindow = PopupWindow (
213
+ customView,
214
+ ViewGroup .LayoutParams .MATCH_PARENT ,
215
+ ViewGroup .LayoutParams .WRAP_CONTENT ,
216
+ )
217
+
218
+ val messageView = customView.findViewById<TextView >(R .id.snackbar_text)
219
+ messageView.text = message
220
+
221
+ val actionButton = customView.findViewById<Button >(R .id.snackbar_action)
222
+
223
+ if (actionText != null && action != null ) {
224
+ actionButton.text = actionText
225
+ actionButton.visibility = View .VISIBLE
226
+ actionButton.setOnClickListener {
227
+ action.invoke()
228
+ popupWindow.dismiss()
229
+ }
230
+ } else {
231
+ actionButton.visibility = View .GONE
232
+ }
233
+
234
+ addSwipeToDismiss(customView, popupWindow)
235
+ popupWindow.showAtLocation(customView, Gravity .BOTTOM , 0 , bottomMargin)
236
+
237
+ Handler (Looper .getMainLooper()).postDelayed({ popupWindow.dismiss() }, duration)
238
+ }
239
+ }
240
+
241
+ private fun addSwipeToDismiss (view : View , popupWindow : PopupWindow ) {
242
+ view.setOnTouchListener(object : View .OnTouchListener {
243
+ private var initialX = 0f
244
+ private var dX = 0f
245
+ private val threshold = 300f // Swipe distance threshold.
246
+
247
+ override fun onTouch (v : View ? , event : MotionEvent ): Boolean {
248
+ when (event.action) {
249
+ MotionEvent .ACTION_DOWN -> {
250
+ initialX = event.rawX
251
+ dX = view.translationX
252
+ }
253
+
254
+ MotionEvent .ACTION_MOVE -> {
255
+ val deltaX = event.rawX - initialX
256
+ view.translationX = dX + deltaX
257
+ }
258
+
259
+ MotionEvent .ACTION_UP , MotionEvent .ACTION_CANCEL -> {
260
+ val finalX = event.rawX - initialX
261
+
262
+ if (abs(finalX) > threshold) {
263
+ // If swipe exceeds threshold, dismiss smoothly.
264
+ view.animate()
265
+ .translationX(if (finalX > 0 ) view.width.toFloat() else - view.width.toFloat())
266
+ .setDuration(200 )
267
+ .withEndAction { popupWindow.dismiss() }
268
+ .start()
269
+ } else {
270
+ // If swipe is canceled, return smoothly.
271
+ view.animate()
272
+ .translationX(0f )
273
+ .setDuration(200 )
274
+ .start()
275
+ }
276
+ }
277
+ }
278
+ return true
279
+ }
280
+ })
281
+ }
186
282
}
187
283
}
0 commit comments