content: /*
   * Copyright 2019 Google LLC
   *
   * 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
   *
   *     https://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 me.zhanghai.android.fastscroll;
  
  import android.content.Context;
  import android.graphics.Rect;
  import android.graphics.drawable.Drawable;
  import android.text.TextUtils;
  import android.view.Gravity;
  import android.view.MotionEvent;
  import android.view.View;
  import android.view.ViewConfiguration;
  import android.view.ViewGroup;
  import android.view.ViewGroupOverlay;
  import android.widget.FrameLayout;
  import android.widget.TextView;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  import androidx.appcompat.widget.AppCompatTextView;
  import androidx.core.math.MathUtils;
  import androidx.core.util.Consumer;
  
  import com.github.anrimian.musicplayer.R;
  
  import java.util.Objects;
  
  public class FastScroller {
  
      private final int mMinTouchTargetSize;
      private final int mTouchSlop;
  
      @NonNull
      private final ViewGroup mView;
      @NonNull
      private final ViewHelper mViewHelper;
      @Nullable
      private Rect mUserPadding;
      @NonNull
      private final AnimationHelper mAnimationHelper;
  
      private final int mTrackWidth;
      private final int mThumbWidth;
      private final int mThumbHeight;
  
      @NonNull
      private final View mTrackView;
      @NonNull
      private final View mThumbView;
      @NonNull
      private final TextView mPopupView;
  
      private boolean mScrollbarEnabled;
      private int mThumbOffset;
  
      private float mDownX;
      private float mDownY;
      private float mLastY;
      private float mDragStartY;
      private int mDragStartThumbOffset;
      private boolean mDragging;
  
      @NonNull
      private final Runnable mAutoHideScrollbarRunnable = this::autoHideScrollbar;
  
      @NonNull
      private final Rect mTempRect = new Rect();
  
      public FastScroller(@NonNull ViewGroup view, @NonNull ViewHelper viewHelper,
                          @Nullable Rect padding, @NonNull Drawable trackDrawable,
                          @NonNull Drawable thumbDrawable, @NonNull Consumer<TextView> popupStyle,
                          @NonNull AnimationHelper animationHelper) {
  
          mMinTouchTargetSize = view.getResources().getDimensionPixelSize(
                  R.dimen.afs_min_touch_target_size);
          Context context = view.getContext();
          mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
  
          mView = view;
          mViewHelper = viewHelper;
          mUserPadding = padding;
          mAnimationHelper = animationHelper;
  
          mTrackWidth = requireNonNegative(trackDrawable.getIntrinsicWidth(),
                  "trackDrawable.getIntrinsicWidth() < 0");
          mThumbWidth = requireNonNegative(thumbDrawable.getIntrinsicWidth(),
                  "thumbDrawable.getIntrinsicWidth() < 0");
          mThumbHeight = requireNonNegative(thumbDrawable.getIntrinsicHeight(),
                  "thumbDrawable.getIntrinsicHeight() < 0");
  
          mTrackView = new View(context);
          mTrackView.setBackground(trackDrawable);
          mTrackView.setAlpha(0f);
          mThumbView = new View(context);
          mThumbView.setBackground(thumbDrawable);
          mThumbView.setAlpha(0f);
          mPopupView = new AppCompatTextView(context);
          mPopupView.setLayoutParams(new FrameLayout.LayoutParams(
                  ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
  //        popupStyle.accept(mPopupView);
  
          ViewGroupOverlay overlay = mView.getOverlay();
          overlay.add(mTrackView);
          overlay.add(mThumbView);
          overlay.add(mPopupView);
  
          postAutoHideScrollbar();
          mPopupView.setAlpha(0);
  
          mViewHelper.addOnPreDrawListener(this::onPreDraw);
          mViewHelper.addOnScrollChangedListener(this::onScrollChanged);
          mViewHelper.addOnTouchEventListener(this::onTouchEvent);
      }
  
      private static int requireNonNegative(int value, @NonNull String message) {
          if (value < 0) {
              throw new IllegalArgumentException(message);
          }
          return value;
      }
  
      public void setPadding(int left, int top, int right, int bottom) {
          if (mUserPadding != null && mUserPadding.left == left && mUserPadding.top == top
                  && mUserPadding.right == right && mUserPadding.bottom == bottom) {
              return;
          }
          if (mUserPadding == null) {
              mUserPadding = new Rect();
          }
          mUserPadding.set(left, top, right, bottom);
          mView.invalidate();
      }
  
      public void setPadding(@Nullable Rect padding) {
          if (Objects.equals(mUserPadding, padding)) {
              return;
          }
          if (padding != null) {
              if (mUserPadding == null) {
                  mUserPadding = new Rect();
              }
              mUserPadding.set(padding);
          } else {
              mUserPadding = null;
          }
          mView.invalidate();
      }
  
      @NonNull
      private Rect getPadding() {
          if (mUserPadding != null) {
              mTempRect.set(mUserPadding);
          } else {
              mTempRect.set(mView.getPaddingLeft(), mView.getPaddingTop(), mView.getPaddingRight(),
                      mView.getPaddingBottom());
          }
          return mTempRect;
      }
  
      private void onPreDraw() {
  
          updateScrollbarState();
          mTrackView.setVisibility(mScrollbarEnabled ? View.VISIBLE : View.INVISIBLE);
          mThumbView.setVisibility(mScrollbarEnabled ? View.VISIBLE : View.INVISIBLE);
          if (!mScrollbarEnabled) {
              mPopupView.setVisibility(View.INVISIBLE);
              return;
          }
  
          int layoutDirection = mView.getLayoutDirection();
          mTrackView.setLayoutDirection(layoutDirection);
          mThumbView.setLayoutDirection(layoutDirection);
          mPopupView.setLayoutDirection(layoutDirection);
  
          boolean isLayoutRtl = layoutDirection == View.LAYOUT_DIRECTION_RTL;
          int viewWidth = mView.getWidth();
          int viewHeight = mView.getHeight();
  
          Rect padding = getPadding();
          int trackLeft = isLayoutRtl ? padding.left : viewWidth - padding.right - mTrackWidth;
          layoutView(mTrackView, trackLeft, padding.top, trackLeft + mTrackWidth,
                  Math.max(viewHeight - padding.bottom, padding.top));
          int thumbLeft = isLayoutRtl ? padding.left : viewWidth - padding.right - mThumbWidth;
          int thumbTop = padding.top + mThumbOffset;
          layoutView(mThumbView, thumbLeft, thumbTop, thumbLeft + mThumbWidth,
                  thumbTop + mThumbHeight);
  
          CharSequence popupText = mViewHelper.getPopupText();
          boolean hasPopup = !TextUtils.isEmpty(popupText);
          mPopupView.setVisibility(hasPopup ? View.VISIBLE : View.INVISIBLE);
          if (hasPopup) {
              FrameLayout.LayoutParams popupLayoutParams = (FrameLayout.LayoutParams)
                      mPopupView.getLayoutParams();
              if (!Objects.equals(mPopupView.getText(), popupText)) {
                  mPopupView.setText(popupText);
                  int widthMeasureSpec = ViewGroup.getChildMeasureSpec(
                          View.MeasureSpec.makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY),
                          padding.left + padding.right + mThumbWidth + popupLayoutParams.leftMargin
                                  + popupLayoutParams.rightMargin, popupLayoutParams.width);
                  int heightMeasureSpec = ViewGroup.getChildMeasureSpec(
                          View.MeasureSpec.makeMeasureSpec(viewHeight, View.MeasureSpec.EXACTLY),
                          padding.top + padding.bottom + popupLayoutParams.topMargin
                                  + popupLayoutParams.bottomMargin, popupLayoutParams.height);
                  mPopupView.measure(widthMeasureSpec, heightMeasureSpec);
              }
              int popupWidth = mPopupView.getMeasuredWidth();
              int popupHeight = mPopupView.getMeasuredHeight();
              int popupLeft = isLayoutRtl ? padding.left + mThumbWidth + popupLayoutParams.leftMargin
                      : viewWidth - padding.right - mThumbWidth - popupLayoutParams.rightMargin
                      - popupWidth;
              int popupAnchorY;
              switch (popupLayoutParams.gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                  case Gravity.LEFT:
                  default:
                      popupAnchorY = 0;
                      break;
                  case Gravity.CENTER_HORIZONTAL:
                      popupAnchorY = popupHeight / 2;
                      break;
                  case Gravity.RIGHT:
                      popupAnchorY = popupHeight;
                      break;
              }
              int thumbAnchorY;
              switch (popupLayoutParams.gravity & Gravity.VERTICAL_GRAVITY_MASK) {
                  case Gravity.TOP:
                  default:
                      thumbAnchorY = mThumbView.getPaddingTop();
                      break;
                  case Gravity.CENTER_VERTICAL: {
                      int thumbPaddingTop = mThumbView.getPaddingTop();
                      thumbAnchorY = thumbPaddingTop + (mThumbHeight - thumbPaddingTop
                              - mThumbView.getPaddingBottom()) / 2;
                      break;
                  }
                  case Gravity.BOTTOM:
                      thumbAnchorY = mThumbHeight - mThumbView.getPaddingBottom();
                      break;
              }
              int popupTop = MathUtils.clamp(thumbTop + thumbAnchorY - popupAnchorY,
                      padding.top + popupLayoutParams.topMargin,
                      viewHeight - padding.bottom - popupLayoutParams.bottomMargin - popupHeight);
              layoutView(mPopupView, popupLeft, popupTop, popupLeft + popupWidth,
                      popupTop + popupHeight);
          }
      }
  
      private void updateScrollbarState() {
          int scrollOffsetRange = getScrollOffsetRange();
          mScrollbarEnabled = scrollOffsetRange > 0;
          mThumbOffset = mScrollbarEnabled ? (int) ((long) getThumbOffsetRange()
                  * mViewHelper.getScrollOffset() / scrollOffsetRange) : 0;
      }
  
      private void layoutView(@NonNull View view, int left, int top, int right, int bottom) {
          int scrollX = mView.getScrollX();
          int scrollY = mView.getScrollY();
          view.layout(scrollX + left, scrollY + top, scrollX + right, scrollY + bottom);
      }
  
      private void onScrollChanged() {
  
          updateScrollbarState();
          if (!mScrollbarEnabled) {
              return;
          }
  
          mAnimationHelper.showScrollbar(mTrackView, mThumbView);
          postAutoHideScrollbar();
      }
  
      private boolean onTouchEvent(@NonNull MotionEvent event) {
  
          if (!mScrollbarEnabled) {
              return false;
          }
  
          float eventX = event.getX();
          float eventY = event.getY();
          Rect padding = getPadding();
          switch (event.getAction()) {
              case MotionEvent.ACTION_DOWN:
  
                  mDownX = eventX;
                  mDownY = eventY;
  
                  if (mThumbView.getAlpha() > 0 && isInViewTouchTarget(mThumbView, eventX, eventY)) {
                      mDragStartY = eventY;
                      mDragStartThumbOffset = mThumbOffset;
                      setDragging(true);
                  }
                  break;
              case MotionEvent.ACTION_MOVE:
  
                  if (!mDragging && isInViewTouchTarget(mTrackView, mDownX, mDownY)
                          && Math.abs(eventY - mDownY) > mTouchSlop) {
                      if (isInViewTouchTarget(mThumbView, mDownX, mDownY)) {
                          mDragStartY = mLastY;
                          mDragStartThumbOffset = mThumbOffset;
                      } else {
                          mDragStartY = eventY;
                          mDragStartThumbOffset = (int) (eventY - padding.top - mThumbHeight / 2f);
                          scrollToThumbOffset(mDragStartThumbOffset);
                      }
                      setDragging(true);
                  }
  
                  if (mDragging) {
                      int thumbOffset = mDragStartThumbOffset + (int) (eventY - mDragStartY);
                      scrollToThumbOffset(thumbOffset);
                  }
                  break;
              case MotionEvent.ACTION_UP:
              case MotionEvent.ACTION_CANCEL:
  
                  setDragging(false);
                  break;
          }
  
          mLastY = eventY;
  
          return mDragging;
      }
  
      private boolean isInView(@NonNull View view, float x, float y) {
          int scrollX = mView.getScrollX();
          int scrollY = mView.getScrollY();
          return x >= view.getLeft() - scrollX && x < view.getRight() - scrollX
                  && y >= view.getTop() - scrollY && y < view.getBottom() - scrollY;
      }
  
      private boolean isInViewTouchTarget(@NonNull View view, float x, float y) {
          int scrollX = mView.getScrollX();
          int scrollY = mView.getScrollY();
          return isInTouchTarget(x, view.getLeft() - scrollX, view.getRight() - scrollX, 0,
                  mView.getWidth())
                  && isInTouchTarget(y, view.getTop() - scrollY, view.getBottom() - scrollY, 0,
                  mView.getHeight());
      }
  
      private boolean isInTouchTarget(float position, int viewStart, int viewEnd, int parentStart,
                                      int parentEnd) {
          int viewSize = viewEnd - viewStart;
          if (viewSize >= mMinTouchTargetSize) {
              return position >= viewStart && position < viewEnd;
          }
          int touchTargetStart = viewStart - (mMinTouchTargetSize - viewSize) / 2;
          if (touchTargetStart < parentStart) {
              touchTargetStart = parentStart;
          }
          int touchTargetEnd = touchTargetStart + mMinTouchTargetSize;
          if (touchTargetEnd > parentEnd) {
              touchTargetEnd = parentEnd;
              touchTargetStart = touchTargetEnd - mMinTouchTargetSize;
              if (touchTargetStart < parentStart) {
                  touchTargetStart = parentStart;
              }
          }
          return position >= touchTargetStart && position < touchTargetEnd;
      }
  
      private void scrollToThumbOffset(int thumbOffset) {
          int thumbOffsetRange = getThumbOffsetRange();
          thumbOffset = MathUtils.clamp(thumbOffset, 0, thumbOffsetRange);
          int scrollOffset = (int) ((long) getScrollOffsetRange() * thumbOffset / thumbOffsetRange);
          mViewHelper.scrollTo(scrollOffset);
      }
  
      private int getScrollOffsetRange() {
          return mViewHelper.getScrollRange() - mView.getHeight();
      }
  
      private int getThumbOffsetRange() {
          Rect padding = getPadding();
          return mView.getHeight() - padding.top - padding.bottom - mThumbHeight;
      }
  
      private void setDragging(boolean dragging) {
  
          if (mDragging == dragging) {
              return;
          }
          mDragging = dragging;
  
          if (mDragging) {
              mView.getParent().requestDisallowInterceptTouchEvent(true);
          }
  
          mTrackView.setPressed(mDragging);
          mThumbView.setPressed(mDragging);
  
          if (mDragging) {
              cancelAutoHideScrollbar();
              mAnimationHelper.showScrollbar(mTrackView, mThumbView);
              mAnimationHelper.showPopup(mPopupView);
          } else {
              postAutoHideScrollbar();
              mAnimationHelper.hidePopup(mPopupView);
          }
      }
  
      private void postAutoHideScrollbar() {
          cancelAutoHideScrollbar();
          if (mAnimationHelper.isScrollbarAutoHideEnabled()) {
              mView.postDelayed(mAutoHideScrollbarRunnable,
                      mAnimationHelper.getScrollbarAutoHideDelayMillis());
          }
      }
  
      private void autoHideScrollbar() {
          if (mDragging) {
              return;
          }
          mAnimationHelper.hideScrollbar(mTrackView, mThumbView);
      }
  
      private void cancelAutoHideScrollbar() {
          mView.removeCallbacks(mAutoHideScrollbarRunnable);
      }
  
      public interface ViewHelper {
  
          void addOnPreDrawListener(@NonNull Runnable onPreDraw);
  
          void addOnScrollChangedListener(@NonNull Runnable onScrollChanged);
  
          void addOnTouchEventListener(@NonNull Predicate<MotionEvent> onTouchEvent);
  
          int getScrollRange();
  
          int getScrollOffset();
  
          void scrollTo(int offset);
  
          @Nullable
          default CharSequence getPopupText() {
              return null;
          }
      }
  
      public interface AnimationHelper {
  
          void showScrollbar(@NonNull View trackView, @NonNull View thumbView);
  
          void hideScrollbar(@NonNull View trackView, @NonNull View thumbView);
  
          boolean isScrollbarAutoHideEnabled();
  
          int getScrollbarAutoHideDelayMillis();
  
          void showPopup(@NonNull View popupView);
  
          void hidePopup(@NonNull View popupView);
      }
  }
  