@@ -13,6 +13,7 @@ import android.widget.Toast
1313import com.tool.tree.R
1414
1515enum class BannerType { INFO , SUCCESS , WARNING , ERROR }
16+ enum class BannerPosition { TOP , BOTTOM }
1617
1718/* *
1819 * Hiện 1 banner thông báo đè lên trên cùng của Activity đang foreground -- KỂ CẢ khi
@@ -25,24 +26,27 @@ enum class BannerType { INFO, SUCCESS, WARNING, ERROR }
2526 * thống luôn nổi trên mọi Dialog/Activity của app (đây cũng chính là lý do ToastReceiver có
2627 * sẵn của bạn luôn hiện được dù đang có dialog hay không).
2728 *
28- * Đánh đổi: cửa sổ Toast KHÔNG nhận sự kiện chạm, nên banner không thể bấm để tắt sớm --
29- * sẽ tự động biến mất sau đúng thời gian `durationMs` được yêu cầu .
29+ * Đánh đổi: cửa sổ Toast KHÔNG nhận sự kiện chạm ( không bấm tắt sớm được), và không thể tùy
30+ * chỉnh thời gian hiển thị (Android giới hạn cứng ở mức LENGTH_LONG ~3.5s cho mọi Toast) .
3031 *
31- * Gọi từ shell: am broadcast -a <applicationId>.broadcast.BANNER --es text "..." --es type "success"
32+ * Gọi từ shell: am broadcast -a <applicationId>.broadcast.BANNER --es text "..." --es type "success" --es position "bottom"
3233 */
3334object BannerNotificationManager {
3435 private val mainHandler = Handler (Looper .getMainLooper())
3536
37+ // Thời gian hiển thị thực tế của Toast.LENGTH_LONG do hệ thống quy định, dùng để giãn cách
38+ // các banner trong hàng đợi, tránh cái sau ghi đè ngay lên cái trước.
39+ private const val TOAST_LONG_DURATION_MS = 3500L
40+
3641 private data class BannerRequest (
3742 val title : String? ,
3843 val message : String ,
3944 val type : BannerType ,
40- val durationMs : Long
45+ val position : BannerPosition
4146 )
4247
4348 private val queue = ArrayDeque <BannerRequest >()
4449 private var isShowing = false
45- private var currentToast: Toast ? = null
4650
4751 /* *
4852 * @param onNoActivity gọi khi không có Activity nào đang foreground (app ở background),
@@ -52,15 +56,15 @@ object BannerNotificationManager {
5256 title : String? = null,
5357 message : String ,
5458 type : BannerType = BannerType .INFO ,
55- durationMs : Long = 3000L ,
59+ position : BannerPosition = BannerPosition . TOP ,
5660 onNoActivity : (() -> Unit )? = null
5761 ) {
5862 if (CurrentActivityHolder .get() == null ) {
5963 onNoActivity?.invoke()
6064 return
6165 }
6266 mainHandler.post {
63- queue.addLast(BannerRequest (title, message, type, durationMs ))
67+ queue.addLast(BannerRequest (title, message, type, position ))
6468 if (! isShowing) showNext()
6569 }
6670 }
@@ -123,32 +127,16 @@ object BannerNotificationManager {
123127 toast.duration = Toast .LENGTH_LONG
124128 @Suppress(" DEPRECATION" )
125129 toast.view = view
126- // Offset nhỏ để không dính sát mép trên / bị status bar che
127- toast.setGravity(Gravity .TOP or Gravity .CENTER_HORIZONTAL , 0 , (24 * density).toInt())
128- currentToast = toast
129-
130- // Toast chỉ hỗ trợ 2 mốc thời gian cố định (LENGTH_SHORT ~2s / LENGTH_LONG ~3.5s),
131- // không có API để đặt số ms tùy ý. Để đạt đúng `durationMs` yêu cầu, gọi lại show()
132- // theo chu kỳ (làm mới thời gian hiển thị) cho tới khi đủ thời lượng mong muốn.
133- val startTime = android.os.SystemClock .uptimeMillis()
134- val refreshInterval = 3000L // nhỏ hơn LENGTH_LONG (~3.5s) để không bị chớp tắt giữa 2 lần show
135- lateinit var keepAliveRunnable: Runnable
136- keepAliveRunnable = Runnable {
137- val elapsed = android.os.SystemClock .uptimeMillis() - startTime
138- if (elapsed < req.durationMs) {
139- toast.show()
140- val remaining = req.durationMs - elapsed
141- mainHandler.postDelayed(keepAliveRunnable, minOf(refreshInterval, remaining))
142- } else {
143- try {
144- toast.cancel()
145- } catch (e: Exception ) {
146- }
147- currentToast = null
148- showNext()
149- }
130+
131+ val offsetPx = (24 * density).toInt()
132+ when (req.position) {
133+ BannerPosition .TOP -> toast.setGravity(Gravity .TOP or Gravity .CENTER_HORIZONTAL , 0 , offsetPx)
134+ BannerPosition .BOTTOM -> toast.setGravity(Gravity .BOTTOM or Gravity .CENTER_HORIZONTAL , 0 , offsetPx)
150135 }
151136 toast.show()
152- mainHandler.postDelayed(keepAliveRunnable, minOf(refreshInterval, req.durationMs))
137+
138+ // Toast tự ẩn theo thời lượng hệ thống quy định (LENGTH_LONG), chỉ cần đợi tương ứng
139+ // rồi xử lý banner tiếp theo trong hàng đợi (nếu có).
140+ mainHandler.postDelayed({ showNext() }, TOAST_LONG_DURATION_MS )
153141 }
154142}
0 commit comments