Merge "Add education tip to widget picker." into sc-dev
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user