Skip to content

Commit c3df94a

Browse files
committed
Update
1 parent eaa93ec commit c3df94a

2 files changed

Lines changed: 29 additions & 37 deletions

File tree

app/src/main/java/com/omarea/common/ui/BannerNotificationManager.kt

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import android.widget.Toast
1313
import com.tool.tree.R
1414

1515
enum 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
*/
3334
object 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
}

app/src/main/java/com/tool/tree/BannerReceiver.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.content.Context
55
import android.content.Intent
66
import android.widget.Toast
77
import com.omarea.common.ui.BannerNotificationManager
8+
import com.omarea.common.ui.BannerPosition
89
import com.omarea.common.ui.BannerType
910
import com.omarea.krscript.config.StringResRef
1011

@@ -16,10 +17,10 @@ import com.omarea.krscript.config.StringResRef
1617
* --es title "Thành công" \
1718
* --es text "Đã cài đặt module xong" \
1819
* --es type "success" \
19-
* --el duration 4000
20+
* --es position "bottom"
2021
*
2122
* Extra "type" nhận 1 trong: info (mặc định) | success | warning | error
22-
* Extra "duration" (ms) không bắt buộc, mặc định 3000
23+
* Extra "position" nhận 1 trong: top (mặc định) | bottom
2324
* Nếu app đang ở background (không có Activity foreground), sẽ tự rơi về hiện Toast thường.
2425
*/
2526
class BannerReceiver : BroadcastReceiver() {
@@ -33,17 +34,20 @@ class BannerReceiver : BroadcastReceiver() {
3334
"error" -> BannerType.ERROR
3435
else -> BannerType.INFO
3536
}
36-
val duration = if (intent.hasExtra("duration")) intent.getLongExtra("duration", 3000L) else 3000L
37+
val position = when (intent.getStringExtra("position")?.lowercase()) {
38+
"bottom" -> BannerPosition.BOTTOM
39+
else -> BannerPosition.TOP
40+
}
3741

3842
BannerNotificationManager.show(
3943
title = title,
4044
message = message,
4145
type = type,
42-
durationMs = duration,
46+
position = position,
4347
onNoActivity = {
4448
// App đang ở background, không có nơi để hiện banner -> fallback Toast
4549
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
4650
}
4751
)
4852
}
49-
}
53+
}

0 commit comments

Comments
 (0)