Loading quickstep/res/layout/bubblebar_flyout.xml +3 −2 Original line number Diff line number Diff line Loading @@ -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"/> Loading quickstep/res/values/dimens.xml +8 −4 Original line number Diff line number Diff line Loading @@ -480,11 +480,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 --> Loading quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt +69 −10 Original line number Diff line number Diff line Loading @@ -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) } Loading @@ -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 Loading @@ -50,6 +80,7 @@ class BubbleBarFlyoutView(context: Context) : ConstraintLayout(context) { } private val cornerRadius: Float private val triangle: Path = Path() private var backgroundColor = Color.BLACK /** Loading @@ -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) } Loading @@ -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 } Loading @@ -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 Loading quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutViewScreenshotTest.kt +74 −6 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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), Loading @@ -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 } } } Loading
quickstep/res/layout/bubblebar_flyout.xml +3 −2 Original line number Diff line number Diff line Loading @@ -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"/> Loading
quickstep/res/values/dimens.xml +8 −4 Original line number Diff line number Diff line Loading @@ -480,11 +480,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 --> Loading
quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading
quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt +69 −10 Original line number Diff line number Diff line Loading @@ -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) } Loading @@ -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 Loading @@ -50,6 +80,7 @@ class BubbleBarFlyoutView(context: Context) : ConstraintLayout(context) { } private val cornerRadius: Float private val triangle: Path = Path() private var backgroundColor = Color.BLACK /** Loading @@ -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) } Loading @@ -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 } Loading @@ -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 Loading
quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutViewScreenshotTest.kt +74 −6 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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), Loading @@ -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 } } }