Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1e849a95 authored by Alina Zaidi's avatar Alina Zaidi Committed by Android (Google) Code Review
Browse files

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

parents 5df03e15 120d6476
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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>
+60 −1
Original line number Diff line number Diff line
@@ -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
     */
+81 −0
Original line number Diff line number Diff line
@@ -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;