diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f8ff2b5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.mp4 filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/publish-maven-central.yaml b/.github/workflows/publish-maven-central.yaml new file mode 100644 index 0000000..ffc172b --- /dev/null +++ b/.github/workflows/publish-maven-central.yaml @@ -0,0 +1,52 @@ +name: Publish to maven central + +on: +# push: +# branches: [ release/v* ] + workflow_dispatch: + +#env: +# KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} +# KEY_ALIAS: ${{ secrets.KEY_ALIAS }} +# KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout github repo + uses: actions/checkout@v2 + + - name: set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: gradle + + - name: Change wrapper permissions + run: chmod +x ./gradlew + + # Decode the secring file + - name: Decode Keystore File + id: decode_secring + uses: timheuer/base64-to-file@v1.2 + with: + fileDir: './secrets' + fileName: 'secring.gpg' + encodedString: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }} + + # generate docs + - name: Generate docs + run: ./gradlew dokkaJavadoc + + # Build bundle and publish to Maven Central + - name: Build & publish to Maven Central + run: ./gradlew + -PmavenCentralUsername=${{ secrets.MAVEN_CENTRAL_USERNAME }} + -PmavenCentralPassword=${{ secrets.MAVEN_CENTRAL_PASSWORD }} + -PsigningKeyId=${{ secrets.SIGNING_KEY_ID }} + -PsigningPassword=${{ secrets.SIGNING_PASSWORD }} + -PsigningSecretKeyRingFile="../${{ steps.decode_secring.outputs.filePath }}" + publishBundle --max-workers 1 \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index ff5b076..2e78173 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ComposeLifecycleOwner.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ComposeLifecycleOwner.kt index 8ac5cf8..ae35cbd 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ComposeLifecycleOwner.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ComposeLifecycleOwner.kt @@ -12,7 +12,7 @@ import androidx.savedstate.SavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner -class ComposeLifecycleOwner : LifecycleOwner, SavedStateRegistryOwner { +internal class ComposeLifecycleOwner : LifecycleOwner, SavedStateRegistryOwner { fun onCreate() { savedStateRegistryController.performRestore(null) diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/Exceptions.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/Exceptions.kt deleted file mode 100644 index 9b46e4f..0000000 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/Exceptions.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.torrydo.floatingbubbleview - - -class PermissionDeniedException : Exception() { - override val message: String - get() = "\"display over other app\" permission IS NOT granted!" -} - - - - diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/Extensions.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/Extensions.kt deleted file mode 100644 index 55501d6..0000000 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/Extensions.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.torrydo.floatingbubbleview - -import android.content.Context -import android.content.res.Resources.getSystem -import android.graphics.Bitmap -import android.graphics.Point -import android.util.Size -import android.view.View -import android.view.ViewTreeObserver -import androidx.core.content.ContextCompat -import androidx.core.graphics.drawable.toBitmap -import java.lang.ref.WeakReference - -internal fun Int.toBitmap(context: Context): Bitmap? { - return WeakReference(context).get()?.let { weakContext -> - ContextCompat.getDrawable(weakContext, this)!!.toBitmap() - } -} - -/** - * @return Point( 0 .. x, 0 .. y ) - * */ -internal fun View.getXYPointOnScreen(): Point { - val arr = IntArray(2) - this.getLocationOnScreen(arr) - - return Point(arr[0], arr[1]) -} - -internal fun Int.toDp(): Int = (this / getSystem().displayMetrics.density).toInt() -internal fun Int.toPx(): Int = (this * getSystem().displayMetrics.density).toInt() - -inline fun View.afterMeasured(crossinline afterMeasuredWork: () -> Unit) { - viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - if (measuredWidth > 0 && measuredHeight > 0) { - viewTreeObserver.removeOnGlobalLayoutListener(this) - afterMeasuredWork() - } - } - }) -} - -internal fun Size.isZero() = width == 0 && height == 0 -internal fun Size.notZero() = width != 0 && height != 0 -internal fun Size.largerThanZero() = width > 0 && height > 0 diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubble.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubble.kt index 64f4825..6477548 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubble.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubble.kt @@ -7,6 +7,7 @@ import android.util.Size import android.view.View import androidx.annotation.DrawableRes import androidx.annotation.StyleRes +import androidx.compose.runtime.Composable import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap @@ -19,6 +20,8 @@ internal constructor( private var closeBubbleView: FloatingCloseBubbleView? = null private var bottomBackground: FloatingBottomBackground? = null + private var isComposeInit = false + init { ScreenInfo.onOrientationChanged(builder.context) @@ -65,21 +68,41 @@ internal constructor( * */ fun onMove(x: Float, y: Float) {} - fun onClick() {} - } - internal interface ServiceInteractor{ + internal interface ServiceInteractor { fun requestStop() } //region public methods ------------------------------------------------------------------------ internal fun showIcon() { + + if(builder.composeLifecycleOwner != null){ + if(isComposeInit.not()){ + builder.composeLifecycleOwner?.onCreate() + isComposeInit = true + } + + builder.composeLifecycleOwner?.apply { + onStart() + onResume() + } + } + bubbleView.show() } internal fun removeIcon() { + + if(builder.composeLifecycleOwner != null && isComposeInit){ + builder.composeLifecycleOwner!!.apply { + onPause() + onStop() + onDestroy() + } + } + bubbleView.remove() } @@ -103,18 +126,22 @@ internal constructor( private var isBubbleAnimated = false override fun onMove(x: Float, y: Float) { + + val bubbleSizeCompat = Size(builder.bubbleView!!.width, builder.bubbleView!!.height) + when (builder.behavior) { BubbleBehavior.DYNAMIC_CLOSE_BUBBLE -> { bubbleView.updateLocationUI(x, y) val (bubbleX, bubbleY) = bubbleView.rawLocationOnScreen() closeBubbleView?.animateCloseIconByBubble(bubbleX.toInt(), bubbleY.toInt()) } + BubbleBehavior.FIXED_CLOSE_BUBBLE -> { if (isFingerInsideClosableArea(x, y)) { if (isBubbleAnimated.not()) { - val xOffset = (closeBubbleView!!.width - builder.bubbleSizePx.width) / 2 - val yOffset = (closeBubbleView!!.height - builder.bubbleSizePx.height) / 2 + val xOffset = (closeBubbleView!!.width - bubbleSizeCompat.width) / 2 + val yOffset = (closeBubbleView!!.height - bubbleSizeCompat.height) / 2 val xUpdated = closeBubbleView!!.baseX.toFloat() + xOffset val yUpdated = closeBubbleView!!.baseY.toFloat() + yOffset @@ -142,7 +169,7 @@ internal constructor( isCloseBubbleVisible = false tryRemoveCloseBubbleAndBackground() - val shouldDestroy = when(builder.behavior){ + val shouldDestroy = when (builder.behavior) { BubbleBehavior.FIXED_CLOSE_BUBBLE -> isFingerInsideClosableArea(x, y) BubbleBehavior.DYNAMIC_CLOSE_BUBBLE -> { val (bubbleX, bubbleY) = bubbleView.rawLocationOnScreen() @@ -186,10 +213,8 @@ internal constructor( private val DEFAULT_BUBBLE_SIZE_PX = 160 // bubble - internal var iconView: View? = null - internal var iconBitmap: Bitmap? = null + internal var bubbleView: View? = null internal var bubbleStyle: Int? = R.style.default_bubble_style - internal var bubbleSizePx: Size = Size(DEFAULT_BUBBLE_SIZE_PX, DEFAULT_BUBBLE_SIZE_PX) // close-bubble internal var closeIconView: View? = null @@ -199,18 +224,21 @@ internal constructor( // config internal var startPoint = Point(0, 0) - internal var opacity = 1f internal var isCloseBubbleEnabled = true internal var isAnimateToEdgeEnabled = true internal var isBottomBackgroundEnabled = false - internal var closablePerimeterDp = 100 + internal var distanceToCloseDp = 100 internal var listener: Listener? = null internal var serviceInteractor: ServiceInteractor? = null internal var behavior: BubbleBehavior = BubbleBehavior.FIXED_CLOSE_BUBBLE + // composable + internal var composeView: (@Composable ()->Unit)? = null + internal var composeLifecycleOwner: ComposeLifecycleOwner? = null + /** * choose behavior for the bubbles @@ -226,7 +254,7 @@ internal constructor( * @param dp distance between bubble and close-bubble * */ fun distanceToClose(dp: Int): Builder { - this.closablePerimeterDp = dp + this.distanceToCloseDp = dp return this } @@ -254,43 +282,27 @@ internal constructor( return this } - // being developed, therefore this function not exposed to the outside package - internal fun bubble(view: View): Builder { - iconView = view - return this - } + fun bubble(content: @Composable () -> Unit): Builder { +// bubbleView = FloatingComposeView(context).apply { +// setContent { content() } +// } + composeLifecycleOwner = ComposeLifecycleOwner() +// composeLifecycleOwner?.attachToDecorView(bubbleView!!) - /** - * set drawable to bubble with default size - * */ - fun bubble(@DrawableRes drawable: Int): Builder { - iconBitmap = ContextCompat.getDrawable(context, drawable)!!.toBitmap() - return this - } + composeView = content - /** - * set drawable to bubble width given width and height in dp - * */ - fun bubble(@DrawableRes drawable: Int, widthDp: Int, heightDp: Int): Builder { - bubbleSizePx = Size(widthDp.toPx(), heightDp.toPx()) - return bubble(drawable) + return this } /** - * set bitmap to bubble width default size - * */ - fun bubble(bitmap: Bitmap): Builder { - iconBitmap = bitmap + * set view to bubble + */ + fun bubble(view: View): Builder { + bubbleView = view +// bubbleSizePx = Size(view.measuredWidth, view.measuredHeight) return this } - /** - * set bitmap to bubble width given width and height in dp - * */ - fun bubble(bitmap: Bitmap, widthDp: Int, heightDp: Int): Builder { - bubbleSizePx = Size(widthDp.toPx(), heightDp.toPx()) - return bubble(bitmap) - } /** * set open and exit animation to bubble @@ -347,11 +359,6 @@ internal constructor( val tempListener = this.listener this.listener = object : Listener { - override fun onClick() { - tempListener?.onClick() - listener.onClick() - } - override fun onDown(x: Float, y: Float) { tempListener?.onDown(x, y) listener.onDown(x, y) @@ -371,7 +378,7 @@ internal constructor( return this } - internal fun addServiceInteractor(interactor: ServiceInteractor): Builder{ + internal fun addServiceInteractor(interactor: ServiceInteractor): Builder { this.serviceInteractor = interactor return this } @@ -404,16 +411,6 @@ internal constructor( return this } - /** - * - 0.0f: invisible - * - 0.0f < x < 1.0f: view with opacity - * - 1.0f: fully visible - * */ - fun opacity(opacity: Float): Builder { - this.opacity = opacity - return this - } - internal fun build(): FloatingBubble { return FloatingBubble(this) } diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt index a7c6916..de4ea16 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleService.kt @@ -8,13 +8,13 @@ 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 import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.PRIORITY_MIN import androidx.core.app.NotificationManagerCompat +import com.torrydo.floatingbubbleview.exceptions.PermissionDeniedException abstract class FloatingBubbleService : Service() { @@ -92,6 +92,7 @@ abstract class FloatingBubbleService : Service() { .build() } } + Configuration.ORIENTATION_LANDSCAPE -> { ScreenInfo.onOrientationChanged(this) if (currentRoute == Route.Bubble) { @@ -105,6 +106,7 @@ abstract class FloatingBubbleService : Service() { .build() } } + else -> { // Log.d("<>", "change undefine: "); } @@ -121,7 +123,8 @@ abstract class FloatingBubbleService : Service() { isRunning = false } - //region Show/Hide methods --------------------------------------------------------------------- + //region Public methods --------------------------------------------------------------------- + /** * get current route diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt index 64b9226..68c4f8d 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingBubbleView.kt @@ -4,9 +4,11 @@ import android.annotation.SuppressLint import android.content.res.Configuration import android.graphics.Point import android.graphics.PointF +import android.util.Log import android.view.* -import android.view.GestureDetector.SimpleOnGestureListener +import androidx.compose.ui.platform.ComposeView import com.torrydo.floatingbubbleview.databinding.BubbleBinding +import kotlin.math.abs internal class FloatingBubbleView( private val builder: FloatingBubble.Builder, @@ -23,10 +25,6 @@ internal class FloatingBubbleView( private val newPoint = Point(0, 0) private var halfScreenWidth = ScreenInfo.widthPx / 2 - private var halfScreenHeight = ScreenInfo.heightPx / 2 - - private var halfIconWidthPx: Int - private var halfIconHeightPx: Int private var orientation = -1 @@ -38,32 +36,26 @@ internal class FloatingBubbleView( Configuration.ORIENTATION_LANDSCAPE } - builder.bubbleSizePx.also { - if (it.notZero()) { - width = it.width - height = it.height - } - } - - halfIconWidthPx = width / 2 - halfIconHeightPx = height / 2 - setupLayoutParams() setupBubbleProperties() customTouch() } - private val isPortrait get() = orientation == Configuration.ORIENTATION_PORTRAIT - private var isAnimatingToEdge = false fun animateIconToEdge(onFinished: (() -> Unit)? = null) { if (isAnimatingToEdge) return + val bubbleWidthCompat = if (builder.bubbleView != null) { + builder.bubbleView!!.width + } else { + width + } + isAnimatingToEdge = true - val iconX = binding.root.getXYPointOnScreen().x // 0..X + val iconX = binding.root.getXYPointOnScreen().x - val isOnTheLeftSide = iconX + halfIconWidthPx < halfScreenWidth + val isOnTheLeftSide = iconX + bubbleWidthCompat / 2 < halfScreenWidth val startX: Int val endX: Int if (isOnTheLeftSide) { @@ -71,8 +63,9 @@ internal class FloatingBubbleView( endX = 0 } else { startX = iconX - endX = ScreenInfo.widthPx - width + endX = ScreenInfo.widthPx - bubbleWidthCompat } + AnimHelper.startSpringX( startValue = startX.toFloat(), finalPosition = endX.toFloat(), @@ -97,21 +90,31 @@ internal class FloatingBubbleView( private fun setupBubbleProperties() { - val iconBitmap = builder.iconBitmap ?: R.drawable.ic_rounded_blue_diamond.toBitmap( - builder.context - ) + windowParams.apply { + x = builder.startPoint.x + y = builder.startPoint.y + } - binding.bubbleView.apply { - setImageBitmap(iconBitmap) - layoutParams.width = this@FloatingBubbleView.width - layoutParams.height = this@FloatingBubbleView.height + if (builder.bubbleView != null) { - alpha = builder.opacity + binding.bubbleRoot.addView(builder.bubbleView) + + return } - windowParams.apply { - x = builder.startPoint.x - y = builder.startPoint.y + if (builder.composeLifecycleOwner != null) { + + builder.bubbleView = + binding.bubbleRoot.findViewById(R.id.view_compose).apply { + setContent { + builder.composeView!!() + } + visibility = View.VISIBLE + } + + builder.composeLifecycleOwner?.attachToDecorView(binding.bubbleRoot) + + return } } @@ -132,9 +135,9 @@ internal class FloatingBubbleView( if (isAboveStatusBar) { newPoint.y = safeTopY } else if (isUnderSoftNavBar) { - if(ScreenInfo.isPortrait){ + if (ScreenInfo.isPortrait) { newPoint.y = safeBottomY - } else if(newPoint.y - ScreenInfo.softNavBarHeightPx > safeBottomY){ + } else if (newPoint.y - ScreenInfo.softNavBarHeightPx > safeBottomY) { newPoint.y = safeBottomY + (ScreenInfo.softNavBarHeightPx) } } @@ -180,57 +183,63 @@ internal class FloatingBubbleView( ) } + private val MAX_X_MOVE = 1f + private val MAX_Y_MOVE = 1f + private var ignoreClick: Boolean = false + @SuppressLint("ClickableViewAccessibility") private fun customTouch() { - fun onActionDown(motionEvent: MotionEvent) { - prevPoint.x = windowParams.x - prevPoint.y = windowParams.y - - rawPointOnDown.x = motionEvent.rawX - rawPointOnDown.y = motionEvent.rawY - builder.listener?.onDown(motionEvent.rawX, motionEvent.rawY) - } + fun handleMovement(event: MotionEvent) { + when (event.action) { + MotionEvent.ACTION_DOWN -> { + prevPoint.x = windowParams.x + prevPoint.y = windowParams.y - fun onActionMove(motionEvent: MotionEvent) { - builder.listener?.onMove(motionEvent.rawX, motionEvent.rawY) - } + rawPointOnDown.x = event.rawX + rawPointOnDown.y = event.rawY - fun onActionUp(motionEvent: MotionEvent) { - builder.listener?.onUp(motionEvent.rawX, motionEvent.rawY) - } - - // listen actions -------------------------------------------------------------------------- + builder.listener?.onDown(event.rawX, event.rawY) + } - val gestureDetector = GestureDetector(builder.context, object : SimpleOnGestureListener() { + MotionEvent.ACTION_MOVE -> { + builder.listener?.onMove(event.rawX, event.rawY) + } -// override fun onSingleTapConfirmed(e: MotionEvent): Boolean { -// builder.listener?.onClick() -// return super.onSingleTapConfirmed(e) -// } + MotionEvent.ACTION_UP -> { + builder.listener?.onUp(event.rawX, event.rawY) + } + } + } - override fun onSingleTapUp(e: MotionEvent): Boolean { - builder.listener?.onClick() - return super.onSingleTapUp(e) + fun ignoreChildClickEvent(event: MotionEvent): Boolean{ + when(event.action){ + MotionEvent.ACTION_DOWN -> { + ignoreClick = false + } + MotionEvent.ACTION_MOVE -> { + if (abs(event.x) > MAX_X_MOVE || abs(event.y) > MAX_Y_MOVE) { + ignoreClick = true + } + } } - }) + return ignoreClick + } - binding.bubbleView.apply { + // listen actions -------------------------------------------------------------------------- - afterMeasured { updateGestureExclusion(builder.context) } - setOnTouchListener { _, motionEvent -> + binding.bubbleRoot.apply { - gestureDetector.onTouchEvent(motionEvent) + afterMeasured { updateGestureExclusion(builder.context) } - when (motionEvent.action) { - MotionEvent.ACTION_DOWN -> onActionDown(motionEvent) - MotionEvent.ACTION_MOVE -> onActionMove(motionEvent) - MotionEvent.ACTION_UP -> onActionUp(motionEvent) - } + doOnTouchEvent = { + handleMovement(it) + } - return@setOnTouchListener true + ignoreChildEvent = { motionEvent -> + ignoreChildClickEvent(motionEvent) } } } diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt index d804f3f..e404c67 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/FloatingCloseBubbleView.kt @@ -31,13 +31,8 @@ internal class FloatingCloseBubbleView( init { builder.closeBubbleSizePx.also { - if (it.notZero()) { - width = it.width - height = it.height - } else { - width = builder.bubbleSizePx.width - height = builder.bubbleSizePx.height - } + width = it.width + height = it.height } LIMIT_FLY_HEIGHT = ScreenInfo.heightPx / 10 @@ -59,7 +54,7 @@ internal class FloatingCloseBubbleView( centerCloseBubbleX = halfScreenWidth centerCloseBubbleY = baseY + halfHeightPx - closablePerimeterPx = builder.closablePerimeterDp.toPx() + closablePerimeterPx = builder.distanceToCloseDp.toPx() setupLayoutParams() setupCloseBubbleProperties() @@ -84,10 +79,12 @@ internal class FloatingCloseBubbleView( * */ fun distanceRatioFromBubbleToClosableArea(x: Int, y: Int): Float { - val centerBubbleX = x + builder.bubbleSizePx.width / 2 - val centerBubbleY = y + builder.bubbleSizePx.height / 2 + val bubbleSize = builder.bubbleSize() + + val centerBubbleX = x + bubbleSize.width / 2 + val centerBubbleY = y + bubbleSize.height / 2 - val distanceToBubble = MathHelper.distance( + val distanceToBubble = XMath.distance( x1 = centerCloseBubbleX.toDouble(), y1 = centerCloseBubbleY.toDouble(), x2 = centerBubbleX.toDouble(), @@ -102,7 +99,7 @@ internal class FloatingCloseBubbleView( } fun distanceRatioFromLocationToClosableArea(x: Float, y: Float): Float { - val distanceToLocation = MathHelper.distance( + val distanceToLocation = XMath.distance( x1 = centerCloseBubbleX.toDouble(), y1 = centerCloseBubbleY.toDouble(), x2 = x.toDouble(), @@ -144,9 +141,21 @@ internal class FloatingCloseBubbleView( update() } } - //endregion ------------------------------------------------------------------------------------ + private fun stickToBubble(x: Int, y: Int) { + + val bubbleSizeCompat = builder.bubbleSize() + + val middleBubbleX = x + bubbleSizeCompat.width / 2 + val middleBubbleY = y + bubbleSizeCompat.height / 2 + + windowParams.x = middleBubbleX - halfWidthPx + windowParams.y = middleBubbleY - halfHeightPx + + update() + } + private fun setupCloseBubbleProperties() { val icBitmap = builder.closeIconBitmap ?: R.drawable.ic_close_bubble.toBitmap( builder.context @@ -157,9 +166,6 @@ internal class FloatingCloseBubbleView( layoutParams.width = this@FloatingCloseBubbleView.width layoutParams.height = this@FloatingCloseBubbleView.height - - alpha = builder.opacity - } windowParams.apply { @@ -168,15 +174,4 @@ internal class FloatingCloseBubbleView( } } - private fun stickToBubble(x: Int, y: Int) { - - val middleBubbleX = x + builder.bubbleSizePx.width / 2 - val middleBubbleY = y + builder.bubbleSizePx.height / 2 - - windowParams.x = middleBubbleX - halfWidthPx - windowParams.y = middleBubbleY - halfHeightPx - - update() - } - } \ No newline at end of file diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/MyBubbleLayout.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/MyBubbleLayout.kt new file mode 100644 index 0000000..8e7f852 --- /dev/null +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/MyBubbleLayout.kt @@ -0,0 +1,33 @@ +package com.torrydo.floatingbubbleview + +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import android.widget.LinearLayout + +internal class MyBubbleLayout( + context: Context, + attrs: AttributeSet?, +) : LinearLayout( + context, + attrs, +) { + + internal var ignoreChildEvent: (MotionEvent) -> Boolean = { false } + internal var doOnTouchEvent: (MotionEvent) -> Unit = {} + + override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { + val b = ev?.let{ + doOnTouchEvent(it) + ignoreChildEvent(it) + } + return b ?: onInterceptTouchEvent(ev) + } + + override fun onTouchEvent(event: MotionEvent?): Boolean { + event?.let(doOnTouchEvent) + return super.onTouchEvent(event) + } + + +} \ No newline at end of file diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ThreadHelper.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ThreadHelper.kt deleted file mode 100644 index 97e632b..0000000 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/ThreadHelper.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.torrydo.floatingbubbleview - -import android.os.Handler -import android.os.Looper - - -internal inline fun onMainThread(crossinline doWork: () -> Unit) { - Handler(Looper.getMainLooper()).post { - doWork() - } -} \ No newline at end of file diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/MathHelper.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/XMath.kt similarity index 88% rename from FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/MathHelper.kt rename to FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/XMath.kt index d2f3bdf..2c17673 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/MathHelper.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/XMath.kt @@ -3,7 +3,7 @@ package com.torrydo.floatingbubbleview import kotlin.math.pow import kotlin.math.sqrt -internal object MathHelper { +internal object XMath { fun distance(x1: Double, y1: Double, x2: Double, y2: Double): Double { return sqrt((x2 - x1).pow(2) + (y2 - y1).pow(2)) diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/exceptions/PermissionDeniedException.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/exceptions/PermissionDeniedException.kt new file mode 100644 index 0000000..9b0bfd2 --- /dev/null +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/exceptions/PermissionDeniedException.kt @@ -0,0 +1,11 @@ +package com.torrydo.floatingbubbleview.exceptions + + +class PermissionDeniedException(private val m: String? = null) : Exception() { + override val message: String + get() = m ?: "\"display over other app\" permission IS NOT granted!" +} + + + + diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/viewx/ViewHelper.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/viewx/ViewHelper.kt new file mode 100644 index 0000000..d8947ad --- /dev/null +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/viewx/ViewHelper.kt @@ -0,0 +1,57 @@ +package com.torrydo.floatingbubbleview.viewx + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.util.Log +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import com.torrydo.floatingbubbleview.toPx +import kotlin.math.abs + + +object ViewHelper { + + //region load bubble + @JvmStatic + fun fromBitmap(context: Context, bm: Bitmap): View { + return ImageView(context).apply { + setImageBitmap(bm) + } + } + + @JvmStatic + fun fromBitmap(context: Context, bm: Bitmap, widthDp: Int, heightDp: Int): View { + val view = fromBitmap(context, bm) + return view.apply { + layoutParams = ViewGroup.LayoutParams(widthDp.toPx(), heightDp.toPx()) + } + } + + @JvmStatic + fun fromDrawable(context: Context, @DrawableRes drawable: Int): View { + return ImageView(context).apply { + setImageDrawable(ContextCompat.getDrawable(context, drawable)) + } + } + + @JvmStatic + fun fromDrawable( + context: Context, + @DrawableRes drawable: Int, + widthDp: Int, + heightDp: Int, + ): View { + val view = fromDrawable(context, drawable) + return view.apply { + layoutParams = ViewGroup.LayoutParams(widthDp.toPx(), heightDp.toPx()) + } + + } + //endregion + +} \ No newline at end of file diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/x.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/x.kt new file mode 100644 index 0000000..e10fad5 --- /dev/null +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/x.kt @@ -0,0 +1,34 @@ +package com.torrydo.floatingbubbleview + +import android.content.Context +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.Point +import android.util.Size +import android.view.View +import android.view.ViewTreeObserver +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.toBitmap +import java.lang.ref.WeakReference + + +/** + * return size of the bubble view if it's not null + * + * otherwise the bubble size variable + * */ +internal fun FloatingBubble.Builder.bubbleSize(): Size { + return Size(bubbleView!!.width, bubbleView!!.height) +} + +internal fun Int.toBitmap(context: Context): Bitmap? { + return WeakReference(context).get()?.let { weakContext -> + ContextCompat.getDrawable(weakContext, this)!!.toBitmap() + } +} + + + +internal fun Int.toDp(): Int = (this / Resources.getSystem().displayMetrics.density).toInt() +internal fun Int.toPx(): Int = (this * Resources.getSystem().displayMetrics.density).toInt() + diff --git a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/SystemHelper.kt b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/x_view.kt similarity index 64% rename from FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/SystemHelper.kt rename to FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/x_view.kt index a33619c..c5e39cb 100644 --- a/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/SystemHelper.kt +++ b/FloatingBubbleView/src/main/java/com/torrydo/floatingbubbleview/x_view.kt @@ -1,10 +1,12 @@ package com.torrydo.floatingbubbleview import android.content.Context +import android.graphics.Point import android.graphics.Rect import android.os.Build import android.provider.Settings import android.view.View +import android.view.ViewTreeObserver // exclude view gesture on home screen ------------------------------------------------------------- @@ -40,3 +42,24 @@ internal fun Context.isDrawOverlaysPermissionGranted(): Boolean { return Settings.canDrawOverlays(this) } + +inline fun View.afterMeasured(crossinline afterMeasuredWork: () -> Unit) { + viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + if (measuredWidth > 0 && measuredHeight > 0) { + viewTreeObserver.removeOnGlobalLayoutListener(this) + afterMeasuredWork() + } + } + }) +} + +/** + * @return Point( 0 .. x, 0 .. y ) + * */ +internal fun View.getXYPointOnScreen(): Point { + val arr = IntArray(2) + this.getLocationOnScreen(arr) + + return Point(arr[0], arr[1]) +} diff --git a/FloatingBubbleView/src/main/res/layout/bubble.xml b/FloatingBubbleView/src/main/res/layout/bubble.xml index b604391..c65c2a4 100644 --- a/FloatingBubbleView/src/main/res/layout/bubble.xml +++ b/FloatingBubbleView/src/main/res/layout/bubble.xml @@ -1,17 +1,28 @@ - + + - \ No newline at end of file + \ No newline at end of file diff --git a/README.md b/README.md index ee26ac5..f8a7cd6 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,15 @@ An Android library that adds floating bubbles to your home screen 🎨, supports
-https://user-images.githubusercontent.com/85553681/223082521-789146d2-c8f7-4e54-a4d7-f281cd495404.mp4 +
+ +| 🍀 Bubble 🎨 | 🔥 Custom 💘 | +| :-: | :-: | +| | | + + +
  @@ -193,7 +200,7 @@ Declare the dependencies in the module-level `build.gradle` file 🍀 = songs.size - 1) { + index = 0 + } else { + ++index + } + }) { + Icon( + imageVector = Icons.Default.SkipNext, + contentDescription = "play arrow", + tint = Color.Red.copy(0.8f) + ) + } + + Box( + modifier = Modifier + .fillMaxHeight() + .padding(start = 10.dp, end = 20.dp) + .pointerInput(Unit) { + detectTapGestures( + onLongPress = { + Toast + .makeText(context, "Long click: Copied \"${songs[index]}\"", Toast.LENGTH_LONG) + .show() + } + ) + }, + contentAlignment = Alignment.Center + ) { + Text( + text = songs[index], + fontWeight = FontWeight.Bold, + ) + } + + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt b/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt index 522024b..8274a5d 100644 --- a/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt +++ b/app/src/main/java/com/torrydo/testfloatingbubble/MyServiceKt.kt @@ -3,7 +3,6 @@ package com.torrydo.testfloatingbubble import android.app.Notification import android.app.PendingIntent import android.content.Intent -import android.util.Log import android.view.KeyEvent import android.view.LayoutInflater import android.view.View @@ -12,6 +11,7 @@ import android.widget.FrameLayout import android.widget.Toast import androidx.core.app.NotificationCompat import com.torrydo.floatingbubbleview.* +import com.torrydo.floatingbubbleview.viewx.ViewHelper class MyServiceKt : FloatingBubbleService() { @@ -47,7 +47,7 @@ class MyServiceKt : FloatingBubbleService() { noti_message = intent?.getStringExtra("noti_message") - size = _size ?: 0 + size = _size ?: 60 notify(myNotification(true)) @@ -123,11 +123,24 @@ class MyServiceKt : FloatingBubbleService() { override fun notificationId() = 69 override fun setupBubble(action: FloatingBubble.Action): FloatingBubble.Builder { + val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater + val v = inflater.inflate(R.layout.sample_bubble, null) + + val imgView = ViewHelper.fromDrawable(this, R.drawable.ic_rounded_blue_diamond, size, size) + + + imgView.setOnClickListener { + action.navigateToExpandableView() + } + return FloatingBubble.Builder(this) // set bubble icon attributes, currently only drawable and bitmap are supported - .bubble(R.drawable.ic_rounded_blue_diamond, size, size) - +// .bubble(imgView) +// .bubble(v) + .bubble { + BubbleCompose() + } // set style for bubble, fade animation by default .bubbleStyle(null) @@ -160,9 +173,6 @@ class MyServiceKt : FloatingBubbleService() { // add listener for the bubble .addFloatingBubbleListener(object : FloatingBubble.Listener { - override fun onClick() { - action.navigateToExpandableView() // must override `setupExpandableView`, otherwise throw an exception - } override fun onMove( x: Float, @@ -174,8 +184,6 @@ class MyServiceKt : FloatingBubbleService() { override fun onDown(x: Float, y: Float) {} // ..., when finger tap the bubble }) - // set bubble's opacity - .opacity(1f) } 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 5471119..55fde28 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 @@ -16,6 +16,7 @@ import com.torrydo.floatingbubbleview.FloatingBubble; import com.torrydo.floatingbubbleview.FloatingBubbleService; import com.torrydo.floatingbubbleview.Route; +import com.torrydo.floatingbubbleview.viewx.ViewHelper; import com.torrydo.testfloatingbubble.R; import kotlin.NotImplementedError; @@ -94,7 +95,7 @@ public FloatingBubble.Builder setupBubble(@NonNull FloatingBubble.Action action) return new FloatingBubble.Builder(this) // set bubble icon attributes, currently only drawable and bitmap are supported - .bubble(R.drawable.ic_rounded_blue_diamond, 60, 60) + .bubble(ViewHelper.fromDrawable(this, R.drawable.ic_rounded_blue_diamond, 60, 60)) // set style for bubble, fade animation by default .bubbleStyle(null) @@ -127,10 +128,6 @@ public FloatingBubble.Builder setupBubble(@NonNull FloatingBubble.Action action) // add listener for the bubble .addFloatingBubbleListener(new FloatingBubble.Listener() { - @Override - public void onClick() { - action.navigateToExpandableView(); // must override `setupExpandableView`, otherwise throw an exception - } @Override public void onMove(float x, float y) { @@ -143,10 +140,10 @@ public void onUp(float x, float y) { @Override public void onDown(float x, float y) { } // ..., when finger tap the bubble - }) + }); - // set bubble's opacity - .opacity(1f); +// // set bubble's opacity +// .opacity(1f); } @Nullable diff --git a/app/src/main/res/layout/sample_bubble.xml b/app/src/main/res/layout/sample_bubble.xml new file mode 100644 index 0000000..67c5b0b --- /dev/null +++ b/app/src/main/res/layout/sample_bubble.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/test/java/com/torrydo/testfloatingbubble/MathHelperTest.kt b/app/src/test/java/com/torrydo/testfloatingbubble/MathHelperTest.kt deleted file mode 100644 index 7b082da..0000000 --- a/app/src/test/java/com/torrydo/testfloatingbubble/MathHelperTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.torrydo.testfloatingbubble - -import com.torrydo.floatingbubbleview.MathHelper -import org.junit.Assert.* - -import org.junit.Test - -class MathHelperTest { - - @Test - fun distance() { - - val rs = MathHelper.distance(-7.0, -4.0, 17.0, 6.5) - - println(rs) - } -} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 879c54f..315e26d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -27,8 +27,8 @@ RELEASE_SIGNING_ENABLED=true GROUP=io.github.torrydo POM_ARTIFACT_ID=floating-bubble-view -VERSION_NAME=0.5.3 -#prev: 0.5.2 +VERSION_NAME=0.5.4 +#prev: 0.5.3 POM_NAME=FloatingBubbleView POM_PACKAGING=aar