1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.taskbar 17 18 import android.animation.Animator 19 import android.animation.AnimatorSet 20 import android.animation.ObjectAnimator 21 import android.annotation.SuppressLint 22 import android.content.Context 23 import android.graphics.Rect 24 import android.graphics.drawable.GradientDrawable 25 import android.util.AttributeSet 26 import android.util.Property 27 import android.view.Gravity 28 import android.view.MotionEvent 29 import android.view.View 30 import android.widget.LinearLayout 31 import android.widget.Switch 32 import androidx.core.view.postDelayed 33 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE 34 import com.android.launcher3.R 35 import com.android.launcher3.popup.ArrowPopup 36 import com.android.launcher3.popup.RoundedArrowDrawable 37 import com.android.launcher3.util.DisplayController 38 import com.android.launcher3.util.Themes 39 import com.android.launcher3.views.ActivityContext 40 41 /** Popup view with arrow for taskbar pinning */ 42 class TaskbarDividerPopupView<T : TaskbarActivityContext> 43 @JvmOverloads 44 constructor( 45 context: Context, 46 attrs: AttributeSet? = null, 47 defStyleAttr: Int = 0, 48 ) : ArrowPopup<T>(context, attrs, defStyleAttr) { 49 companion object { 50 private const val TAG = "TaskbarDividerPopupView" 51 private const val DIVIDER_POPUP_CLOSING_DELAY = 333L 52 private const val DIVIDER_POPUP_CLOSING_ANIMATION_DURATION = 83L 53 54 @JvmStatic 55 fun createAndPopulate( 56 view: View, 57 taskbarActivityContext: TaskbarActivityContext, 58 ): TaskbarDividerPopupView<*> { 59 val taskMenuViewWithArrow = 60 taskbarActivityContext.layoutInflater.inflate( 61 R.layout.taskbar_divider_popup_menu, 62 taskbarActivityContext.dragLayer, 63 false 64 ) as TaskbarDividerPopupView<*> 65 66 return taskMenuViewWithArrow.populateForView(view) 67 } 68 } 69 70 private lateinit var dividerView: View 71 72 private val popupCornerRadius = Themes.getDialogCornerRadius(context) 73 private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width) 74 private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height) 75 private val arrowPointRadius = resources.getDimension(R.dimen.popup_arrow_corner_radius) 76 77 private var alwaysShowTaskbarOn = !DisplayController.isTransientTaskbar(context) 78 private var didPreferenceChange = false 79 private var verticalOffsetForPopupView = 80 resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_vertical_margin) 81 82 /** Callback invoked when the pinning popup view is closing. */ 83 var onCloseCallback: (preferenceChanged: Boolean) -> Unit = {} 84 85 init { 86 // This synchronizes the arrow and menu to open at the same time 87 mOpenChildFadeStartDelay = mOpenFadeStartDelay 88 mOpenChildFadeDuration = mOpenFadeDuration 89 mCloseFadeStartDelay = mCloseChildFadeStartDelay 90 mCloseFadeDuration = mCloseChildFadeDuration 91 } 92 93 override fun isOfType(type: Int): Boolean = type and TYPE_TASKBAR_PINNING_POPUP != 0 94 95 override fun getTargetObjectLocation(outPos: Rect) { 96 popupContainer.getDescendantRectRelativeToSelf(dividerView, outPos) 97 } 98 99 @SuppressLint("UseSwitchCompatOrMaterialCode", "ClickableViewAccessibility") 100 override fun onFinishInflate() { 101 super.onFinishInflate() 102 val taskbarSwitchOption = requireViewById<LinearLayout>(R.id.taskbar_switch_option) 103 val alwaysShowTaskbarSwitch = requireViewById<Switch>(R.id.taskbar_pinning_switch) 104 val taskbarVisibilityIcon = requireViewById<View>(R.id.taskbar_pinning_visibility_icon) 105 106 alwaysShowTaskbarSwitch.isChecked = alwaysShowTaskbarOn 107 alwaysShowTaskbarSwitch.setOnTouchListener { view, event -> 108 (view.parent as View).onTouchEvent(event) 109 } 110 alwaysShowTaskbarSwitch.setOnClickListener { view -> (view.parent as View).performClick() } 111 112 if (ActivityContext.lookupContext<TaskbarActivityContext>(context).isGestureNav) { 113 taskbarSwitchOption.setOnClickListener { 114 alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn 115 onClickAlwaysShowTaskbarSwitchOption() 116 } 117 } else { 118 alwaysShowTaskbarSwitch.isEnabled = false 119 } 120 121 if (!alwaysShowTaskbarSwitch.isEnabled) { 122 taskbarVisibilityIcon.background.setTint( 123 resources.getColor(android.R.color.system_neutral2_500, context.theme) 124 ) 125 } 126 } 127 128 /** Orient object as usual and then center object horizontally. */ 129 override fun orientAboutObject() { 130 super.orientAboutObject() 131 x = mTempRect.centerX() - measuredWidth / 2f 132 } 133 134 override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean { 135 if (ev?.action == MotionEvent.ACTION_DOWN) { 136 if (!popupContainer.isEventOverView(this, ev)) { 137 close(true) 138 } 139 } else if (popupContainer.isEventOverView(dividerView, ev)) { 140 return true 141 } 142 return false 143 } 144 145 private fun populateForView(view: View): TaskbarDividerPopupView<*> { 146 dividerView = view 147 tryUpdateBackground() 148 return this 149 } 150 151 /** Updates the text background to match the shape of this background (when applicable). */ 152 private fun tryUpdateBackground() { 153 if (background !is GradientDrawable) { 154 return 155 } 156 val background = background as GradientDrawable 157 val color = context.getColor(R.color.popup_shade_first) 158 val backgroundMask = GradientDrawable() 159 backgroundMask.setColor(color) 160 backgroundMask.shape = GradientDrawable.RECTANGLE 161 if (background.cornerRadii != null) { 162 backgroundMask.cornerRadii = background.cornerRadii 163 } else { 164 backgroundMask.cornerRadius = background.cornerRadius 165 } 166 167 setBackground(backgroundMask) 168 } 169 170 override fun addArrow() { 171 super.addArrow() 172 // Change arrow location to the middle of popup. 173 mArrow.x = (dividerView.x + dividerView.width / 2) - (mArrowWidth / 2) 174 } 175 176 override fun updateArrowColor() { 177 if (!Gravity.isVertical(mGravity)) { 178 mArrow.background = 179 RoundedArrowDrawable( 180 arrowWidth, 181 arrowHeight, 182 arrowPointRadius, 183 popupCornerRadius, 184 measuredWidth.toFloat(), 185 measuredHeight.toFloat(), 186 (measuredWidth - arrowWidth) / 2, // arrowOffsetX 187 -mArrowOffsetVertical.toFloat(), // arrowOffsetY 188 false, // isPointingUp 189 true, // leftAligned 190 context.getColor(R.color.popup_shade_first), 191 ) 192 elevation = mElevation 193 mArrow.elevation = mElevation 194 } 195 } 196 197 override fun getExtraVerticalOffset(): Int { 198 return (mActivityContext.deviceProfile.taskbarHeight - 199 mActivityContext.deviceProfile.taskbarIconSize) / 2 + verticalOffsetForPopupView 200 } 201 202 override fun onCreateCloseAnimation(anim: AnimatorSet?) { 203 // If taskbar pinning preference changed insert custom close animation for popup menu. 204 if (didPreferenceChange) { 205 mOpenCloseAnimator = getCloseAnimator() 206 } 207 onCloseCallback(didPreferenceChange) 208 onCloseCallback = {} 209 } 210 211 /** Aligning the view pivot to center for animation. */ 212 override fun setPivotForOpenCloseAnimation() { 213 pivotX = measuredWidth / 2f 214 pivotY = measuredHeight.toFloat() 215 } 216 217 private fun getCloseAnimator(): AnimatorSet { 218 val alphaValues = floatArrayOf(1f, 0f) 219 val translateYValue = 220 if (!alwaysShowTaskbarOn) verticalOffsetForPopupView else -verticalOffsetForPopupView 221 val alpha = getAnimatorOfFloat(this, ALPHA, *alphaValues) 222 val arrowAlpha = getAnimatorOfFloat(mArrow, ALPHA, *alphaValues) 223 val translateY = 224 ObjectAnimator.ofFloat( 225 this, 226 TRANSLATION_Y, 227 *floatArrayOf(this.translationY, this.translationY + translateYValue) 228 ) 229 val arrowTranslateY = 230 ObjectAnimator.ofFloat( 231 mArrow, 232 TRANSLATION_Y, 233 *floatArrayOf(mArrow.translationY, mArrow.translationY + translateYValue) 234 ) 235 val animatorSet = AnimatorSet() 236 animatorSet.playTogether(alpha, arrowAlpha, translateY, arrowTranslateY) 237 return animatorSet 238 } 239 240 private fun getAnimatorOfFloat( 241 view: View, 242 property: Property<View, Float>, 243 vararg values: Float 244 ): Animator { 245 val animator: Animator = ObjectAnimator.ofFloat(view, property, *values) 246 animator.setDuration(DIVIDER_POPUP_CLOSING_ANIMATION_DURATION) 247 animator.interpolator = EMPHASIZED_ACCELERATE 248 return animator 249 } 250 251 private fun onClickAlwaysShowTaskbarSwitchOption() { 252 didPreferenceChange = true 253 // Allow switch animation to finish and then close the popup. 254 postDelayed(DIVIDER_POPUP_CLOSING_DELAY) { 255 if (isOpen) { 256 close(true) 257 } 258 } 259 } 260 } 261