Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit da40defd authored by Ats Jenk's avatar Ats Jenk
Browse files

Show drop target for bubble bar when dragging it

Show where the expanded view will land when dragging bubble bar expanded
view to the other side.

Bug: 313661121
Test: manual
  - test that drop target does not appear when drag does not cross over
    half the screen
  - test that drop target appears when dragging over half the screen
  - test that once drop target appears, it is shown when dragging back
    to initial location
  - test that when drop target is visible, and drag to dismiss target,
    drop target is hidden
  - test that drop target is shown again after dragging out of dismiss
    target and it was showing before

Change-Id: I5503cda9a412b19ac73b3a65a20de50fc99b9e7d
parent af8f1ceb
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2024 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->
<shape android:shape="rectangle"
       xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#bf309fb5" />
    <corners android:radius="@dimen/bubble_bar_expanded_view_corner_radius" />
    <stroke android:width="1dp" android:color="#A00080FF"/>
</shape>
+22 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?><!--
  ~ Copyright (C) 2024 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->
<View xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/bubble_bar_drop_target"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:background="@drawable/bubble_drop_target_background"
    android:elevation="@dimen/bubble_elevation"
    android:importantForAccessibility="no" />
+20 −0
Original line number Diff line number Diff line
@@ -855,4 +855,24 @@ public class BubblePositioner {
    public int getBubbleBarExpandedViewPadding() {
        return mExpandedViewPadding;
    }

    /**
     * Get bubble bar expanded view bounds on screen
     */
    public void getBubbleBarExpandedViewBounds(boolean onLeft, boolean isOverflowExpanded,
            Rect out) {
        final int padding = getBubbleBarExpandedViewPadding();
        final int width = getExpandedViewWidthForBubbleBar(isOverflowExpanded);
        final int height = getExpandedViewHeightForBubbleBar(isOverflowExpanded);

        out.set(0, 0, width, height);
        int left;
        if (onLeft) {
            left = getInsets().left + padding;
        } else {
            left = getAvailableRect().width() - width - padding;
        }
        int top = getExpandedViewBottomForBubbleBar() - height;
        out.offsetTo(left, top);
    }
}
+125 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.wm.shell.bubbles.bar

import android.content.Context
import android.graphics.Rect
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.FrameLayout.LayoutParams
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner
import com.android.wm.shell.common.bubbles.BubbleBarLocation

/** Controller to show/hide drop target when bubble bar expanded view is being dragged */
class BubbleBarDropTargetController(
    val context: Context,
    val container: FrameLayout,
    val positioner: BubblePositioner
) {

    private var dropTargetView: View? = null
    private val tempRect: Rect by lazy(LazyThreadSafetyMode.NONE) { Rect() }

    /**
     * Show drop target at [location] with animation.
     *
     * If the drop target is currently visible, animates it out first, before showing it at the
     * supplied location.
     */
    fun show(location: BubbleBarLocation) {
        val targetView = dropTargetView ?: createView().also { dropTargetView = it }
        if (targetView.alpha > 0) {
            targetView.animateOut {
                targetView.updateBounds(location)
                targetView.animateIn()
            }
        } else {
            targetView.updateBounds(location)
            targetView.animateIn()
        }
    }

    /**
     * Set the view hidden or not
     *
     * Requires the drop target to be first shown by calling [show]. Otherwise does not do anything.
     */
    fun setHidden(hidden: Boolean) {
        val targetView = dropTargetView ?: return
        if (hidden) {
            targetView.animateOut()
        } else {
            targetView.animateIn()
        }
    }

    /** Remove the drop target if it is was shown. */
    fun dismiss() {
        dropTargetView?.animateOut {
            dropTargetView?.let { container.removeView(it) }
            dropTargetView = null
        }
    }

    private fun createView(): View {
        return LayoutInflater.from(context)
            .inflate(R.layout.bubble_bar_drop_target, container, false /* attachToRoot */)
            .also { view: View ->
                view.alpha = 0f
                // Add at index 0 to ensure it does not cover the bubble
                container.addView(view, 0)
            }
    }

    private fun getBounds(onLeft: Boolean, out: Rect) {
        positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOverflowExpanded */, out)
        val centerX = out.centerX()
        val centerY = out.centerY()
        out.scale(DROP_TARGET_SCALE)
        // Move rect center back to the same position as before scale
        out.offset(centerX - out.centerX(), centerY - out.centerY())
    }

    private fun View.updateBounds(location: BubbleBarLocation) {
        getBounds(location.isOnLeft(isLayoutRtl), tempRect)
        val lp = layoutParams as LayoutParams
        lp.width = tempRect.width()
        lp.height = tempRect.height()
        layoutParams = lp
        x = tempRect.left.toFloat()
        y = tempRect.top.toFloat()
    }

    private fun View.animateIn() {
        animate().alpha(1f).setDuration(DROP_TARGET_ALPHA_IN_DURATION).start()
    }

    private fun View.animateOut(endAction: Runnable? = null) {
        animate()
            .alpha(0f)
            .setDuration(DROP_TARGET_ALPHA_OUT_DURATION)
            .withEndAction(endAction)
            .start()
    }

    companion object {
        private const val DROP_TARGET_ALPHA_IN_DURATION = 150L
        private const val DROP_TARGET_ALPHA_OUT_DURATION = 100L
        private const val DROP_TARGET_SCALE = 0.9f
    }
}
+19 −5
Original line number Diff line number Diff line
@@ -81,15 +81,26 @@ class BubbleBarExpandedViewDragController(
        /**
         * Bubble bar [BubbleBarLocation] has changed as a result of dragging the expanded view.
         *
         * Triggered when drag gesture passes the middle of the screen and before touch up.
         * Can be triggered multiple times per gesture.
         * Triggered when drag gesture passes the middle of the screen and before touch up. Can be
         * triggered multiple times per gesture.
         *
         * @param location new location of the bubble bar as a result of the ongoing drag operation
         */
        fun onLocationChanged(location: BubbleBarLocation)

        /** Expanded view has been released in the dismiss target */
        fun onReleasedInDismiss()
        /**
         * Called when bubble bar is moved into or out of the dismiss target
         *
         * @param isStuck `true` if view is dragged inside dismiss target
         */
        fun onStuckToDismissChanged(isStuck: Boolean)

        /**
         * Bubble bar was released
         *
         * @param inDismiss `true` if view was release in dismiss target
         */
        fun onReleased(inDismiss: Boolean)
    }

    private inner class HandleDragListener : RelativeTouchListener() {
@@ -152,6 +163,7 @@ class BubbleBarExpandedViewDragController(
        private fun finishDrag() {
            if (!isStuckToDismiss) {
                animationHelper.animateToRestPosition()
                dragListener.onReleased(inDismiss = false)
                dismissView.hide()
            }
            isMoving = false
@@ -164,6 +176,7 @@ class BubbleBarExpandedViewDragController(
            draggedObject: MagnetizedObject<*>
        ) {
            isStuckToDismiss = true
            dragListener.onStuckToDismissChanged(isStuck = true)
        }

        override fun onUnstuckFromTarget(
@@ -175,13 +188,14 @@ class BubbleBarExpandedViewDragController(
        ) {
            isStuckToDismiss = false
            animationHelper.animateUnstuckFromDismissView(target)
            dragListener.onStuckToDismissChanged(isStuck = false)
        }

        override fun onReleasedInTarget(
            target: MagnetizedObject.MagneticTarget,
            draggedObject: MagnetizedObject<*>
        ) {
            dragListener.onReleasedInDismiss()
            dragListener.onReleased(inDismiss = true)
            dismissView.hide()
        }
    }
Loading