11package com.mobven.mb_version
22
3- import android.app.Activity
43import android.annotation.SuppressLint
4+ import android.app.Activity
55import android.app.Application
66import android.content.Context
77import android.content.pm.PackageInfo
88import android.graphics.Color
99import android.os.Build
1010import android.view.Gravity
11+ import android.view.MotionEvent
1112import android.view.View
13+ import android.view.ViewConfiguration
1214import android.view.ViewGroup
1315import android.widget.FrameLayout
16+ import android.widget.ImageView
1417import android.widget.TextView
18+ import androidx.core.content.ContextCompat
19+ import com.mobven.mb_version.MBVersionOverlay.show
20+ import androidx.core.graphics.toColorInt
1521
1622/* *
1723 * A lightweight Android library for displaying app version information as an overlay on all activities.
1824 * @author Mobven
19- * @version 1.0.1
25+ * @version 1.0.3
2026 * @since 1.0.0
2127 */
2228object MBVersionOverlay {
2329
2430 private var isEnabled = false
2531 private var customText: String? = null
26- private var backgroundColor = Color .parseColor( " #AA4444FF" )
32+ private var backgroundColor = " #AA4444FF" .toColorInt( )
2733 private var textColor = Color .WHITE
2834 private var textSize = 16f
2935 private var position = Position .BOTTOM
3036 private var bottomMargin = 32
3137 private var isDraggable = true
38+ private var dismissedForSession = false
3239
3340 enum class Position {
3441 TOP , BOTTOM
@@ -43,12 +50,9 @@ object MBVersionOverlay {
4350 *
4451 * @since 1.0.0
4552 */
46- fun init (
47- application : Application ,
48- enabled : Boolean = true
49- ) {
53+ fun init (application : Application , enabled : Boolean = true) {
5054 isEnabled = enabled
51-
55+ dismissedForSession = false
5256 if (isEnabled) {
5357 application.registerActivityLifecycleCallbacks(MBVersionOverlayLifecycleCallback ())
5458 } else {
@@ -91,12 +95,7 @@ object MBVersionOverlay {
9195 */
9296 fun show (activity : Activity , show : Boolean = true) {
9397 if (! isEnabled) return
94-
95- if (show) {
96- addOverlayToActivity(activity)
97- } else {
98- removeOverlayFromActivity(activity)
99- }
98+ if (show) addOverlayToActivity(activity) else removeOverlayFromActivity(activity)
10099 }
101100
102101 /* *
@@ -108,26 +107,15 @@ object MBVersionOverlay {
108107 * @since 1.0.0
109108 */
110109 internal fun addOverlayToActivity (activity : Activity ) {
111- if (! isEnabled) {
112- println (" MBVersionOverlay: Not enabled, skipping" )
110+ if (! isEnabled || dismissedForSession ) {
111+ println (" MBVersionOverlay: Not enabled or dismissed for session , skipping" )
113112 return
114113 }
115-
116114 activity.runOnUiThread {
117- println (" MBVersionOverlay: Adding overlay to ${activity.javaClass.simpleName} " )
118-
119- val rootView = activity.findViewById<ViewGroup >(android.R .id.content)
120- if (rootView == null ) {
121- println (" MBVersionOverlay: Root view is null!" )
122- return @runOnUiThread
123- }
124-
115+ val rootView =
116+ activity.findViewById<ViewGroup >(android.R .id.content) ? : return @runOnUiThread
125117 val overlayId = getOverlayId()
126-
127- if (rootView.findViewById<View >(overlayId) != null ) {
128- println (" MBVersionOverlay: Overlay already exists" )
129- return @runOnUiThread
130- }
118+ if (rootView.findViewById<View >(overlayId) != null ) return @runOnUiThread
131119
132120 val overlay = createOverlayView(activity)
133121 overlay.id = overlayId
@@ -140,14 +128,11 @@ object MBVersionOverlay {
140128 Position .TOP -> Gravity .TOP
141129 Position .BOTTOM -> Gravity .BOTTOM
142130 }
143-
144131 if (position == Position .BOTTOM ) {
145132 bottomMargin = dpToPx(activity, this @MBVersionOverlay.bottomMargin)
146133 }
147134 }
148-
149135 rootView.addView(overlay, params)
150- println (" MBVersionOverlay: Overlay added successfully" )
151136 }
152137 }
153138
@@ -181,28 +166,70 @@ object MBVersionOverlay {
181166 *
182167 * @since 1.0.0
183168 */
184- private fun createOverlayView (context : Context ): TextView {
185- return TextView (context).apply {
186- text = getDisplayText(context)
169+ @SuppressLint(" ClickableViewAccessibility" )
170+ private fun createOverlayView (context : Context ): View {
171+ val act = context as Activity
172+
173+ val container = FrameLayout (context).apply {
187174 setBackgroundColor(backgroundColor)
175+ elevation = 100f
176+ translationZ = 100f
177+ setPadding(
178+ dpToPx(context, 4 ),
179+ dpToPx(context, 4 ),
180+ dpToPx(context, 4 ),
181+ dpToPx(context, 4 )
182+ )
183+ layoutParams = FrameLayout .LayoutParams (
184+ FrameLayout .LayoutParams .MATCH_PARENT ,
185+ FrameLayout .LayoutParams .WRAP_CONTENT
186+ )
187+ }
188+
189+ val tv = TextView (context).apply {
190+ text = getDisplayText(context)
188191 setTextColor(textColor)
189192 textSize = this @MBVersionOverlay.textSize
190- setPadding(16 , 8 , 16 , 8 )
191193 gravity = Gravity .CENTER
194+ layoutParams = FrameLayout .LayoutParams (
195+ FrameLayout .LayoutParams .MATCH_PARENT ,
196+ FrameLayout .LayoutParams .WRAP_CONTENT
197+ ).apply {
198+ marginEnd = dpToPx(context, 48 )
199+ gravity = Gravity .CENTER_VERTICAL
200+ }
201+ }
192202
193- elevation = 100f
194- translationZ = 100f
195-
196- if (isDraggable) {
197- setupDragAndDrop(this )
198- } else {
199- setOnClickListener {
200- visibility = if (visibility == View .VISIBLE ) View .GONE else View .VISIBLE
201- }
203+ val close = ImageView (context).apply {
204+ id = R .id.mb_version_close_button
205+ setPadding(
206+ dpToPx(context, 8 ),
207+ dpToPx(context, 8 ),
208+ dpToPx(context, 8 ),
209+ dpToPx(context, 8 )
210+ )
211+ setImageResource(android.R .drawable.ic_menu_close_clear_cancel)
212+ imageTintList = ContextCompat .getColorStateList(context, android.R .color.white)
213+ layoutParams = FrameLayout .LayoutParams (
214+ FrameLayout .LayoutParams .WRAP_CONTENT ,
215+ FrameLayout .LayoutParams .WRAP_CONTENT ,
216+ Gravity .END or Gravity .CENTER_VERTICAL
217+ )
218+ setOnClickListener {
219+ dismissedForSession = true
220+ removeOverlayFromActivity(act)
202221 }
203222 }
223+
224+ container.addView(tv)
225+ container.addView(close)
226+
227+ if (isDraggable) setupDragOnly(container)
228+
229+ return container
204230 }
205231
232+
206233 /* *
207234 * Generates the display text for the overlay.
208235 * If customText is set, it returns that. Otherwise, generates version info from PackageManager.
@@ -264,51 +291,55 @@ object MBVersionOverlay {
264291 * @since 1.0.1
265292 */
266293 @SuppressLint(" ClickableViewAccessibility" )
267- private fun setupDragAndDrop (view : TextView ) {
294+ private fun setupDragOnly (view : View ) {
268295 var dX = 0f
269296 var dY = 0f
270- var lastAction = 0
271-
272- view.setOnTouchListener { v, event ->
273- when (event.actionMasked) {
274- android.view.MotionEvent .ACTION_DOWN -> {
275- dX = v.x - event.rawX
276- dY = v.y - event.rawY
277- lastAction = android.view.MotionEvent .ACTION_DOWN
297+ var startX = 0f
298+ var startY = 0f
299+ var dragging = false
300+ val slop = ViewConfiguration .get(view.context).scaledTouchSlop
301+
302+ view.setOnTouchListener { v, e ->
303+ when (e.actionMasked) {
304+ MotionEvent .ACTION_DOWN -> {
305+ dX = v.x - e.rawX
306+ dY = v.y - e.rawY
307+ startX = e.rawX
308+ startY = e.rawY
309+ dragging = false
310+ v.parent?.requestDisallowInterceptTouchEvent(true )
278311 true
279312 }
280313
281- android.view.MotionEvent .ACTION_MOVE -> {
282- val newX = event.rawX + dX
283- val newY = event.rawY + dY
284-
285- val parent = v.parent as ? ViewGroup
286- parent?.let { parentView ->
287- val maxX = parentView.width - v.width
288- val maxY = parentView.height - v.height
289-
290- v.x = newX.coerceIn(0f , maxX.toFloat())
291- v.y = newY.coerceIn(0f , maxY.toFloat())
314+ MotionEvent .ACTION_MOVE -> {
315+ val dx = e.rawX - startX
316+ val dy = e.rawY - startY
317+ if (! dragging && (kotlin.math.abs(dx) > slop || kotlin.math.abs(dy) > slop)) {
318+ dragging = true
292319 }
293-
294- lastAction = android.view.MotionEvent .ACTION_MOVE
295- true
320+ if (dragging) {
321+ val p = v.parent as ? ViewGroup
322+ p?.let {
323+ val newX = (e.rawX + dX).coerceIn(0f , (it.width - v.width).toFloat())
324+ val newY = (e.rawY + dY).coerceIn(0f , (it.height - v.height).toFloat())
325+ v.x = newX; v.y = newY
326+ }
327+ true
328+ } else false
296329 }
297330
298- android.view.MotionEvent .ACTION_UP -> {
299- if (lastAction == android.view.MotionEvent .ACTION_DOWN ) {
300- v.visibility = if (v.visibility == View .VISIBLE ) View .GONE else View .VISIBLE
301- } else {
302- snapToEdge(v)
303- }
304- true
331+ MotionEvent .ACTION_UP , MotionEvent .ACTION_CANCEL -> {
332+ if (dragging) {
333+ snapToEdge(v); true
334+ } else false
305335 }
306336
307337 else -> false
308338 }
309339 }
310340 }
311341
342+
312343 /* *
313344 * Snaps the overlay to the nearest edge of the screen after dragging.
314345 * This provides a cleaner user experience by avoiding overlays in the middle of content.
@@ -367,13 +398,11 @@ object MBVersionOverlay {
367398
368399 /* *
369400 * Generates a unique ID for the overlay view to prevent duplicates and enable easy retrieval.
370- * Uses hash code of a constant string to ensure consistent ID across app lifecycle.
371- *
372401 * @return Unique integer ID for the overlay view
373402 *
374403 * @since 1.0.0
375404 */
376405 private fun getOverlayId (): Int {
377- return " mb_version_overlay_view " .hashCode()
406+ return R .id.mb_version_overlay_view
378407 }
379408}
0 commit comments