From 67b71c16f471ad83134c8f0405a86f77026567bc Mon Sep 17 00:00:00 2001 From: "DESKTOP-DJRPVMC\\trido" Date: Thu, 20 Apr 2023 16:06:02 +0700 Subject: [PATCH 1/5] feat: revamp notification config methods the notification can be shown later --- .../FloatingBubbleService.kt | 145 ++++++++---------- .../testfloatingbubble/MainActivityKt.kt | 1 + .../torrydo/testfloatingbubble/MyServiceKt.kt | 31 ++-- .../java_sample/MyService.java | 22 +-- 4 files changed, 101 insertions(+), 98 deletions(-) diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt index eb52dac..6f30b96 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt @@ -5,7 +5,6 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.Service import android.content.Intent -import android.graphics.Color import android.os.Build import android.os.IBinder import androidx.annotation.ChecksSdkIntAtLeast @@ -48,22 +47,23 @@ abstract class FloatingBubbleService : Service() { override fun onBind(intent: Intent?): IBinder? = null override fun onCreate() { super.onCreate() + + if (!isDrawOverlaysPermissionGranted()) { + throw PermissionDeniedException() + } + isRunning = true currentRoute = initialRoute() + initialNotification()?.let { + notify(it) + } + when (currentRoute) { - Route.Empty -> { - startWithNotification() - } - Route.Bubble -> { - startWithNotification() - showBubbles() - } - Route.ExpandableView -> { - startWithNotification() - showExpandableView() - } + Route.Empty -> {} + Route.Bubble -> showBubbles() + Route.ExpandableView -> showExpandableView() } } @@ -73,23 +73,7 @@ abstract class FloatingBubbleService : Service() { isRunning = false } - /** - * init view instances and notification - * */ - @Throws(PermissionDeniedException::class) - private fun startWithNotification() { - - if (!isDrawOverlaysPermissionGranted()) { - throw PermissionDeniedException() - } - - if (isHigherThanAndroid8()) { - showForegroundNotification() - } - - } - - // region Public Methods ----------------------------------------------------------------------- + // region Show/Hide methods ----------------------------------------------------------------------- /** * get current route @@ -103,7 +87,9 @@ abstract class FloatingBubbleService : Service() { .build() } floatingBubble!!.showIcon() + currentRoute = Route.Bubble + } /** @@ -112,6 +98,10 @@ abstract class FloatingBubbleService : Service() { fun removeBubbles() { floatingBubble?.removeIcon() floatingBubble?.tryRemoveCloseBubbleAndBackground() + + if (currentRoute == Route.Bubble) { + currentRoute = Route.Empty + } } /** @@ -130,7 +120,9 @@ abstract class FloatingBubbleService : Service() { } try { expandableView!!.show() + currentRoute = Route.ExpandableView + } catch (e: Exception) { // Log.e("<>", "showExpandableView: ", e) return false @@ -141,6 +133,10 @@ abstract class FloatingBubbleService : Service() { fun removeExpandableView() { expandableView?.remove() + + if (currentRoute == Route.ExpandableView) { + currentRoute = Route.Empty + } } /** @@ -152,34 +148,9 @@ abstract class FloatingBubbleService : Service() { removeBubbles() currentRoute = Route.Empty - } - - //endregion - - //region Interface ---------------------------------------------------------------------------- - - private val customExpandableViewListener = object : ExpandableView.Action { - override fun popToBubble() { - removeExpandableView() - showBubbles() - } } - private val customFloatingBubbleServiceInteractor = object : FloatingBubble.ServiceInteractor { - override fun requestStop() { - removeAllViews() - stopSelf() - } - } - - private val customFloatingBubbleAction = object : FloatingBubble.Action { - override fun navigateToExpandableView() { - if (showExpandableView()) { - removeBubbles() - } - } - } //endregion //region Notification -------------------------------------------------------------------------- @@ -190,60 +161,79 @@ abstract class FloatingBubbleService : Service() { open fun notificationId() = 101 /** - * create a new instance by calling `setupNotificationBuilder`, then update it to the existing notification + * show the notification or update if already exists * */ - fun updateNotification() { - val notification = setupNotificationBuilder(channelId()) - NotificationManagerCompat.from(this).notify(notificationId(), notification) - } + fun notify(notification: Notification) { - private fun showForegroundNotification() { + if (isNotificationInitialized) { + NotificationManagerCompat.from(this).notify(notificationId(), notification) + return + } - val channelId = if (isHigherThanAndroid8()) { + if (isAndroid8OrHigher()) { createNotificationChannel(channelId(), channelName()) - } else { - // In earlier version, channel ID is not required - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - "" } - val notification = setupNotificationBuilder(channelId) startForeground(notificationId(), notification) isNotificationInitialized = true - } + } - open fun setupNotificationBuilder(channelId: String): Notification { - return NotificationCompat.Builder(this, channelId) + open fun initialNotification(): Notification? { + return NotificationCompat.Builder(this, channelId()) .setOngoing(true) .setSmallIcon(R.drawable.ic_rounded_blue_diamond) .setContentTitle("bubble is running") .setPriority(PRIORITY_MIN) .setCategory(Notification.CATEGORY_SERVICE) + .setSilent(true) .build() } @RequiresApi(Build.VERSION_CODES.O) - private fun createNotificationChannel( + open fun createNotificationChannel( channelId: String, channelName: String - ): String { + ) { val channel = NotificationChannel( channelId, channelName, - NotificationManager.IMPORTANCE_DEFAULT // IMPORTANCE_NONE recreate the notification + NotificationManager.IMPORTANCE_DEFAULT // IMPORTANCE_NONE recreate the notification if update ) - channel.lightColor = Color.BLUE channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE val notificationManagerCompat = NotificationManagerCompat.from(this) notificationManagerCompat.createNotificationChannel(channel) - return channelId } //endregion - //region View builders --------------------------------------------------------------------- + //region Interface implementation -------------------------------------------------------------- + + private val customExpandableViewListener = object : ExpandableView.Action { + override fun popToBubble() { + removeExpandableView() + showBubbles() + } + } + + private val customFloatingBubbleServiceInteractor = object : FloatingBubble.ServiceInteractor { + override fun requestStop() { + removeAllViews() + stopSelf() + } + } + + private val customFloatingBubbleAction = object : FloatingBubble.Action { + override fun navigateToExpandableView() { + if (showExpandableView()) { + removeBubbles() + } + } + } + //endregion + + //region Builder --------------------------------------------------------------------- open fun initialRoute(): Route = Route.Bubble @@ -253,9 +243,8 @@ abstract class FloatingBubbleService : Service() { //endregion - //region Helper Method ------------------------------------------------------------------------- + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O) - private fun isHigherThanAndroid8() = Build.VERSION.SDK_INT >= AndroidVersions.`8` - //endregion + private fun isAndroid8OrHigher() = Build.VERSION.SDK_INT >= AndroidVersions.`8` } \ No newline at end of file diff --git a/app/src/main/java/com/torrydo/testfloatingbubble/MainActivityKt.kt b/app/src/main/java/com/torrydo/testfloatingbubble/MainActivityKt.kt index 656ea71..55ae3a8 100644 --- a/app/src/main/java/com/torrydo/testfloatingbubble/MainActivityKt.kt +++ b/app/src/main/java/com/torrydo/testfloatingbubble/MainActivityKt.kt @@ -28,6 +28,7 @@ class MainActivityKt : AppCompatActivity() { } else { val intent = Intent(this, MyServiceKt::class.java) intent.putExtra("size", 60) + intent.putExtra("noti_message", "HELLO FROM MAIN ACT") ContextCompat.startForegroundService(this, intent) isVisible = false } diff --git a/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt b/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt index 4b2aa34..137ad95 100644 --- a/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt +++ b/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt @@ -23,7 +23,9 @@ class MyServiceKt : FloatingBubbleService() { } else { removeAllViews() } - updateNotification() + + notify(myNotification(FloatingBubbleReceiver.isEnabled)) + } FloatingBubbleReceiver.stop_bubble_function = { stopSelf() @@ -39,13 +41,16 @@ class MyServiceKt : FloatingBubbleService() { private var size = 0 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - val route = intent?.getStringExtra("route") val _size = intent?.getIntExtra("size", 0) + noti_message = intent?.getStringExtra("noti_message") + size = _size ?: 0 + notify(myNotification(true)) + showBubbles() when (route) { @@ -59,20 +64,19 @@ class MyServiceKt : FloatingBubbleService() { return START_STICKY } - /** - * Sets up a notification for Bubble on Android 8 and up. - * @param channelId The ID of the notification channel. - * @return The notification instance. - */ - override fun setupNotificationBuilder(channelId: String): Notification { + var noti_message: String? = "" - val builder = NotificationCompat.Builder(this, channelId) + private fun myNotification( + isVisible: Boolean + ): Notification{ + val builder = NotificationCompat.Builder(this, channelId()) .setOngoing(true) .setSmallIcon(R.drawable.ic_rounded_blue_diamond) .setContentTitle("bubble is running") - .setContentText("click to do nothing") + .setContentText(noti_message) .setPriority(NotificationCompat.PRIORITY_MIN) .setCategory(Notification.CATEGORY_SERVICE) + .setSilent(true) // Create the hide action button @@ -99,7 +103,7 @@ class MyServiceKt : FloatingBubbleService() { ) val actionStop = NotificationCompat.Action.Builder(null, "Stop", actionStopPendingIntent) - val action1 = if (FloatingBubbleReceiver.isEnabled) { + val action1 = if (isVisible) { NotificationCompat.Action.Builder(null, "Hide", actionHidePendingIntent).build() } else { NotificationCompat.Action.Builder(null, "Show", actionHidePendingIntent).build() @@ -110,6 +114,10 @@ class MyServiceKt : FloatingBubbleService() { return builder.build() } + override fun initialNotification(): Notification? { + return null + } + override fun channelId() = "your_channel_id" override fun channelName() = "your_channel_name" override fun notificationId() = 69 @@ -223,4 +231,5 @@ class MyServiceKt : FloatingBubbleService() { override fun onCloseExpandableView() {} }) } + } \ No newline at end of file diff --git a/app/src/main/java/com/torrydo/testfloatingbubble/java_sample/MyService.java b/app/src/main/java/com/torrydo/testfloatingbubble/java_sample/MyService.java index 47d8ffe..5471119 100644 --- a/app/src/main/java/com/torrydo/testfloatingbubble/java_sample/MyService.java +++ b/app/src/main/java/com/torrydo/testfloatingbubble/java_sample/MyService.java @@ -28,13 +28,11 @@ public class MyService extends FloatingBubbleService { /** * Sets up a notification for Bubble on Android 8 and up. * - * @param channelId The ID of the notification channel. * @return The notification instance. */ - @NonNull @Override - public Notification setupNotificationBuilder(@NonNull String channelId) { - return new NotificationCompat.Builder(this, channelId) + public Notification initialNotification() { + return new NotificationCompat.Builder(this, channelId()) .setOngoing(true) .setSmallIcon(R.drawable.ic_rounded_blue_diamond) .setContentTitle("bubble is running") @@ -73,6 +71,7 @@ public int onStartCommand(@Nullable Intent intent, int flags, int startId) { assert intent != null; this.data = intent.getStringExtra("key1"); + if (this.data != null) { try { // this.showExpandableView(); @@ -134,13 +133,16 @@ public void onClick() { } @Override - public void onMove(float x, float y) {} // The location of the finger on the screen which triggers the movement of the bubble. + public void onMove(float x, float y) { + } // The location of the finger on the screen which triggers the movement of the bubble. @Override - public void onUp(float x, float y) {} // ..., when finger release from bubble + public void onUp(float x, float y) { + } // ..., when finger release from bubble @Override - public void onDown(float x, float y) {} // ..., when finger tap the bubble + public void onDown(float x, float y) { + } // ..., when finger tap the bubble }) // set bubble's opacity @@ -172,10 +174,12 @@ public ExpandableView.Builder setupExpandableView(@NonNull ExpandableView.Action .addExpandableViewListener(new ExpandableView.Listener() { @Override - public void onOpenExpandableView() {} + public void onOpenExpandableView() { + } @Override - public void onCloseExpandableView() {} + public void onCloseExpandableView() { + } }); } } From b11490d3b6e76ff756f363c57dc13b2b8eb5f15d Mon Sep 17 00:00:00 2001 From: "DESKTOP-DJRPVMC\\trido" Date: Fri, 21 Apr 2023 14:54:39 +0700 Subject: [PATCH 2/5] fix: bubble display incorrectly in landscape mode --- .../floatingbubbleview/FloatingBubble.kt | 23 +++--- .../FloatingBubbleService.kt | 44 ++++++++++- .../floatingbubbleview/FloatingBubbleView.kt | 31 +++++--- .../FloatingCloseBubbleView.kt | 73 ++++++++++--------- .../torrydo/floatingbubbleview/ScreenInfo.kt | 53 ++++++++++---- .../torrydo/testfloatingbubble/MyServiceKt.kt | 8 +- 6 files changed, 150 insertions(+), 82 deletions(-) diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubble.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubble.kt index 881e46f..64f4825 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubble.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubble.kt @@ -20,12 +20,7 @@ internal constructor( private var bottomBackground: FloatingBottomBackground? = null init { - ScreenInfo.getScreenSize(builder.context).also { - ScreenInfo.widthPx = it.width - ScreenInfo.heightPx = it.height - } - ScreenInfo.statusBarHeightPx = ScreenInfo.getStatusBarHeight(builder.context) - ScreenInfo.softNavBarHeightPx = ScreenInfo.getSoftNavigationBarSize(builder.context) + ScreenInfo.onOrientationChanged(builder.context) bubbleView = FloatingBubbleView( builder.addFloatingBubbleListener(CustomBubbleListener()) @@ -78,7 +73,7 @@ internal constructor( fun requestStop() } - // internal func --------------------------------------------------------------------------------- + //region public methods ------------------------------------------------------------------------ internal fun showIcon() { bubbleView.show() @@ -98,7 +93,9 @@ internal constructor( bottomBackground?.remove() } - // private func -------------------------------------------------------------------------------- + //endregion + + //region Private func -------------------------------------------------------------------------- private inner class CustomBubbleListener : Listener { @@ -108,7 +105,7 @@ internal constructor( override fun onMove(x: Float, y: Float) { when (builder.behavior) { BubbleBehavior.DYNAMIC_CLOSE_BUBBLE -> { - bubbleView.updateLocation(x, y) + bubbleView.updateLocationUI(x, y) val (bubbleX, bubbleY) = bubbleView.rawLocationOnScreen() closeBubbleView?.animateCloseIconByBubble(bubbleX.toInt(), bubbleY.toInt()) } @@ -130,7 +127,7 @@ internal constructor( } else { isBubbleAnimated = false - bubbleView.updateLocation(x, y) + bubbleView.updateLocationUI(x, y) } } } @@ -180,7 +177,9 @@ internal constructor( ) == 0.0f } - // builder ------------------------------------------------------------------------------------- + //endregion + + //region Builder ------------------------------------------------------------------------------- class Builder(internal val context: Context) { @@ -420,4 +419,6 @@ internal constructor( } } + //endregion + } \ No newline at end of file diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt index 6f30b96..1ae395b 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt @@ -5,8 +5,10 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.Service import android.content.Intent +import android.content.res.Configuration import android.os.Build import android.os.IBinder +import android.util.Log import androidx.annotation.ChecksSdkIntAtLeast import androidx.annotation.Discouraged import androidx.annotation.RequiresApi @@ -24,6 +26,8 @@ abstract class FloatingBubbleService : Service() { private var currentRoute = Route.Empty + private var orientation: Int = -1 + companion object { @Volatile @@ -53,7 +57,7 @@ abstract class FloatingBubbleService : Service() { } isRunning = true - + orientation = this.resources.configuration.orientation currentRoute = initialRoute() initialNotification()?.let { @@ -65,15 +69,49 @@ abstract class FloatingBubbleService : Service() { Route.Bubble -> showBubbles() Route.ExpandableView -> showExpandableView() } + + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + + // Check if the configuration has actually changed. + if (newConfig.orientation != orientation) { + val newOrientation = newConfig.orientation + when (newOrientation) { + Configuration.ORIENTATION_PORTRAIT -> { + ScreenInfo.onOrientationChanged(this) +// floatingBubble?.onOrientationChanged(newOrientation) + floatingBubble?.removeIcon() + floatingBubble?.tryRemoveCloseBubbleAndBackground() + floatingBubble = null + showBubbles() + } + Configuration.ORIENTATION_LANDSCAPE -> { + ScreenInfo.onOrientationChanged(this) +// floatingBubble?.onOrientationChanged(newOrientation) + floatingBubble?.removeIcon() + floatingBubble?.tryRemoveCloseBubbleAndBackground() + floatingBubble = null + showBubbles() + } + else -> { + Log.d("<>", "change undefine: "); + } + } + orientation = newOrientation + } + } override fun onDestroy() { removeAllViews() super.onDestroy() + isRunning = false } - // region Show/Hide methods ----------------------------------------------------------------------- + //region Show/Hide methods --------------------------------------------------------------------- /** * get current route @@ -233,7 +271,7 @@ abstract class FloatingBubbleService : Service() { } //endregion - //region Builder --------------------------------------------------------------------- + //region Builder ------------------------------------------------------------------------------- open fun initialRoute(): Route = Route.Bubble diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt index 66de034..6085930 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt @@ -1,6 +1,7 @@ package com.torrydo.floatingbubbleview import android.annotation.SuppressLint +import android.content.res.Configuration import android.graphics.Point import android.graphics.PointF import android.view.* @@ -21,14 +22,22 @@ internal class FloatingBubbleView( private val rawPointOnDown = PointF(0f, 0f) private val newPoint = Point(0, 0) - private val halfScreenWidth = ScreenInfo.widthPx / 2 - private val halfScreenHeight = ScreenInfo.heightPx / 2 + private var halfScreenWidth = ScreenInfo.widthPx / 2 + private var halfScreenHeight = ScreenInfo.heightPx / 2 - private val halfIconWidthPx: Int - private val halfIconHeightPx: Int + private var halfIconWidthPx: Int + private var halfIconHeightPx: Int + + private var orientation = -1 init { + orientation = if (ScreenInfo.heightPx >= ScreenInfo.widthPx) { + Configuration.ORIENTATION_PORTRAIT + } else { + Configuration.ORIENTATION_LANDSCAPE + } + builder.bubbleSizePx.also { if (it.notZero()) { width = it.width @@ -42,8 +51,10 @@ internal class FloatingBubbleView( setupLayoutParams() setupBubbleProperties() customTouch() + } + private val isPortrait get() = orientation == Configuration.ORIENTATION_PORTRAIT private var isAnimatingToEdge = false fun animateIconToEdge(onFinished: (() -> Unit)? = null) { @@ -105,7 +116,7 @@ internal class FloatingBubbleView( } - fun updateLocation(x: Float, y: Float) { + fun updateLocationUI(x: Float, y: Float) { val mIconDeltaX = x - rawPointOnDown.x val mIconDeltaY = y - rawPointOnDown.y @@ -133,12 +144,12 @@ internal class FloatingBubbleView( /** * set location without updating UI * */ - fun setLocation(x: Float, y: Float){ + fun setLocation(x: Float, y: Float) { newPoint.x = x.toInt() newPoint.y = y.toInt() } - fun rawLocationOnScreen(): Pair{ + fun rawLocationOnScreen(): Pair { return Pair(newPoint.x.toFloat(), newPoint.y.toFloat()) } @@ -217,12 +228,9 @@ internal class FloatingBubbleView( return@setOnTouchListener true } - } } - // override - override fun setupLayoutParams() { super.setupLayoutParams() @@ -231,12 +239,11 @@ internal class FloatingBubbleView( WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + builder.bubbleStyle?.let { windowAnimations = it } } - - } } \ No newline at end of file diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt index 204492e..673ac17 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt @@ -1,7 +1,9 @@ package com.torrydo.floatingbubbleview +import android.util.Log import android.view.LayoutInflater import android.view.WindowManager +import androidx.core.content.OnConfigurationChangedProvider import com.torrydo.floatingbubbleview.databinding.CloseBubbleBinding internal class FloatingCloseBubbleView( @@ -12,25 +14,24 @@ internal class FloatingCloseBubbleView( ) { companion object { - internal const val DEFAULT_PADDING_BOTTOM_PX = 30 + internal const val DEFAULT_PADDING_BOTTOM_PX = 20 } - private val LIMIT_FLY_HEIGHT: Int + private var LIMIT_FLY_HEIGHT: Int - val halfWidthPx: Int - val halfHeightPx: Int + var halfWidthPx: Int + var halfHeightPx: Int - private val halfScreenWidth: Int - val baseX: Int - val baseY: Int + private var halfScreenWidth: Int + var baseX: Int + var baseY: Int - private val centerCloseBubbleX: Int - private val centerCloseBubbleY: Int + private var centerCloseBubbleX: Int + private var centerCloseBubbleY: Int - private val closablePerimeterPx: Int + private var closablePerimeterPx: Int init { - builder.closeBubbleSizePx.also { if (it.notZero()) { width = it.width @@ -53,7 +54,7 @@ internal class FloatingCloseBubbleView( ScreenInfo.statusBarHeightPx - DEFAULT_PADDING_BOTTOM_PX - centerCloseBubbleX = baseX + halfWidthPx + centerCloseBubbleX = halfScreenWidth centerCloseBubbleY = baseY + halfHeightPx closablePerimeterPx = builder.closablePerimeterDp.toPx() @@ -62,7 +63,6 @@ internal class FloatingCloseBubbleView( setupCloseBubbleProperties() } - override fun setupLayoutParams() { super.setupLayoutParams() @@ -73,29 +73,7 @@ internal class FloatingCloseBubbleView( } } - // private ------------------------------------------------------------------------------------- - - - private fun setupCloseBubbleProperties() { - val icBitmap = builder.closeIconBitmap ?: R.drawable.ic_close_bubble.toBitmap( - builder.context - ) - - binding.closeBubbleImg.apply { - setImageBitmap(icBitmap) - - layoutParams.width = this@FloatingCloseBubbleView.width - layoutParams.height = this@FloatingCloseBubbleView.height - - alpha = builder.opacity - - } - - windowParams.apply { - this.x = baseX - this.y = baseY - } - } + //region Public methods ------------------------------------------------------------------------ /** * @param x is the top left x axis of the bubble @@ -165,6 +143,29 @@ internal class FloatingCloseBubbleView( } } + //endregion ------------------------------------------------------------------------------------ + + private fun setupCloseBubbleProperties() { + val icBitmap = builder.closeIconBitmap ?: R.drawable.ic_close_bubble.toBitmap( + builder.context + ) + + binding.closeBubbleImg.apply { + setImageBitmap(icBitmap) + + layoutParams.width = this@FloatingCloseBubbleView.width + layoutParams.height = this@FloatingCloseBubbleView.height + + alpha = builder.opacity + + } + + windowParams.apply { + this.x = baseX + this.y = baseY + } + } + private fun stickToBubble(x: Int, y: Int) { val middleBubbleX = x + builder.bubbleSizePx.width / 2 diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ScreenInfo.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ScreenInfo.kt index c1679e3..8523978 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ScreenInfo.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ScreenInfo.kt @@ -1,6 +1,7 @@ package com.torrydo.floatingbubbleview import android.content.Context +import android.content.res.Configuration import android.content.res.Resources import android.os.Build import android.util.DisplayMetrics @@ -20,29 +21,40 @@ internal object ScreenInfo { internal var statusBarHeightPx = 0 internal var softNavBarHeightPx = 0 - // functions ----------------------------------------------------------------------------------- - private val api: Api = - when { - // android 4 to 5 - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.R -> ApiLevel23() - // android 11 and above - Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> ApiLevel30() - // android 5 to 11 - else -> Api() + // methods ------------------------------------------------------------------------------------- + fun onOrientationChanged(context: Context) { + + statusBarHeightPx = getStatusBarHeight(context) + softNavBarHeightPx = getSoftNavigationBarHeight(context) + + getScreenSize(context).also { + if(it.height >= it.width){ // portrait + widthPx = it.width + heightPx = it.height + }else{ + widthPx = it.width - statusBarHeightPx - softNavBarHeightPx + heightPx = it.height + } } + } + + //region Internal methods ---------------------------------------------------------------------- + /** * @return pixel * */ - internal fun getStatusBarHeight(context: Context): Int{ - val statusBarHeightId = context.resources.getIdentifier("status_bar_height", "dimen", "android") + internal fun getStatusBarHeight(context: Context): Int { + val statusBarHeightId = + context.resources.getIdentifier("status_bar_height", "dimen", "android") return context.resources.getDimensionPixelSize(statusBarHeightId) } - fun getSoftNavigationBarSize(context: Context): Int { + fun getSoftNavigationBarHeight(context: Context): Int { var result = 0 - val resourceId = context.resources.getIdentifier("navigation_bar_height", "dimen", "android") + val resourceId = + context.resources.getIdentifier("navigation_bar_height", "dimen", "android") if (resourceId > 0) { result = context.resources.getDimensionPixelSize(resourceId) } @@ -52,7 +64,7 @@ internal object ScreenInfo { /** * Returns screen size in pixels. */ - internal fun getScreenSize(context: Context): Size { + fun getScreenSize(context: Context): Size { return WeakReference(context).get()?.let { api.getScreenSize(it) @@ -60,6 +72,19 @@ internal object ScreenInfo { } ?: Size(0, 0) } + //endregion + + + private val api: Api = + when { + // android 4 to 5 + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.R -> ApiLevel23() + // android 11 and above + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> ApiLevel30() + // android 5 to 11 + else -> Api() + } + private open class Api { // api level 21 to 23 diff --git a/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt b/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt index 137ad95..522024b 100644 --- a/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt +++ b/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt @@ -170,13 +170,9 @@ class MyServiceKt : FloatingBubbleService() { ) { } // The location of the finger on the screen which triggers the movement of the bubble. - override fun onUp(x: Float, y: Float) { - Log.d("<>", "onup: ${x} - ${y}"); - } // ..., when finger release from bubble + override fun onUp(x: Float, y: Float) {} // ..., when finger release from bubble - override fun onDown(x: Float, y: Float) { - Log.d("<>", "ondown ${x}-${y}: "); - } // ..., when finger tap the bubble + override fun onDown(x: Float, y: Float) {} // ..., when finger tap the bubble }) // set bubble's opacity .opacity(1f) From 1de2d8354134e4a505e048b7e702f2624d52ce72 Mon Sep 17 00:00:00 2001 From: "DESKTOP-DJRPVMC\\trido" Date: Fri, 21 Apr 2023 14:57:09 +0700 Subject: [PATCH 3/5] fix: keyboard auto show up when open ex-view in some android versions, (5,6,7,...). the keyboard automatically show up when open the expandable-view --- .../main/java/com/torrydo/floatingbubbleview/ExpandableView.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ExpandableView.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ExpandableView.kt index 7b88b3a..5b428f8 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ExpandableView.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ExpandableView.kt @@ -96,7 +96,6 @@ class ExpandableView( gravity = Gravity.TOP flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND dimAmount = builder.dim // default = 0.5f - softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE builder.viewStyle?.let { windowAnimations = it From f75f120153e80c29a8b8bf46a01254817b729b55 Mon Sep 17 00:00:00 2001 From: "DESKTOP-DJRPVMC\\trido" Date: Sat, 22 Apr 2023 14:36:36 +0700 Subject: [PATCH 4/5] fix: bubble showup when ex-view visible and screen rotated when the ex-view is visible, rotate the device make the bubble appear --- .../FloatingBubbleService.kt | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt index 1ae395b..513a92e 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt @@ -81,19 +81,29 @@ abstract class FloatingBubbleService : Service() { when (newOrientation) { Configuration.ORIENTATION_PORTRAIT -> { ScreenInfo.onOrientationChanged(this) -// floatingBubble?.onOrientationChanged(newOrientation) - floatingBubble?.removeIcon() - floatingBubble?.tryRemoveCloseBubbleAndBackground() - floatingBubble = null - showBubbles() + if (currentRoute == Route.Bubble) { + floatingBubble?.removeIcon() + floatingBubble?.tryRemoveCloseBubbleAndBackground() + floatingBubble = null + showBubbles() + } else { + floatingBubble = setupBubble(customFloatingBubbleAction) + .addServiceInteractor(customFloatingBubbleServiceInteractor) + .build() + } } Configuration.ORIENTATION_LANDSCAPE -> { ScreenInfo.onOrientationChanged(this) -// floatingBubble?.onOrientationChanged(newOrientation) - floatingBubble?.removeIcon() - floatingBubble?.tryRemoveCloseBubbleAndBackground() - floatingBubble = null - showBubbles() + if (currentRoute == Route.Bubble) { + floatingBubble?.removeIcon() + floatingBubble?.tryRemoveCloseBubbleAndBackground() + floatingBubble = null + showBubbles() + } else { + floatingBubble = setupBubble(customFloatingBubbleAction) + .addServiceInteractor(customFloatingBubbleServiceInteractor) + .build() + } } else -> { Log.d("<>", "change undefine: "); From f918a6db6da6f4dbabca658fa9cd0908042753a9 Mon Sep 17 00:00:00 2001 From: "DESKTOP-DJRPVMC\\trido" Date: Sat, 29 Apr 2023 17:01:43 +0700 Subject: [PATCH 5/5] fix: bubble displays incorrecly in landscape mode. the bubble cannot reach the bottom of the screen in landscape mode. --- .idea/deploymentTargetDropDown.xml | 17 ++++++++ .idea/kotlinc.xml | 6 +++ .idea/misc.xml | 3 +- .../FloatingBubbleService.kt | 2 +- .../floatingbubbleview/FloatingBubbleView.kt | 6 ++- .../FloatingCloseBubbleView.kt | 8 ++-- .../torrydo/floatingbubbleview/ScreenInfo.kt | 9 ++++ README.md | 42 ++++++++++--------- app/src/main/AndroidManifest.xml | 2 + gradle.properties | 4 +- 10 files changed, 71 insertions(+), 28 deletions(-) create mode 100644 .idea/deploymentTargetDropDown.xml create mode 100644 .idea/kotlinc.xml diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..ff5b076 --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..e1eea1d --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 3fcf930..1e01b7e 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - - + diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt index 513a92e..a7c6916 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt @@ -106,7 +106,7 @@ abstract class FloatingBubbleService : Service() { } } else -> { - Log.d("<>", "change undefine: "); +// Log.d("<>", "change undefine: "); } } orientation = newOrientation diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt index 6085930..64b9226 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt @@ -132,7 +132,11 @@ internal class FloatingBubbleView( if (isAboveStatusBar) { newPoint.y = safeTopY } else if (isUnderSoftNavBar) { - newPoint.y = safeBottomY + if(ScreenInfo.isPortrait){ + newPoint.y = safeBottomY + } else if(newPoint.y - ScreenInfo.softNavBarHeightPx > safeBottomY){ + newPoint.y = safeBottomY + (ScreenInfo.softNavBarHeightPx) + } } //endregion diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt index 673ac17..d804f3f 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt @@ -1,9 +1,7 @@ package com.torrydo.floatingbubbleview -import android.util.Log import android.view.LayoutInflater import android.view.WindowManager -import androidx.core.content.OnConfigurationChangedProvider import com.torrydo.floatingbubbleview.databinding.CloseBubbleBinding internal class FloatingCloseBubbleView( @@ -14,7 +12,7 @@ internal class FloatingCloseBubbleView( ) { companion object { - internal const val DEFAULT_PADDING_BOTTOM_PX = 20 + internal const val DEFAULT_PADDING_BOTTOM_PX = 30 } private var LIMIT_FLY_HEIGHT: Int @@ -54,6 +52,10 @@ internal class FloatingCloseBubbleView( ScreenInfo.statusBarHeightPx - DEFAULT_PADDING_BOTTOM_PX + if (ScreenInfo.isLandscape) { + baseY = baseY - DEFAULT_PADDING_BOTTOM_PX + ScreenInfo.softNavBarHeightPx + } + centerCloseBubbleX = halfScreenWidth centerCloseBubbleY = baseY + halfHeightPx diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ScreenInfo.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ScreenInfo.kt index 8523978..8427ed7 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ScreenInfo.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ScreenInfo.kt @@ -5,8 +5,10 @@ import android.content.res.Configuration import android.content.res.Resources import android.os.Build import android.util.DisplayMetrics +import android.util.Log import android.util.Size import android.view.Display +import android.view.WindowInsets import android.view.WindowManager import android.view.WindowMetrics import androidx.annotation.RequiresApi @@ -21,6 +23,9 @@ internal object ScreenInfo { internal var statusBarHeightPx = 0 internal var softNavBarHeightPx = 0 + var isPortrait = true + val isLandscape get() = isPortrait.not() + // methods ------------------------------------------------------------------------------------- fun onOrientationChanged(context: Context) { @@ -32,9 +37,13 @@ internal object ScreenInfo { if(it.height >= it.width){ // portrait widthPx = it.width heightPx = it.height + + isPortrait = true }else{ widthPx = it.width - statusBarHeightPx - softNavBarHeightPx heightPx = it.height + + isPortrait = false } } diff --git a/README.md b/README.md index 96efa1f..ee26ac5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ๐Ÿ€Floating Bubble View An Android library that adds floating bubbles to your home screen ๐ŸŽจ, supports both XML and ๐Ÿ’˜ Jetpack Compose -  +
https://user-images.githubusercontent.com/85553681/223082521-789146d2-c8f7-4e54-a4d7-f281cd495404.mp4 @@ -400,16 +400,11 @@ public class MyService extends FloatingBubbleService { ... - /** - * Sets up a notification for Bubble on Android 8 and up. - * - * @param channelId The ID of the notification channel. - * @return The notification instance. - */ - @NonNull + // config the initial notification for Bubble on Android 8 and up. + // return null if you want to show the notification later. @Override - public Notification setupNotificationBuilder(@NonNull String channelId) { - return new NotificationCompat.Builder(this, channelId) + public Notification initialNotification() { + return new NotificationCompat.Builder(this, channelId()) .setOngoing(true) .setSmallIcon(R.drawable.ic_rounded_blue_diamond) .setContentTitle("bubble is running") @@ -446,19 +441,16 @@ public class MyService extends FloatingBubbleService { ```kotlin class MyService : FloatingBubbleService() { - /** - * Sets up a notification for Bubble on Android 8 and up. - * @param channelId The ID of the notification channel. - * @return The notification instance. - */ - override fun setupNotificationBuilder(channelId: String): Notification { - return NotificationCompat.Builder(this, channelId) + // config the initial notification for Bubble on Android 8 and up. + // return null if you want to show the notification later. + open fun initialNotification(): Notification? { + return NotificationCompat.Builder(this, channelId()) .setOngoing(true) .setSmallIcon(R.drawable.ic_rounded_blue_diamond) .setContentTitle("bubble is running") - .setContentText("click to do nothing") - .setPriority(NotificationCompat.PRIORITY_MIN) + .setPriority(PRIORITY_MIN) .setCategory(Notification.CATEGORY_SERVICE) + .setSilent(true) .build() } @@ -470,6 +462,17 @@ class MyService : FloatingBubbleService() { +
Notice since Android 13 โš  +
+ +Starting in Android 13 (API level 33), notifications are only visible if the "POST_NOTIFICATIONS" permission is granted.
+ +> The service will run normally even if the notification is not visible. ๐Ÿ€ + +> P/s: You still need to initialize the notification before showing any view. + +
+ ### 3, Check if bubble is running ```java @@ -544,6 +547,7 @@ fun SomeComposable(){ | `showExpandableView()` | Displays the expandable-view | | `removeExpandableView()` | Removes the expandable-view | | `removeAllViews()` | Removes all views | +| `notify(Notification)` | Displays or updates notification | diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0424964..a3595f7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,8 @@ + +