diff --git a/res/drawable/ic_chevron_left_rounded_700.xml b/res/drawable/ic_chevron_left_rounded_700.xml index 17cc7f89b4..c3730d3a57 100644 --- a/res/drawable/ic_chevron_left_rounded_700.xml +++ b/res/drawable/ic_chevron_left_rounded_700.xml @@ -6,6 +6,6 @@ android:tint="?attr/colorControlNormal" android:autoMirrored="true"> diff --git a/res/drawable/ic_chevron_right_rounded_700.xml b/res/drawable/ic_chevron_right_rounded_700.xml deleted file mode 100644 index 8125d64783..0000000000 --- a/res/drawable/ic_chevron_right_rounded_700.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/res/drawable/ic_circle.xml b/res/drawable/ic_circle.xml new file mode 100644 index 0000000000..2f5cf218e1 --- /dev/null +++ b/res/drawable/ic_circle.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml index 002e7b7eec..4c1464af56 100644 --- a/res/layout/user_folder_icon_normalized.xml +++ b/res/layout/user_folder_icon_normalized.xml @@ -53,6 +53,16 @@ android:textColorHighlight="?android:attr/colorControlHighlight" android:textColorHint="?attr/folderHintTextColor"/> + + + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 83c7ba3fd3..7d50d7dc02 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -48,6 +48,7 @@ + diff --git a/res/values/colors.xml b/res/values/colors.xml index 914ffd64c9..bfa588841e 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -75,6 +75,9 @@ #0B57D0 #A8C7FA + #000000 + #FFFFFF + #FFFFFFFF #FFFFFFFF #CCFFFFFF diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 2ec73c0564..88f542eb5c 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -545,6 +545,7 @@ 24dp 20dp + 32dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 9426b923c8..ea1acffd66 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -272,6 +272,8 @@ Edit Name + Paginate left + Paginate right diff --git a/res/values/styles.xml b/res/values/styles.xml index e45176efac..b7e19e5aa9 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -60,6 +60,7 @@ #89000000 @style/WidgetContainerTheme @color/page_indicator_dot_color_light + @color/page_indicator_arrow_bg_light @color/materialColorSecondaryFixed @color/materialColorOnSecondaryFixedVariant @color/folder_preview_light @@ -130,6 +131,7 @@ @color/notification_dot_color_dark @style/WidgetContainerTheme.Dark @color/page_indicator_dot_color_dark + @color/page_indicator_arrow_bg_dark @color/folder_preview_dark @color/folder_background_dark ?android:attr/colorPrimary diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 6aeafe4ac2..329e1ee044 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -28,6 +28,8 @@ import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridO import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED; import static com.android.launcher3.model.data.FolderInfo.willAcceptItemType; +import static com.android.launcher3.pageindicators.PaginationArrow.DISABLED_ARROW_OPACITY; +import static com.android.launcher3.pageindicators.PaginationArrow.FULLY_OPAQUE; import static com.android.launcher3.testing.shared.TestProtocol.FOLDER_OPENED_MESSAGE; import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; @@ -100,6 +102,7 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemFactory; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pageindicators.PageIndicatorDots; +import com.android.launcher3.pageindicators.PaginationArrow; import com.android.launcher3.util.Executors; import com.android.launcher3.util.LauncherBindableItemsContainer; import com.android.launcher3.util.Thunk; @@ -208,6 +211,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo FolderPagedView mContent; private FolderNameEditText mFolderName; private PageIndicatorDots mPageIndicator; + private PaginationArrow mLeftArrow; + private PaginationArrow mRightArrow; protected View mFooter; private int mFooterHeight; @@ -316,6 +321,15 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mFolderName.forceDisableSuggestions(true); mKeyboardInsetAnimationCallback = new KeyboardInsetAnimationCallback(this); setWindowInsetsAnimationCallback(mKeyboardInsetAnimationCallback); + + if (enableLauncherVisualRefresh()) { + mLeftArrow = findViewById(R.id.left_indicator_arrow); + mRightArrow = findViewById(R.id.right_indicator_arrow); + mRightArrow.setOnClickListener(v -> mContent.snapToPage( + mContent.getCurrentPage() + 1)); + mLeftArrow.setOnClickListener(v -> mContent.snapToPage( + mContent.getCurrentPage() - 1)); + } } /** If arrows are visible, replace the container padding with indicator padding */ @@ -324,13 +338,28 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (mPageIndicator.getVisibility() == View.VISIBLE) { // Replace the container padding with indicator padding for arrows mFooter.setPadding(0, 0, 0, 0); - mPageIndicator.setPadding(sidePadding, 0, sidePadding, 0); ((MarginLayoutParams) mFolderName.getLayoutParams()) .setMarginStart(sidePadding); + mLeftArrow.setVisibility(View.VISIBLE); + mRightArrow.setVisibility(View.VISIBLE); } else { mFooter.setPadding(sidePadding, 0, sidePadding, 0); - mPageIndicator.setPadding(0, 0, 0, 0); ((MarginLayoutParams) mFolderName.getLayoutParams()).setMarginStart(0); + mLeftArrow.setVisibility(View.GONE); + mRightArrow.setVisibility(View.GONE); + } + } + + /** + * Called when the page is switched. Sets arrow UX to a disabled appearance if the page is at + * one end or the other. + */ + public void updateArrowAlphas() { + if (enableLauncherVisualRefresh()) { + mLeftArrow.setAlpha( + 0 == mContent.getCurrentPage() ? DISABLED_ARROW_OPACITY : FULLY_OPAQUE); + mRightArrow.setAlpha(mContent.getPageCount() == mContent.getCurrentPage() + 1 + ? DISABLED_ARROW_OPACITY : FULLY_OPAQUE); } } diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index 4c86b09275..ceac72d5c4 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -48,7 +48,6 @@ import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.pageindicators.Direction; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; import com.android.launcher3.util.Thunk; @@ -131,8 +130,6 @@ public class FolderPagedView extends PagedView implements Cli public void setFolder(Folder folder) { mFolder = folder; mPageIndicator = folder.findViewById(R.id.folder_page_indicator); - mPageIndicator.setArrowClickListener(direction -> snapToPage( - (Direction.END == direction) ? mCurrentPage + 1 : mCurrentPage - 1)); initParentViews(folder); } @@ -489,6 +486,7 @@ public class FolderPagedView extends PagedView implements Cli super.notifyPageSwitchListener(prevPage); if (mFolder != null) { mFolder.updateTextViewFocus(); + mFolder.updateArrowAlphas(); } } diff --git a/src/com/android/launcher3/pageindicators/PageIndicator.java b/src/com/android/launcher3/pageindicators/PageIndicator.java index a6f76c4d44..0640bf3672 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicator.java +++ b/src/com/android/launcher3/pageindicators/PageIndicator.java @@ -15,8 +15,6 @@ */ package com.android.launcher3.pageindicators; -import java.util.function.Consumer; - /** * Base class for a page indicator. */ @@ -28,14 +26,6 @@ public interface PageIndicator { void setMarkersCount(int numMarkers); - /** - * This is only going to be used by the FolderPagedView's PageIndicator. A refactor is planned - * to separate the two purposes of this class, but in the meantime, this indicator will serve to - * let the folder snap to the page of its click, and also tell the PageIndicator not to draw - * arrows if the click listener is null (at least until after this is refactored). - */ - void setArrowClickListener(Consumer listener); - /** * Sets a flag indicating whether to pause scroll. *

