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

Commit 19a43fb7 authored by Liran Binyamin's avatar Liran Binyamin
Browse files

Update bubble bar flyout according to spec

Also add a triangle

Flag: com.android.wm.shell.enable_bubble_bar
Bug: 277815200
Test: atest BubbleBarFlyoutViewScreenshotTest
Test: atest BubbleBarFlyoutControllerTest
Change-Id: I1a16ba6c9e1a2ca4efe01524ba3ef1d049f81d68
parent 36b5ef22
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -20,11 +20,12 @@

    <ImageView
        android:id="@+id/bubble_flyout_avatar"
        android:layout_width="36dp"
        android:layout_width="50dp"
        android:layout_height="36dp"
        android:padding="@dimen/bubblebar_flyout_avatar_message_space"
        android:paddingEnd="@dimen/bubblebar_flyout_avatar_message_space"
        android:scaleType="centerInside"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        tools:src="#ff0000"/>

+8 −4
Original line number Diff line number Diff line
@@ -481,11 +481,15 @@
    <dimen name="bubble_expanded_view_drop_target_margin">16dp</dimen>

    <!-- Bubble bar flyout view -->
    <dimen name="bubblebar_flyout_padding_horizontal">14dp</dimen>
    <dimen name="bubblebar_flyout_padding_vertical">10dp</dimen>
    <dimen name="bubblebar_flyout_padding">16dp</dimen>
    <dimen name="bubblebar_flyout_elevation">4dp</dimen>
    <dimen name="bubblebar_flyout_avatar_message_space">6dp</dimen>
    <dimen name="bubblebar_flyout_max_width">96dp</dimen>
    <dimen name="bubblebar_flyout_avatar_message_space">14dp</dimen>
    <dimen name="bubblebar_flyout_min_width">238dp</dimen>
    <dimen name="bubblebar_flyout_max_width">276dp</dimen>
    <dimen name="bubblebar_flyout_triangle_width">12dp</dimen>
    <dimen name="bubblebar_flyout_triangle_height">10dp</dimen>
    <dimen name="bubblebar_flyout_triangle_overlap_amount">1dp</dimen>
    <dimen name="bubblebar_flyout_triangle_radius">2dp</dimen>

    <!-- Launcher splash screen -->
    <!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
+2 −2
Original line number Diff line number Diff line
@@ -28,12 +28,12 @@ class BubbleBarFlyoutController(
) {

    private var flyout: BubbleBarFlyoutView? = null
    val horizontalMargin =
    private val horizontalMargin =
        container.context.resources.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin)

    fun setUpFlyout(message: BubbleBarFlyoutMessage) {
        flyout?.let(container::removeView)
        val flyout = BubbleBarFlyoutView(container.context)
        val flyout = BubbleBarFlyoutView(container.context, onLeft = positioner.isOnLeft)

        flyout.translationY = positioner.targetTy

+69 −10
Original line number Diff line number Diff line
@@ -21,14 +21,17 @@ import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import com.android.launcher3.R
import com.android.launcher3.popup.RoundedArrowDrawable

/** The flyout view used to notify the user of a new bubble notification. */
class BubbleBarFlyoutView(context: Context) : ConstraintLayout(context) {
class BubbleBarFlyoutView(context: Context, private val onLeft: Boolean) :
    ConstraintLayout(context) {

    private val sender: TextView by
        lazy(LazyThreadSafetyMode.NONE) { findViewById(R.id.bubble_flyout_name) }
@@ -39,9 +42,36 @@ class BubbleBarFlyoutView(context: Context) : ConstraintLayout(context) {
    private val message: TextView by
        lazy(LazyThreadSafetyMode.NONE) { findViewById(R.id.bubble_flyout_text) }

    private val flyoutHorizontalPadding by
    private val flyoutPadding by
        lazy(LazyThreadSafetyMode.NONE) {
            context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_padding_horizontal)
            context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_padding)
        }

    private val triangleHeight by
        lazy(LazyThreadSafetyMode.NONE) {
            context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_triangle_height)
        }

    private val triangleOverlap by
        lazy(LazyThreadSafetyMode.NONE) {
            context.resources.getDimensionPixelSize(
                R.dimen.bubblebar_flyout_triangle_overlap_amount
            )
        }

    private val triangleWidth by
        lazy(LazyThreadSafetyMode.NONE) {
            context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_triangle_width)
        }

    private val triangleRadius by
        lazy(LazyThreadSafetyMode.NONE) {
            context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_triangle_radius)
        }

    private val minFlyoutWidth by
        lazy(LazyThreadSafetyMode.NONE) {
            context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_min_width)
        }

    private val maxFlyoutWidth by
