Merge "Add education tip to widget picker." into sc-dev

This commit is contained in:
Alina Zaidi
2021-04-13 19:26:30 +00:00
committed by Android (Google) Code Review
3 changed files with 144 additions and 1 deletions
+3
View File
@@ -140,6 +140,9 @@
<dimen name="widget_row_padding">8dp</dimen>
<dimen name="widget_row_divider">2dp</dimen>
<dimen name="widget_picker_education_tip_width">120dp</dimen>
<dimen name="widget_picker_education_tip_min_margin">4dp</dimen>
<!-- Padding applied to shortcut previews -->
<dimen name="shortcut_preview_padding_left">0dp</dimen>
<dimen name="shortcut_preview_padding_right">0dp</dimen>
@@ -21,6 +21,7 @@ import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.drawable.ShapeDrawable;
import android.os.Handler;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
@@ -29,6 +30,8 @@ import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.core.content.ContextCompat;
import com.android.launcher3.AbstractFloatingView;
@@ -43,6 +46,7 @@ import com.android.launcher3.graphics.TriangleShape;
*/
public class ArrowTipView extends AbstractFloatingView {
private static final String TAG = ArrowTipView.class.getSimpleName();
private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
private static final long SHOW_DELAY_MS = 200;
private static final long SHOW_DURATION_MS = 300;
@@ -105,7 +109,8 @@ public class ArrowTipView extends AbstractFloatingView {
arrowLp.width, arrowLp.height, false));
Paint arrowPaint = arrowDrawable.getPaint();
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
context.getTheme()
.resolveAttribute(android.R.attr.colorAccent, typedValue, true);
arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId));
// The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
arrowPaint.setPathEffect(new CornerPathEffect(
@@ -164,6 +169,60 @@ public class ArrowTipView extends AbstractFloatingView {
return this;
}
/**
* Show the ArrowTipView (tooltip) custom aligned.
*
* @param text The text to be shown in the tooltip.
* @param arrowXCoord The X coordinate for the arrow on the tip. The arrow is usually in the
* center of ArrowTipView unless the ArrowTipView goes beyond screen margin.
* @param yCoord The Y coordinate of the bottom of the tooltip.
* @return The tool tip view.
*/
@Nullable public ArrowTipView showAtLocation(String text, int arrowXCoord, int yCoord) {
ViewGroup parent = mActivity.getDragLayer();
@Px int parentViewWidth = parent.getWidth();
@Px int textViewWidth = getContext().getResources()
.getDimensionPixelSize(R.dimen.widget_picker_education_tip_width);
@Px int minViewMargin = getContext().getResources()
.getDimensionPixelSize(R.dimen.widget_picker_education_tip_min_margin);
if (parentViewWidth < textViewWidth + 2 * minViewMargin) {
Log.w(TAG, "Cannot display tip on a small screen of size: " + parentViewWidth);
return null;
}
TextView textView = findViewById(R.id.text);
textView.setText(text);
textView.setWidth(textViewWidth);
parent.addView(this);
requestLayout();
post(() -> setY(yCoord - getHeight()));
post(() -> {
float halfWidth = getWidth() / 2f;
float xCoord;
if (arrowXCoord - halfWidth < minViewMargin) {
xCoord = minViewMargin;
} else if (arrowXCoord + halfWidth > parentViewWidth - minViewMargin) {
xCoord = parentViewWidth - minViewMargin - getWidth();
} else {
xCoord = arrowXCoord - halfWidth;
}
setX(xCoord);
findViewById(R.id.arrow).setX(arrowXCoord - xCoord);
requestLayout();
});
setAlpha(0);
animate()
.alpha(1f)
.withLayer()
.setStartDelay(SHOW_DELAY_MS)
.setDuration(SHOW_DURATION_MS)
.setInterpolator(Interpolators.DEACCEL)
.start();
return this;
}
/**
* Register a callback fired when toast is hidden
*/
@@ -41,6 +41,7 @@ import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.DeviceProfile;
@@ -51,6 +52,7 @@ import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.TopRoundedCornerView;
import com.android.launcher3.widget.BaseWidgetSheet;
@@ -66,6 +68,7 @@ import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePag
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.IntStream;
/**
* Popup for showing the full list of available widgets
@@ -78,11 +81,13 @@ public class WidgetsFullSheet extends BaseWidgetSheet
private static final long DEFAULT_OPEN_DURATION = 267;
private static final long FADE_IN_DURATION = 150;
private static final long EDUCATION_TIP_DELAY_MS = 200;
private static final float VERTICAL_START_POSITION = 0.3f;
// The widget recommendation table can easily take over the entire screen on devices with small
// resolution or landscape on phone. This ratio defines the max percentage of content area that
// the table can display.
private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
private static final String WIDGETS_EDUCATION_TIP_SEEN = "launcher.widgets_education_tip_seen";
private final Rect mInsets = new Rect();
private final boolean mHasWorkProfile;
@@ -92,6 +97,35 @@ public class WidgetsFullSheet extends BaseWidgetSheet
mCurrentUser.equals(entry.mPkgItem.user);
private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
mPrimaryWidgetsFilter.negate();
private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (hasSeenEducationTip()) {
removeOnLayoutChangeListener(this);
return;
}
// Widgets are loaded asynchronously, We are adding a delay because we only want
// to show the tip when the widget preview has finished loading and rendering in
// this view.
removeCallbacks(mShowEducationTipTask);
postDelayed(mShowEducationTipTask, EDUCATION_TIP_DELAY_MS);
}
};
private final Runnable mShowEducationTipTask = () -> {
if (hasSeenEducationTip()) {
removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
return;
}
View viewForTip = getViewToShowEducationTip();
if (viewForTip != null && ViewCompat.isLaidOut(viewForTip)) {
removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
showEducationTipOnView(viewForTip);
}
};
private final int mTabsHeight;
private final int mWidgetCellHorizontalPadding;
@@ -170,6 +204,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet
mSearchAndRecommendationViewHolder.mSearchBar.initialize(
mLauncher.getPopupDataProvider(), /* searchModeListener= */ this);
if (!hasSeenEducationTip()) {
addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
}
}
@Override
@@ -564,6 +602,49 @@ public class WidgetsFullSheet extends BaseWidgetSheet
mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
}
private void showEducationTipOnView(View view) {
mLauncher.getSharedPrefs().edit().putBoolean(WIDGETS_EDUCATION_TIP_SEEN, true).apply();
int[] coords = new int[2];
view.getLocationOnScreen(coords);
ArrowTipView arrowTipView = new ArrowTipView(mLauncher);
arrowTipView.showAtLocation(
getContext().getString(R.string.long_press_widget_to_add),
/* arrowXCoord= */coords[0] + view.getWidth() / 2,
/* yCoord= */coords[1]);
}
@Nullable private View getViewToShowEducationTip() {
if (mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getVisibility() == VISIBLE
&& mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getChildCount() > 0
) {
return ((ViewGroup) mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
.getChildAt(0)).getChildAt(0);
}
AdapterHolder adapterHolder = mAdapters.get(mIsInSearchMode
? AdapterHolder.SEARCH
: mViewPager == null
? AdapterHolder.PRIMARY
: mViewPager.getCurrentPage());
WidgetsRowViewHolder viewHolderForTip =
(WidgetsRowViewHolder) IntStream.range(
0, adapterHolder.mWidgetsListAdapter.getItemCount())
.mapToObj(adapterHolder.mWidgetsRecyclerView::
findViewHolderForAdapterPosition)
.filter(viewHolder -> viewHolder instanceof WidgetsRowViewHolder)
.findFirst()
.orElse(null);
if (viewHolderForTip != null) {
return ((ViewGroup) viewHolderForTip.mTableContainer.getChildAt(0)).getChildAt(0);
}
return null;
}
private boolean hasSeenEducationTip() {
return mLauncher.getSharedPrefs().getBoolean(WIDGETS_EDUCATION_TIP_SEEN, false);
}
/** A holder class for holding adapters & their corresponding recycler view. */
private final class AdapterHolder {
static final int PRIMARY = 0;