Should be set to {@code true} while the screen is binding or new data is being applied, diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java index 09f3bd43f6..c2ab115c0c 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java +++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java @@ -27,39 +27,30 @@ import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.graphics.Canvas; -import android.graphics.ColorFilter; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Paint.Style; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.drawable.VectorDrawable; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.IntProperty; -import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewOutlineProvider; import android.view.animation.Interpolator; import android.view.animation.OvershootInterpolator; -import androidx.annotation.DrawableRes; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import androidx.core.content.ContextCompat; import com.android.launcher3.Insettable; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.util.Themes; -import java.util.function.Consumer; - /** * {@link PageIndicator} which shows dots per page. The active page is shown with the current * accent color. @@ -75,15 +66,12 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator private static final int PAGINATION_FADE_IN_DURATION = 83; private static final int PAGINATION_FADE_OUT_DURATION = 167; - private static final int DISABLED_ARROW_OPACITY = 97; // 38% - private static final int ENTER_ANIMATION_START_DELAY = 300; private static final int ENTER_ANIMATION_STAGGERED_DELAY = 150; private static final int ENTER_ANIMATION_DURATION = 400; private static final int HEIGHT_MULTIPLIER = 4; private static final int WIDTH_MULTIPLIER = 3; - private static final float ARROW_TOUCH_BOX_FACTOR = 2f; private static final int PAGE_INDICATOR_ALPHA = 255; private static final int DOT_ALPHA = 128; @@ -92,7 +80,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator private static final int VISIBLE_ALPHA = 255; private static final int INVISIBLE_ALPHA = 0; private Paint mPaginationPaint; - private @Nullable Consumer mOnArrowClickListener; // This value approximately overshoots to 1.5 times the original size. private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f; @@ -135,14 +122,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator private final float mGapWidth; private final float mCircleGap; private final boolean mIsRtl; - private final VectorDrawable mArrowRight; - private final VectorDrawable mArrowLeft; - private final Rect mArrowRightBounds = new Rect(); - private final Rect mArrowLeftBounds = new Rect(); - private final int mArrowTouchBoxExtraWidth; - private final int mArrowTouchBoxHeight; - private final int mArrowGapWidth; - private final int mArrowLength; private int mNumPages; private int mActivePage; @@ -194,23 +173,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator : DOT_GAP_FACTOR * mDotRadius; setOutlineProvider(new MyOutlineProver()); mIsRtl = Utilities.isRtl(getResources()); - mArrowRight = setupArrow(R.drawable.ic_chevron_right_rounded_700); - mArrowLeft = setupArrow(R.drawable.ic_chevron_left_rounded_700); - /* the width of the arrows themselves plus extra folder / touch padding. x2 for 2 arrows. */ - mArrowTouchBoxExtraWidth = 2 * ((int) ((5.5f) * mGapWidth) - + getResources().getDimensionPixelSize(R.dimen.folder_footer_horiz_padding)); - mArrowTouchBoxHeight = - getResources().getDimensionPixelSize(R.dimen.folder_footer_height_default); - mArrowLength = getResources().getDimensionPixelSize(R.dimen.folder_arrow_icon_length); - mArrowGapWidth = getResources().getDimensionPixelSize(R.dimen.folder_arrow_gap_width); - } - - private VectorDrawable setupArrow(@DrawableRes int resId) { - VectorDrawable icon = (VectorDrawable) ContextCompat.getDrawable(getContext(), resId); - ColorFilter arrowColorFilter = new PorterDuffColorFilter(mPaginationPaint.getColor(), - PorterDuff.Mode.SRC_ATOP); - icon.setColorFilter(arrowColorFilter); - return icon; } @Override @@ -448,11 +410,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator requestLayout(); } - @Override - public void setArrowClickListener(@Nullable Consumer listener) { - mOnArrowClickListener = listener; - } - @Override public void setPauseScroll(boolean pause, boolean isTwoPanels) { mIsTwoPanels = isTwoPanels; @@ -467,20 +424,13 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // TODO(b/394355070): Verify Folder Entry Animation works correctly with visual updates // Add extra spacing of mDotRadius on all sides so than entry animation could be run - // and so the hitboxes of arrows can be clicked easier. int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(widthMeasureSpec) : (int) ((mNumPages * WIDTH_MULTIPLIER + 2) * mDotRadius); int height = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(heightMeasureSpec) : (int) (HEIGHT_MULTIPLIER * mDotRadius); - if (enableLauncherVisualRefresh() && mOnArrowClickListener != null) { - // Extra height and width and gaps for accessibility arrows. - width += mArrowTouchBoxExtraWidth; - height = mArrowTouchBoxHeight; - } setMeasuredDimension(width, height); } @@ -573,17 +523,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator float bounceProgress = (posDif > 1) ? posDif - 1 : 0; float bounceAdjustment = Math.abs(currentPosition - boundedPosition) * diameter; - if (mOnArrowClickListener != null) { - // Here we draw the Left Arrow - mArrowLeft.setAlpha(boundedPosition == 0 ? DISABLED_ARROW_OPACITY : alpha); - mArrowLeftBounds.left = (int) (sTempRect.left - mArrowGapWidth - mArrowLength); - mArrowLeftBounds.top = (int) (y - (float) mArrowLength / 2); - mArrowLeftBounds.right = mArrowLeftBounds.left + mArrowLength; - mArrowLeftBounds.bottom = mArrowLeftBounds.top + mArrowLength; - mArrowLeft.setBounds(mArrowLeftBounds); - mArrowLeft.draw(canvas); - } - // Here we draw the dots, one at a time from the left-most dot to the right-most dot // 1.0 => 000000 000000111111 000000 // 1.3 => 000000 0000001111 11000000 @@ -640,17 +579,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator sTempRect.left = sTempRect.right + mGapWidth; } - if (mOnArrowClickListener != null) { - // Here we draw the Right Arrow - mArrowRight.setAlpha(boundedPosition == (mNumPages - 1) - ? DISABLED_ARROW_OPACITY : alpha); - mArrowRightBounds.left = (int) (sTempRect.left - mGapWidth + mArrowGapWidth); - mArrowRightBounds.top = (int) (y - (float) mArrowLength / 2); - mArrowRightBounds.right = mArrowRightBounds.left + mArrowLength; - mArrowRightBounds.bottom = mArrowRightBounds.top + mArrowLength; - mArrowRight.setBounds(mArrowRightBounds); - mArrowRight.draw(canvas); - } } else { // Here we draw the dots mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION)); @@ -668,27 +596,6 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator mPaginationPaint.setAlpha(alpha); } - @Override - public boolean onTouchEvent(MotionEvent ev) { - if (mOnArrowClickListener == null) { - // No - Op. Don't care about touch events - } else if ((mIsRtl && withinExpandedBounds(mArrowRightBounds, ev)) - || (!mIsRtl && withinExpandedBounds(mArrowLeftBounds, ev))) { - mOnArrowClickListener.accept(Direction.START); - } else if ((mIsRtl && withinExpandedBounds(mArrowLeftBounds, ev)) - || (!mIsRtl && withinExpandedBounds(mArrowRightBounds, ev))) { - mOnArrowClickListener.accept(Direction.END); - } - return super.onTouchEvent(ev); - } - - // For larger Touch box - private boolean withinExpandedBounds(Rect rect, MotionEvent ev) { - RectF scaledRect = new RectF(rect); - scale(scaledRect, ARROW_TOUCH_BOX_FACTOR); - return scaledRect.contains(ev.getX(), ev.getY()); - } - private static void scale(RectF rect, float factor) { float horizontalAdjustment = rect.width() * (factor - 1) / 2; float verticalAdjustment = rect.height() * (factor - 1) / 2; diff --git a/src/com/android/launcher3/pageindicators/PaginationArrow.kt b/src/com/android/launcher3/pageindicators/PaginationArrow.kt new file mode 100644 index 0000000000..77a9e3c232 --- /dev/null +++ b/src/com/android/launcher3/pageindicators/PaginationArrow.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.pageindicators + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import android.widget.ImageView +import androidx.core.content.ContextCompat +import com.android.launcher3.R + +/** + * Handles logic for the pagination arrow. The foreground and background images and the pressed / + * hovered state UX. + */ +@SuppressLint("AppCompatCustomView") +class PaginationArrow(context: Context, attrs: AttributeSet) : ImageView(context, attrs) { + private val bgCircle = ContextCompat.getDrawable(context, R.drawable.ic_circle) + + init { + foreground = ContextCompat.getDrawable(context, R.drawable.ic_chevron_left_rounded_700) + } + + override fun onTouchEvent(event: MotionEvent?): Boolean { + when (event?.action) { + MotionEvent.ACTION_DOWN -> { + background = bgCircle + background.alpha = BACKGROUND_PRESSED_OPACITY + } + MotionEvent.ACTION_UP -> background = null + } + return super.onTouchEvent(event) + } + + override fun onHoverEvent(event: MotionEvent?): Boolean { + when (event?.action) { + MotionEvent.ACTION_HOVER_ENTER -> { + background = bgCircle + background.alpha = BACKGROUND_HOVERED_OPACITY + } + MotionEvent.ACTION_HOVER_EXIT -> background = null + } + return super.onHoverEvent(event) + } + + companion object { + const val FULLY_OPAQUE = 1f + const val DISABLED_ARROW_OPACITY = .38f + + // alpha ints are 0 - 255; the former being transparent and the latter being fully opaque + private const val BACKGROUND_HOVERED_OPACITY = 28 // 11% + private const val BACKGROUND_PRESSED_OPACITY = 38 // 15% + } +} diff --git a/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java index 185c3ee9ed..e94f3a065d 100644 --- a/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java +++ b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java @@ -26,12 +26,9 @@ import androidx.annotation.Nullable; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.pageindicators.Direction; import com.android.launcher3.pageindicators.PageIndicator; import com.android.launcher3.views.ActivityContext; -import java.util.function.Consumer; - /** * Supports two indicator colors, dedicated for personal and work tabs. */ @@ -80,11 +77,6 @@ public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageInd public void setMarkersCount(int numMarkers) { } - @Override - public void setArrowClickListener(Consumer listener) { - // No-Op. All Apps doesn't need accessibility arrows for single click navigation. - } - @Override public boolean hasOverlappingRendering() { return false;