@@ -50,6 +80,7 @@ class BubbleBarFlyoutView(context: Context) : ConstraintLayout(context) {
        }

    private val cornerRadius: Float
    private val triangle: Path = Path()
    private var backgroundColor = Color.BLACK

    /**
@@ -69,13 +100,19 @@ class BubbleBarFlyoutView(context: Context) : ConstraintLayout(context) {
        clipChildren = false
        clipToPadding = false

        val horizontalPadding =
            context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_padding_horizontal)
        val verticalPadding =
            context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_padding_vertical)
        setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding)
        val padding = context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_padding)
        // add extra padding to the bottom of the view to include the triangle
        setPadding(padding, padding, padding, padding + triangleHeight - triangleOverlap)
        translationZ =
            context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_elevation).toFloat()

        RoundedArrowDrawable.addDownPointingRoundedTriangleToPath(
            triangleWidth.toFloat(),
            triangleHeight.toFloat(),
            triangleRadius.toFloat(),
            triangle,
        )

        applyConfigurationColors(resources.configuration)
    }

@@ -88,15 +125,28 @@ class BubbleBarFlyoutView(context: Context) : ConstraintLayout(context) {
            avatar.visibility = GONE
        }

        val maxTextViewWidth = maxFlyoutWidth - flyoutHorizontalPadding * 2
        val minTextViewWidth: Int
        val maxTextViewWidth: Int
        if (avatar.visibility == VISIBLE) {
            minTextViewWidth = minFlyoutWidth - avatar.width - flyoutPadding * 2
            maxTextViewWidth = maxFlyoutWidth - avatar.width - flyoutPadding * 2
        } else {
            // when there's no avatar, the width of the text view is constant, so we're setting the
            // min and max to the same value
            minTextViewWidth = minFlyoutWidth - flyoutPadding * 2
            maxTextViewWidth = minTextViewWidth
        }

        if (flyoutMessage.senderName.isEmpty()) {
            sender.visibility = GONE
        } else {
            sender.minWidth = minTextViewWidth
            sender.maxWidth = maxTextViewWidth
            sender.text = flyoutMessage.senderName
            sender.visibility = VISIBLE
        }

        message.minWidth = minTextViewWidth
        message.maxWidth = maxTextViewWidth
        message.text = flyoutMessage.message
    }
@@ -106,14 +156,23 @@ class BubbleBarFlyoutView(context: Context) : ConstraintLayout(context) {
            0f,
            0f,
            width.toFloat(),
            height.toFloat(),
            height.toFloat() - triangleHeight + triangleOverlap,
            cornerRadius,
            cornerRadius,
            backgroundPaint,
        )
        drawTriangle(canvas)
        super.onDraw(canvas)
    }

    private fun drawTriangle(canvas: Canvas) {
        canvas.save()
        val triangleX = if (onLeft) cornerRadius else width - cornerRadius - triangleWidth
        canvas.translate(triangleX, (height - triangleHeight).toFloat())
        canvas.drawPath(triangle, backgroundPaint)
        canvas.restore()
    }

    private fun applyConfigurationColors(configuration: Configuration) {
        val nightModeFlags = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
        val isNightModeOn = nightModeFlags == Configuration.UI_MODE_NIGHT_YES
+74 −6
Original line number Diff line number Diff line
@@ -56,10 +56,10 @@ class BubbleBarFlyoutViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
        )

    @Test
    fun bubbleBarFlyoutView_noAvatar() {
        screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar") { activity ->
    fun bubbleBarFlyoutView_noAvatar_onRight() {
        screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar_onRight") { activity ->
            activity.actionBar?.hide()
            val flyout = BubbleBarFlyoutView(context)
            val flyout = BubbleBarFlyoutView(context, onLeft = false)
            flyout.setData(
                BubbleBarFlyoutMessage(
                    senderAvatar = null,
@@ -73,10 +73,44 @@ class BubbleBarFlyoutViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
    }

    @Test
    fun bubbleBarFlyoutView_avatar() {
        screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar") { activity ->
    fun bubbleBarFlyoutView_noAvatar_onLeft() {
        screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar_onLeft") { activity ->
            activity.actionBar?.hide()
            val flyout = BubbleBarFlyoutView(context)
            val flyout = BubbleBarFlyoutView(context, onLeft = true)
            flyout.setData(
                BubbleBarFlyoutMessage(
                    senderAvatar = null,
                    senderName = "sender",
                    message = "message",
                    isGroupChat = false,
                )
            )
            flyout
        }
    }

    @Test
    fun bubbleBarFlyoutView_noAvatar_longMessage() {
        screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar_longMessage") { activity ->
            activity.actionBar?.hide()
            val flyout = BubbleBarFlyoutView(context, onLeft = true)
            flyout.setData(
                BubbleBarFlyoutMessage(
                    senderAvatar = null,
                    senderName = "sender",
                    message = "really, really, really, really, really long message. like really.",
                    isGroupChat = false,
                )
            )
            flyout
        }
    }

    @Test
    fun bubbleBarFlyoutView_avatar_onRight() {
        screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar_onRight") { activity ->
            activity.actionBar?.hide()
            val flyout = BubbleBarFlyoutView(context, onLeft = false)
            flyout.setData(
                BubbleBarFlyoutMessage(
                    senderAvatar = ColorDrawable(Color.RED),
@@ -88,4 +122,38 @@ class BubbleBarFlyoutViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
            flyout
        }
    }

    @Test
    fun bubbleBarFlyoutView_avatar_onLeft() {
        screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar_onLeft") { activity ->
            activity.actionBar?.hide()
            val flyout = BubbleBarFlyoutView(context, onLeft = true)
            flyout.setData(
                BubbleBarFlyoutMessage(
                    senderAvatar = ColorDrawable(Color.RED),
                    senderName = "sender",
                    message = "message",
                    isGroupChat = true,
                )
            )
            flyout
        }
    }

    @Test
    fun bubbleBarFlyoutView_avatar_longMessage() {
        screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar_longMessage") { activity ->
            activity.actionBar?.hide()
            val flyout = BubbleBarFlyoutView(context, onLeft = true)
            flyout.setData(
                BubbleBarFlyoutMessage(
                    senderAvatar = ColorDrawable(Color.RED),
                    senderName = "sender",
                    message = "really, really, really, really, really long message. like really.",
                    isGroupChat = true,
                )
            )
            flyout
        }
    }
}