1 /* <lambda>null2 * Copyright (C) 2020 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 17 package com.android.systemui.controls 18 19 import android.annotation.StringRes 20 import android.content.Context 21 import android.graphics.CornerPathEffect 22 import android.graphics.drawable.ShapeDrawable 23 import android.util.TypedValue 24 import android.view.LayoutInflater 25 import android.view.View 26 import android.view.ViewGroup 27 import android.view.animation.AccelerateInterpolator 28 import android.view.animation.DecelerateInterpolator 29 import android.widget.TextView 30 import com.android.systemui.Prefs 31 import com.android.systemui.R 32 import com.android.systemui.recents.TriangleShape 33 34 /** 35 * Manager for showing an onboarding tooltip on screen. 36 * 37 * The tooltip can be made to appear below or above a point. The number of times it will appear 38 * is determined by an shared preference (defined in [Prefs]). 39 * 40 * @property context A context to use to inflate the views and retrieve shared preferences from 41 * @property preferenceName name of the preference to use to track the number of times the tooltip 42 * has been shown. 43 * @property maxTimesShown the maximum number of times to show the tooltip 44 * @property below whether the tooltip should appear below (with up pointing arrow) or above (down 45 * pointing arrow) the specified point. 46 * @see [TooltipManager.show] 47 */ 48 class TooltipManager( 49 context: Context, 50 private val preferenceName: String, 51 private val maxTimesShown: Int = 2, 52 private val below: Boolean = true 53 ) { 54 55 companion object { 56 private const val SHOW_DELAY_MS: Long = 500 57 private const val SHOW_DURATION_MS: Long = 300 58 private const val HIDE_DURATION_MS: Long = 100 59 } 60 61 private var shown = Prefs.getInt(context, preferenceName, 0) 62 63 val layout: ViewGroup = 64 LayoutInflater.from(context).inflate(R.layout.controls_onboarding, null) as ViewGroup 65 val preferenceStorer = { num: Int -> 66 Prefs.putInt(context, preferenceName, num) 67 } 68 69 init { 70 layout.alpha = 0f 71 } 72 73 private val textView = layout.requireViewById<TextView>(R.id.onboarding_text) 74 private val dismissView = layout.requireViewById<View>(R.id.dismiss).apply { 75 setOnClickListener { 76 hide(true) 77 } 78 } 79 80 private val arrowView = layout.requireViewById<View>(R.id.arrow).apply { 81 val typedValue = TypedValue() 82 context.theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true) 83 val toastColor = context.resources.getColor(typedValue.resourceId, context.theme) 84 val arrowRadius = context.resources.getDimensionPixelSize( 85 R.dimen.recents_onboarding_toast_arrow_corner_radius) 86 val arrowLp = layoutParams 87 val arrowDrawable = ShapeDrawable(TriangleShape.create( 88 arrowLp.width.toFloat(), arrowLp.height.toFloat(), below)) 89 val arrowPaint = arrowDrawable.paint 90 arrowPaint.color = toastColor 91 // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable. 92 arrowPaint.pathEffect = CornerPathEffect(arrowRadius.toFloat()) 93 setBackground(arrowDrawable) 94 } 95 96 init { 97 if (!below) { 98 layout.removeView(arrowView) 99 layout.addView(arrowView) 100 (arrowView.layoutParams as ViewGroup.MarginLayoutParams).apply { 101 bottomMargin = topMargin 102 topMargin = 0 103 } 104 } 105 } 106 107 /** 108 * Show the tooltip 109 * 110 * @param stringRes the id of the string to show in the tooltip 111 * @param x horizontal position (w.r.t. screen) for the arrow point 112 * @param y vertical position (w.r.t. screen) for the arrow point 113 */ 114 fun show(@StringRes stringRes: Int, x: Int, y: Int) { 115 if (!shouldShow()) return 116 textView.setText(stringRes) 117 shown++ 118 preferenceStorer(shown) 119 layout.post { 120 val p = IntArray(2) 121 layout.getLocationOnScreen(p) 122 layout.translationX = (x - p[0] - layout.width / 2).toFloat() 123 layout.translationY = (y - p[1]).toFloat() - if (!below) layout.height else 0 124 if (layout.alpha == 0f) { 125 layout.animate() 126 .alpha(1f) 127 .withLayer() 128 .setStartDelay(SHOW_DELAY_MS) 129 .setDuration(SHOW_DURATION_MS) 130 .setInterpolator(DecelerateInterpolator()) 131 .start() 132 } 133 } 134 } 135 136 /** 137 * Hide the tooltip 138 * 139 * @param animate whether to animate the fade out 140 */ 141 fun hide(animate: Boolean = false) { 142 if (layout.alpha == 0f) return 143 layout.post { 144 if (animate) { 145 layout.animate() 146 .alpha(0f) 147 .withLayer() 148 .setStartDelay(0) 149 .setDuration(HIDE_DURATION_MS) 150 .setInterpolator(AccelerateInterpolator()) 151 .start() 152 } else { 153 layout.animate().cancel() 154 layout.alpha = 0f 155 } 156 } 157 } 158 159 private fun shouldShow() = shown < maxTimesShown 160 }