Skip to content

Commit 7f54c9e

Browse files
committed
Update
1 parent 1693ec7 commit 7f54c9e

4 files changed

Lines changed: 40 additions & 128 deletions

File tree

Lines changed: 39 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
11
package com.omarea.common.ui
22

3-
import android.animation.Animator
4-
import android.animation.AnimatorListenerAdapter
53
import android.app.Activity
64
import android.graphics.drawable.GradientDrawable
75
import android.os.Handler
86
import android.os.Looper
7+
import android.view.Gravity
98
import android.view.LayoutInflater
109
import android.view.View
1110
import android.view.ViewGroup
1211
import android.widget.ImageView
1312
import android.widget.TextView
14-
import androidx.core.view.ViewCompat
15-
import androidx.core.view.WindowInsetsCompat
13+
import android.widget.Toast
1614
import com.tool.tree.R
1715

1816
enum class BannerType { INFO, SUCCESS, WARNING, ERROR }
1917

2018
/**
21-
* Hiện 1 banner thông báo đè lên trên cùng của Activity (hoặc Dialog) đang hiển thị.
22-
* Khác với Toast: hiển thị trong nội dung ứng dụng, có thể tùy biến màu/icon/tiêu đề,
23-
* và chỉ hoạt động khi app đang mở (cần 1 Activity foreground để add vào).
19+
* Hiện 1 banner thông báo đè lên trên cùng của Activity đang foreground -- KỂ CẢ khi
20+
* đang có Dialog mở (ví dụ ProgressBarDialog).
2421
*
25-
* Nếu đang có Dialog mở (đăng ký qua [CurrentDialogHolder], ví dụ [ProgressBarDialog]),
26-
* banner sẽ được add vào chính Window của Dialog đó để chắc chắn nổi lên trên — vì Dialog
27-
* là 1 Window riêng biệt luôn nổi trên Window của Activity, add vào content Activity sẽ bị
28-
* Dialog che mất bất kể elevation. Nếu không có Dialog nào mở, banner add vào content
29-
* của Activity như bình thường.
22+
* Kỹ thuật: dùng chính cơ chế của Toast (custom view Toast, type cửa sổ TYPE_TOAST) thay vì
23+
* tự add view vào content của Activity/Dialog. Lý do: Dialog là 1 Window riêng, kích thước
24+
* chỉ vừa đủ khung dialog (không phải full màn hình), nên add view thường vào bên trong nó
25+
* sẽ bị bó hẹp/cắt theo đúng khung dialog nhỏ đó. TYPE_TOAST là loại cửa sổ đặc biệt của hệ
26+
* 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ó
27+
* sẵn của bạn luôn hiện được dù đang có dialog hay không).
28+
*
29+
* Đá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 --
30+
* sẽ tự động biến mất sau đúng thời gian `durationMs` được yêu cầu.
3031
*
3132
* Gọi từ shell: am broadcast -a <applicationId>.broadcast.BANNER --es text "..." --es type "success"
3233
*/
@@ -42,10 +43,11 @@ object BannerNotificationManager {
4243

4344
private val queue = ArrayDeque<BannerRequest>()
4445
private var isShowing = false
46+
private var currentToast: Toast? = null
4547

4648
/**
4749
* @param onNoActivity gọi khi không có Activity nào đang foreground (app ở background),
48-
* dùng để nơi gọi có thể fallback sang Toast hoặc bỏ qua.
50+
* dùng để nơi gọi có thể fallback sang Toast thường hoặc bỏ qua.
4951
*/
5052
fun show(
5153
title: String? = null,
@@ -72,7 +74,6 @@ object BannerNotificationManager {
7274
}
7375
val activity = CurrentActivityHolder.get()
7476
if (activity == null) {
75-
// Không còn Activity nào để hiện -> bỏ qua item này, thử item tiếp theo
7677
showNext()
7778
return
7879
}
@@ -84,30 +85,13 @@ object BannerNotificationManager {
8485
}
8586
}
8687

87-
/** Tìm ViewGroup gốc để add banner vào: ưu tiên Window của Dialog đang mở, nếu không có thì dùng content của Activity. */
88-
private fun resolveRoot(activity: Activity): ViewGroup? {
89-
val topDialog = CurrentDialogHolder.getTopVisible()
90-
val dialogWindow = topDialog?.window
91-
if (dialogWindow != null && dialogWindow.decorView.isAttachedToWindow) {
92-
val dialogContent = dialogWindow.findViewById<ViewGroup>(android.R.id.content)
93-
if (dialogContent != null) return dialogContent
94-
}
95-
return activity.findViewById(android.R.id.content)
96-
}
97-
9888
private fun showOn(activity: Activity, req: BannerRequest) {
99-
val root = resolveRoot(activity)
100-
if (root == null) {
101-
showNext()
102-
return
103-
}
104-
val view = LayoutInflater.from(activity).inflate(R.layout.banner_notification, root, false)
89+
val view = LayoutInflater.from(activity).inflate(R.layout.banner_notification, null, false)
10590

10691
val bannerRoot = view.findViewById<View>(R.id.banner_root)
10792
val icon = view.findViewById<ImageView>(R.id.banner_icon)
10893
val titleView = view.findViewById<TextView>(R.id.banner_title)
10994
val messageView = view.findViewById<TextView>(R.id.banner_message)
110-
val closeButton = view.findViewById<View>(R.id.banner_close)
11195

11296
val (colorRes, iconRes) = when (req.type) {
11397
BannerType.INFO -> R.color.banner_info to R.drawable.ic_banner_info
@@ -128,54 +112,30 @@ object BannerNotificationManager {
128112
}
129113
messageView.text = req.message
130114

131-
// Tránh banner bị che bởi status bar / notch
132-
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
133-
val top = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top
134-
v.setPadding(v.paddingLeft, top, v.paddingRight, v.paddingBottom)
135-
insets
136-
}
137-
138-
root.addView(view)
139-
ViewCompat.requestApplyInsets(view)
140-
141-
var dismissed = false
142-
val dismiss = {
143-
if (!dismissed) {
144-
dismissed = true
145-
mainHandler.removeCallbacksAndMessages(view)
146-
view.animate()
147-
.translationY(-(view.height.takeIf { it > 0 } ?: 300).toFloat())
148-
.alpha(0f)
149-
.setDuration(200)
150-
.setListener(object : AnimatorListenerAdapter() {
151-
override fun onAnimationEnd(animation: Animator) {
152-
try {
153-
root.removeView(view)
154-
} catch (e: Exception) {
155-
// root (vd. Window của Dialog) có thể đã bị đóng -> bỏ qua
156-
}
157-
showNext()
158-
}
159-
})
160-
.start()
115+
// Toast mặc định wrap_content theo nội dung -> ép chiều rộng gần bằng màn hình
116+
// (trừ lề 2 bên) để có dáng banner tràn ngang như mong muốn.
117+
val density = activity.resources.displayMetrics.density
118+
val marginPx = (12 * density).toInt()
119+
val screenWidth = activity.resources.displayMetrics.widthPixels
120+
view.layoutParams = ViewGroup.LayoutParams(screenWidth - marginPx * 2, ViewGroup.LayoutParams.WRAP_CONTENT)
121+
122+
val toast = Toast(activity.applicationContext)
123+
toast.duration = Toast.LENGTH_LONG
124+
@Suppress("DEPRECATION")
125+
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+
toast.show()
129+
currentToast = toast
130+
131+
val runnable = Runnable {
132+
try {
133+
toast.cancel()
134+
} catch (e: Exception) {
161135
}
136+
currentToast = null
137+
showNext()
162138
}
163-
164-
closeButton.setOnClickListener { dismiss() }
165-
view.setOnClickListener { dismiss() }
166-
167-
view.alpha = 0f
168-
view.translationY = -200f
169-
view.post {
170-
view.translationY = -(view.height.takeIf { it > 0 } ?: 300).toFloat()
171-
view.animate()
172-
.translationY(0f)
173-
.alpha(1f)
174-
.setDuration(250)
175-
.start()
176-
}
177-
178-
val runnable = Runnable { dismiss() }
179-
mainHandler.postAtTime(runnable, view, android.os.SystemClock.uptimeMillis() + req.durationMs)
139+
mainHandler.postDelayed(runnable, req.durationMs)
180140
}
181141
}

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

Lines changed: 0 additions & 35 deletions
This file was deleted.

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,6 @@ class DialogHelper {
396396
setBackgroundDrawableResource(android.R.color.transparent)
397397
}
398398
}
399-
CurrentDialogHolder.register(dialog)
400399

401400
return setOutsideTouchDismiss(view, DialogWrap(dialog).setCancelable(cancelable))
402401
}

app/src/main/res/layout/banner_notification.xml

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,12 @@
33
android:id="@+id/banner_root"
44
android:layout_width="match_parent"
55
android:layout_height="wrap_content"
6-
android:layout_marginStart="12dp"
7-
android:layout_marginEnd="12dp"
8-
android:layout_marginTop="?android:actionBarSize"
96
android:orientation="horizontal"
107
android:gravity="center_vertical"
118
android:background="@drawable/banner_bg"
129
android:elevation="8dp"
1310
android:paddingStart="16dp"
14-
android:paddingEnd="12dp"
11+
android:paddingEnd="16dp"
1512
android:paddingTop="14dp"
1613
android:paddingBottom="14dp">
1714

@@ -48,13 +45,4 @@
4845
android:maxLines="4" />
4946
</LinearLayout>
5047

51-
<ImageView
52-
android:id="@+id/banner_close"
53-
android:layout_width="32dp"
54-
android:layout_height="32dp"
55-
android:layout_gravity="top"
56-
android:padding="8dp"
57-
android:src="@drawable/ic_banner_close"
58-
android:background="?attr/selectableItemBackgroundBorderless" />
59-
6048
</LinearLayout>

0 commit comments

Comments
 (0)