public class ExpandedRecyclerView extends RecyclerView {
    public static final int STEP_START = 1;
    public static final int STEP_LAYOUT = 2;
    public static final int STEP_ANIMATIONS = 4;

    public ExpandedRecyclerView(Context context) {
        super(context);
    }

    public ExpandedRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public ExpandedRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected int getAdapterPositionFor(RecyclerView.ViewHolder viewHolder) {
        return super.getAdapterPositionFor(viewHolder);
    }

    public static ViewHolder getChildViewHolderInt(View child) {
        return child == null ? null : (ViewHolder) ((RecyclerView.LayoutParams) child.getLayoutParams()).mViewHolder;
    }

    public int getLayoutStep() {
        return mState.mLayoutStep;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
        }

        public final boolean isAddedInPreLayout() {
            return (mFlags & FLAG_APPEARED_IN_PRE_LAYOUT) != 0;
        }

        public boolean isAttachedScrap() {
            return super.isScrap() && !mInChangeScrap;
        }

    }

} public class HeaderFooterWrapperAdapter<T extends RecyclerView.Adapter> extends OmegaRecyclerView.Adapter<RecyclerView.ViewHolder> implements StickyAdapter {

    // Defines available view type integers for headers and footers.
    private static final int BASE_HEADER_VIEW_TYPE = -1 << 10;
    private static final int BASE_FOOTER_VIEW_TYPE = -1 << 11;

    private final T mRealAdapter;
    private SparseArray<View> mHeaderArray = new SparseArray<>();
    private SparseArray<View> mHeaderArrayCopy = new SparseArray<>();
    private SparseArray<View> mFooterArray = new SparseArray<>();
    private SparseArray<View> mFooterArrayCopy = new SparseArray<>();

    private boolean mHeadersVisibility = true;
    private boolean mFootersVisibility = true;

    public HeaderFooterWrapperAdapter(T adapter) {
        mRealAdapter = adapter;
        super.setHasStableIds(mRealAdapter.hasStableIds());
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        mRealAdapter.onAttachedToRecyclerView(recyclerView);
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (isHeader(viewType)) {
            return new SectionViewHolder(mHeaderArray.get(viewType));
        }
        if (isFooter(viewType)) {
            return new SectionViewHolder(mFooterArray.get(viewType));
        }

        return mRealAdapter.onCreateViewHolder(parent, viewType);
    }


    @Override
    public void setHasStableIds(boolean hasStableIds) {
        super.setHasStableIds(hasStableIds);
        mRealAdapter.setHasStableIds(hasStableIds);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (!(isHeaderPosition(position) || isFooterPosition(position))) {
            mRealAdapter.onBindViewHolder(holder, position - mHeaderArray.size());
        } else {
            ((SectionViewHolder) holder).bind();
        }
    }

    @Override
    public int getItemCount() {
        return mHeaderArray.size() + mRealAdapter.getItemCount() + mFooterArray.size();
    }

    @Override
    public boolean isDividerAllowedAbove(int position) {
        SparseArray<View> sectionArray = null;
        if (isHeaderPosition(position)) sectionArray = mHeaderArray;
        if (isFooterPosition(position)) sectionArray = mFooterArray;

        if (sectionArray != null) {
            return true;
        }

        if (mRealAdapter instanceof OmegaRecyclerView.Adapter) {
            return ((OmegaRecyclerView.Adapter) mRealAdapter).isDividerAllowedAbove(position - mHeaderArray.size());
        }

        return super.isDividerAllowedAbove(position);
    }

    @Override
    public boolean isDividerAllowedBelow(int position) {
        SparseArray<View> sectionArray = null;
        if (isHeaderPosition(position)) sectionArray = mHeaderArray;
        if (isFooterPosition(position)) sectionArray = mFooterArray;

        if (sectionArray != null) {
            Object tag = sectionArray.get(getItemViewType(position)).getTag(R.id.section_show_divider);
            if (tag instanceof Boolean) {
                return (boolean) tag;
            }
            return super.isDividerAllowedBelow(position);
        }

        if (mRealAdapter instanceof OmegaRecyclerView.Adapter) {
            return ((OmegaRecyclerView.Adapter) mRealAdapter).isDividerAllowedBelow(position - mHeaderArray.size());
        }

        return super.isDividerAllowedBelow(position);
    }

    @Override
    public long getItemId(int position) {
        if (position < 0 || mRealAdapter.getItemCount() <= position || mRealAdapter.getItemCount() == 0) {
            return getItemViewType(position);
        }

        return mRealAdapter.getItemId(position);
    }

    @Override
    public int getItemViewType(int position) {
        if (isHeaderPosition(position)) {
            return mHeaderArray.keyAt(position);
        }
        if (isFooterPosition(position)) {
            return mFooterArray.keyAt(position - mRealAdapter.getItemCount() - mHeaderArray.size());
        }
        return mRealAdapter.getItemViewType(position - mHeaderArray.size());
    }

    public T getWrappedAdapter() {
        return mRealAdapter;
    }

    private boolean isHeader(int viewType) {
        return viewType >= BASE_HEADER_VIEW_TYPE && viewType < (BASE_HEADER_VIEW_TYPE + mHeaderArray.size());
    }

    private boolean isFooter(int viewType) {
        return viewType >= BASE_FOOTER_VIEW_TYPE && viewType < (BASE_FOOTER_VIEW_TYPE + mFooterArray.size());
    }

    private boolean isHeaderPosition(int position) {
        return position < mHeaderArray.size();
    }

    private boolean isFooterPosition(int position) {
        return position >= mRealAdapter.getItemCount() + mHeaderArray.size();
    }

    public void setHeaders(@NonNull List<View> list) {
        mHeaderArray = new SparseArray<>();
        mHeaderArrayCopy = new SparseArray<>();
        for (View view : list) {
            int key = BASE_HEADER_VIEW_TYPE + list.indexOf(view);
            mHeaderArray.append(key, view);
            mHeaderArrayCopy.append(key, view);
        }
        notifyDataSetChanged();
    }

    public void setFooters(@NonNull List<View> list) {
        mFooterArray = new SparseArray<>();
        mFooterArrayCopy = new SparseArray<>();
        for (View view : list) {
            int key = BASE_FOOTER_VIEW_TYPE + list.indexOf(view);
            mFooterArray.append(key, view);
            mFooterArrayCopy.append(key, view);
        }
        notifyDataSetChanged();
    }

    public void setHeadersVisible(boolean visible) {
        if (mHeadersVisibility != visible) {
            if (visible) {
                copyTo(mHeaderArrayCopy, mHeaderArray);
            } else {
                mHeaderArray.clear();
            }
            mHeadersVisibility = visible;
            notifyDataSetChanged();
        }
    }

    public boolean isHeadersVisible() {
        return mHeadersVisibility;
    }

    public void setFootersVisible(boolean visible) {
        if (mFootersVisibility != visible) {
            if (visible) {
                copyTo(mFooterArrayCopy, mFooterArray);
            } else {
                mFooterArray.clear();
            }
            mFootersVisibility = visible;
            notifyDataSetChanged();
        }
    }

    public boolean isFootersVisible() {
        return mFootersVisibility;
    }

    private <V> void copyTo(SparseArray<V> fromArray, SparseArray<V> toArray) {
        toArray.clear();

        for (int i = 0; i < fromArray.size(); i++) {
            toArray.append(fromArray.keyAt(i), fromArray.valueAt(i));
        }
    }

    @Override
    public long getStickyId(int position) {
        if (position < 0 || mRealAdapter.getItemCount() <= position) {
            return HeaderStickyDecoration.NO_STICKY_ID;
        }
        StickyAdapter stickyAdapter = getStickyAdapter();
        if (stickyAdapter == null) {
            return HeaderStickyDecoration.NO_STICKY_ID;
        }

        int realAdapterItemCount = mRealAdapter.getItemCount();
        if (realAdapterItemCount == 0) {
            return HeaderStickyDecoration.NO_STICKY_ID;
        }
        if (isHeaderPosition(position)) {
            return stickyAdapter.getStickyId(0);
        }
        if (isFooterPosition(position)) {
            return stickyAdapter.getStickyId(realAdapterItemCount - 1);
        }

        return stickyAdapter.getStickyId(position);
    }

    public int applyRealPositionToChildPosition(int realPosition) {
        return realPosition - mHeaderArray.size();
    }

    public int applyChildPositionToRealPosition(int childPosition) {
        return childPosition + mHeaderArray.size();
    }

    @Nullable
    public StickyAdapter getStickyAdapter() {
        if (mRealAdapter instanceof StickyAdapter) {
            return (StickyAdapter) mRealAdapter;
        }
        return null;
    }

    @Override
    public RecyclerView.ViewHolder onCreateStickyViewHolder(ViewGroup parent) {
        StickyAdapter stickyAdapter = getStickyAdapter();
        assert stickyAdapter != null;
        return stickyAdapter.onCreateStickyViewHolder(parent);
    }

    @Override
    public void onBindStickyViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        StickyAdapter stickyAdapter = getStickyAdapter();
        assert stickyAdapter != null;
        //noinspection unchecked
        stickyAdapter.onBindStickyViewHolder(viewHolder, position);
    }

    @Override
    public void onClickStickyViewHolder(long id) {
        StickyAdapter stickyAdapter = getStickyAdapter();
        if (stickyAdapter != null) stickyAdapter.onClickStickyViewHolder(id);
    }

    @Override
    protected void tryNotifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
        super.tryNotifyItemRangeChanged(positionStart + mHeaderArray.size(), itemCount, payload);
    }

    @Override
    protected void tryNotifyItemRangeInserted(int positionStart, int itemCount) {
        super.tryNotifyItemRangeInserted(positionStart + mHeaderArray.size(), itemCount);
    }

    @Override
    protected void tryNotifyItemRemoved(int position) {
        super.tryNotifyItemRemoved(position + mHeaderArray.size());
    }

    @Override
    protected void tryNotifyItemMoved(int fromPosition, int toPosition) {
        super.tryNotifyItemMoved(fromPosition + mHeaderArray.size(), toPosition + mHeaderArray.size());
    }

    @Override
    protected void tryNotifyItemRangeRemoved(int positionStart, int itemCount) {
        super.tryNotifyItemRangeRemoved(positionStart + mHeaderArray.size(), itemCount);
    }

    private static class SectionViewHolder extends RecyclerView.ViewHolder {

        private View contentView;

        private SectionViewHolder(View itemView) {
            super(new SectionContentFrameLayout(itemView.getContext()));
            contentView = itemView;
        }

        private void bind() {
            if (contentView.getParent() instanceof ViewGroup) {
                ((ViewGroup) contentView.getParent()).removeView(contentView);
            }
            itemView.setLayoutParams(new ViewGroup.LayoutParams(contentView.getLayoutParams()));
            ((ViewGroup) itemView).addView(contentView);
        }

    }
} public class DividerItemDecoration extends BaseItemDecoration {

    private final Rect mViewRect = new Rect();
    private final Rect mItemRect = new Rect();
    private final int mOriginalDividerSize;
    private final int mOffset;
    private float mDividerAlpha;
    private Drawable mDivider;
    private int mDividerSize;
    private int mPaddingStart;
    private int mPaddingEnd;


    public DividerItemDecoration(Drawable divider, int dividerSize, int showDivider, int offset, float dividerAlpha) {
        super(showDivider);
        mOriginalDividerSize = dividerSize;
        mDivider = divider;
        mDividerSize = dividerSize;
        mOffset = offset;
        mDividerAlpha = dividerAlpha;
        updateSize();
    }

    @Override
    void onOrientationUpdated(int orientation) {
        updateSize();
    }

    public void setPadding(int padding) {
        mPaddingStart = padding;
        mPaddingEnd = padding;
    }

    public void setPaddingStart(int padding) {
        mPaddingStart = padding;
    }

    public void setPaddingEnd(int padding) {
        mPaddingEnd = padding;
    }

    public void setDividerAlpha(float dividerAlpha) {
        this.mDividerAlpha = dividerAlpha;
    }

    public void setDividerDrawable(@NonNull Drawable dividerDrawable) {
        mDivider = dividerDrawable;
        if (mOriginalDividerSize < 0) {
            mDividerSize = mOriginalDividerSize;
            updateSize();
        }
    }

    private void updateSize() {
        if (mDividerSize < 0) {
            switch (getOrientation()) {
                case Orientation.HORIZONTAL:
                    mDividerSize = mDivider.getIntrinsicWidth();
                    break;
                case Orientation.VERTICAL:
                    mDividerSize = mDivider.getIntrinsicHeight();
                    break;
            }
        }
    }

    @Override
    public void getItemOffset(@NonNull Rect outRect, @NonNull RecyclerView parent, @NonNull DividerDecorationHelper helper, int position, int itemCount) {
        if (position == 0 && isShowBeginDivider()) {
            helper.setStart(outRect, mDividerSize);
        }
        if (position != 0 && isShowMiddleDivider()) {
            helper.setStart(outRect, mDividerSize);
        }
        if (position == itemCount - 1 && isShowEndDivider()) {
            helper.setEnd(outRect, mDividerSize);
        }
    }


    @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        if (mDivider == null || !(parent.getLayoutManager() instanceof LinearLayoutManager)) {
            super.onDrawOver(c, parent, state);
            return;
        }

        final int childCount = parent.getChildCount();

        if (childCount > 0) {
            c.save();
            DividerDecorationHelper helper = DividerDecorationHelper.getHelper(getOrientation(), parent);

            if (parent.getClipToPadding()) {
                mViewRect.set(parent.getPaddingLeft(),
                        parent.getPaddingTop(),
                        parent.getWidth() - parent.getPaddingRight(),
                        parent.getHeight() - parent.getPaddingBottom());

                c.clipRect(mViewRect);

                mItemRect.set(parent.getPaddingLeft() + mPaddingStart,
                        parent.getPaddingTop() + mPaddingStart,
                        parent.getWidth() - parent.getPaddingRight() - mPaddingEnd,
                        parent.getHeight() - parent.getPaddingBottom() - mPaddingEnd);

                helper.setStart(mItemRect, helper.getStart(mItemRect) - mPaddingStart);
                helper.setEnd(mItemRect, helper.getEnd(mItemRect) + mPaddingEnd);
            } else {
                mItemRect.set(mPaddingStart, mPaddingStart,
                        parent.getWidth() - mPaddingEnd, parent.getHeight() - mPaddingEnd);
            }
            View child;

            int offsetIndex = 0;

            // show beginning divider
            if (isShowBeginDivider()) {
                child = getFirstChild(parent);
                int adapterPosition = getAdapterPosition(parent, child);
                if (adapterPosition == 0 && isShowDividerAbove(parent, 0)) {
                    offsetIndex = 1;
                    updateViewRect(parent, child);
                    helper.setStart(mItemRect, helper.getStart(mViewRect) - helper.getOffset(mOffset));
                    helper.setEnd(mItemRect, helper.getStart(mItemRect) - helper.getOffset(mDividerSize));
                    drawDivider(c, child, mItemRect);
                }
            }

            // show middle dividers
            if (isShowMiddleDivider()) {
                for (int i = offsetIndex; i < childCount; i++) {
                    child = parent.getChildAt(i);
                    int adapterPosition = getAdapterPosition(parent, child);
                    if (adapterPosition > 0 && isShowDividerAbove(parent, adapterPosition)) {
                        updateViewRect(parent, child);
                        helper.setStart(mItemRect, helper.getStart(mViewRect) - helper.getOffset(mOffset));
                        helper.setEnd(mItemRect, helper.getStart(mItemRect) - helper.getOffset(mDividerSize));
                        drawDivider(c, child, mItemRect);
                    }
                }
            }

            // show end divider
            if (isShowEndDivider()) {
                child = parent.getChildAt(childCount - 1);
                int adapterPosition = getAdapterPosition(parent, child);
                int adapterCount = parent.getAdapter().getItemCount();
                if (adapterPosition == adapterCount - 1 && isShowDividerBelow(parent, adapterPosition, adapterCount)) {
                    updateViewRect(parent, child);
                    helper.setStart(mItemRect, helper.getEnd(mViewRect) + helper.getOffset(mOffset));
                    helper.setEnd(mItemRect, helper.getStart(mItemRect) + helper.getOffset(mDividerSize));
                    drawDivider(c, child, mItemRect);
                }
            }

            c.restore();
        }
    }

    // Bug with Expandable adapter and hide animation.
    // RecyclerView getChild - returns wrong child (ChildClippingFrameLayout).
    private View getFirstChild(RecyclerView parent) {
        int count = parent.getChildCount();
        View topView = null;
        for (int i = 0; i < count; i++) {
            View view = parent.getChildAt(i);
            if (topView == null) {
                topView = view;
            } else if (topView.getY() > view.getY()) {
                topView = view;
            }
        }
        return topView;
    }

    private void updateViewRect(RecyclerView parent, View view) {
        view.getHitRect(mViewRect);
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        mViewRect.left -= params.leftMargin;
        mViewRect.top -= params.topMargin;
        mViewRect.right += params.rightMargin;
        mViewRect.bottom += params.bottomMargin;
    }

    private void drawDivider(Canvas canvas, View view, Rect rect) {
        Drawable divider = this.mDivider;
        divider.setAlpha((int) (view.getAlpha() * 255f * mDividerAlpha));
        rect.sort();
        divider.setBounds(rect);
        divider.draw(canvas);
    }

    private boolean isShowDividerBelow(RecyclerView parent, int adapterPosition, int count) {
        RecyclerView.Adapter adapter = parent.getAdapter();
        if (adapter instanceof OmegaRecyclerView.Adapter) {
            boolean allowedBelow = ((OmegaRecyclerView.Adapter) adapter).isDividerAllowedBelow(adapterPosition);
            if (allowedBelow) {
                return (adapterPosition + 1 == count)
                        || ((OmegaRecyclerView.Adapter) adapter).isDividerAllowedAbove(adapterPosition + 1);

            } else {
                return false;
            }
        }
        return true;
    }

    private boolean isShowDividerAbove(RecyclerView parent, int adapterPosition) {
        RecyclerView.Adapter adapter = parent.getAdapter();
        if (adapter instanceof OmegaRecyclerView.Adapter) {
            OmegaRecyclerView.Adapter omegaAdapter = (OmegaRecyclerView.Adapter) adapter;
            boolean allowedBelow = adapterPosition == 0 || omegaAdapter.isDividerAllowedBelow(adapterPosition - 1);
            if (allowedBelow) {
                return omegaAdapter.isDividerAllowedAbove(adapterPosition);
            } else {
                return false;
            }
        }
        return true;
    }

} public class BaseSpaceItemDecoration extends BaseItemDecoration {

    private final int mItemSpace;

    public BaseSpaceItemDecoration(int showDivider, int space) {
        super(showDivider);
        mItemSpace = space;
    }

    @Override
    void getItemOffset(@NonNull Rect outRect, @NonNull RecyclerView parent,
                       @NonNull DividerDecorationHelper helper, int position, int itemCount) {
        int countBeginEndPositions = getCountBeginEndPositions(parent);
        int itemSpace = getItemSpace(parent, position, itemCount);

        if (isShowBeginDivider() || countBeginEndPositions <= position) helper.setStart(outRect, itemSpace);
        if (isShowEndDivider() && position == itemCount - countBeginEndPositions) helper.setEnd(outRect, itemSpace);

        if (countBeginEndPositions > 1) {
            if (position % countBeginEndPositions != 0 || isShowBeginDivider()) helper.setOtherStart(outRect, itemSpace);
            if (position / (countBeginEndPositions - 1) > 0 && isShowEndDivider()) helper.setOtherEnd(outRect, itemSpace);
        }
    }

    protected int getItemSpace(@NonNull RecyclerView parent, int position, int itemCount) {
        return mItemSpace;
    }

    private int getCountBeginEndPositions(RecyclerView recyclerView) {
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            return ((GridLayoutManager) layoutManager).getSpanCount();
        } else return 1;
    }

} public interface OnPageRequestListener {

    void onPageRequest(int page);

    int getPagePreventionForEnd();

} public class PageRequester extends RecyclerView.ItemDecoration {

    @Nullable
    private OnPageRequestListener mCallback;
    private int mLastItemCount = -1;
    private boolean mEnabled;

    private int mCurrentPage;

    public void attach(RecyclerView omegaRecyclerView) {
        omegaRecyclerView.addItemDecoration(this);
    }

    public void setPaginationCallback(@Nullable OnPageRequestListener callback) {
        mCallback = callback;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView recyclerView, RecyclerView.State state) {
        if (mCallback == null || !mEnabled) return;

        int adapterPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition();
        int itemCount = recyclerView.getAdapter().getItemCount();
        // RecyclerView.Adapter getItemCount gives count of values + 1 (PaginationViewHolder)
        int preventionPosition = itemCount - mCallback.getPagePreventionForEnd() - 1;
        if (adapterPosition >= preventionPosition && itemCount > mLastItemCount) {
            mCurrentPage++;
            recyclerView.post(new Runnable() {
                @Override
                public void run() {
                    mCallback.onPageRequest(mCurrentPage);
                }
            });
            mLastItemCount = itemCount;
        }
    }

    public void reset() {
        mCurrentPage = 0;
        mLastItemCount = -1;
    }

    @Nullable
    public OnPageRequestListener getCallback() {
        return mCallback;
    }

    public void setEnabled(boolean enabled) {
        if (mEnabled != enabled) {
            mEnabled = enabled;
            if (enabled) {
                restart();
            }
        }
    }

    private void restart() {
        if (mCallback != null) {
            mCallback.onPageRequest(mCurrentPage);
        }
    }

} public class PaginationAdapter extends WrapperAdapter {

    private static final int VIEW_TYPE_PAGINATION_PROGRESS = -1;
    private static final int VIEW_TYPE_PAGINATION_ERROR = -2;

    @LayoutRes
    private int mPaginationLayout;
    @LayoutRes
    private int mPaginationErrorLayout;

    private PaginationViewState mShowPagination;

    public PaginationAdapter(RecyclerView.Adapter adapter,
                             @LayoutRes int paginationLayout,
                             @LayoutRes int paginationErrorLayout) {
        super(adapter);
        mPaginationLayout = paginationLayout;
        mPaginationErrorLayout = paginationErrorLayout;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        switch (viewType) {
            case VIEW_TYPE_PAGINATION_PROGRESS:
                return createPaginationViewHolder(parent, inflater);
            case VIEW_TYPE_PAGINATION_ERROR:
                return createPaginationErrorViewHolder(parent, inflater);
            default:
                return super.onCreateViewHolder(parent, viewType);
        }
    }

    private OmegaRecyclerView.ViewHolder createPaginationViewHolder(ViewGroup parent, LayoutInflater inflater) {
        View view = null;
        if (mChildAdapter instanceof PaginationViewCreator) {
            view = ((PaginationViewCreator) mChildAdapter).createPaginationView(parent, inflater);
        }
        if (view == null) {
            view = inflater.inflate(mPaginationLayout, parent, false);
        }
        return new OmegaRecyclerView.ViewHolder(view);
    }

    private OmegaRecyclerView.ViewHolder createPaginationErrorViewHolder(ViewGroup parent, LayoutInflater inflater) {
        View view = null;
        if (mChildAdapter instanceof PaginationViewCreator) {
            view = ((PaginationViewCreator) mChildAdapter).createPaginationErrorView(parent, inflater);
        }
        if (view == null) {
            view = inflater.inflate(mPaginationErrorLayout, parent, false);
        }
        return new OmegaRecyclerView.ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position < super.getItemCount()) {
            super.onBindViewHolder(holder, position);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
        if (position < super.getItemCount()) {
            super.onBindViewHolder(holder, position, payloads);
        }
    }

    @Override
    public int getItemCount() {
        return mShowPagination != null ? super.getItemCount() + 1 : super.getItemCount();
    }

    @Override
    public long getItemId(int position) {
        if (position == super.getItemCount()) {
            return RecyclerView.NO_ID;
        }
        return super.getItemId(position);
    }

    @Override
    public boolean isDividerAllowedAbove(int position) {
        if (position == super.getItemCount()) {
            return position != 0;
        }
        return super.isDividerAllowedAbove(position);
    }

    @Override
    public boolean isDividerAllowedBelow(int position) {
        if (position == super.getItemCount()) {
            return false;
        }
        return super.isDividerAllowedBelow(position);
    }

    @Override
    public int getItemViewType(int position) {
        if (mShowPagination != null && position == super.getItemCount()) {
            switch (mShowPagination) {
                case ERROR:
                    return VIEW_TYPE_PAGINATION_ERROR;
                case PROGRESS:
                    return VIEW_TYPE_PAGINATION_PROGRESS;
            }
        }
        return super.getItemViewType(position);
    }

    public void showProgressPagination() {
        if (mShowPagination != PaginationViewState.PROGRESS) {
            mShowPagination = PaginationViewState.PROGRESS;
            mChildAdapter.notifyDataSetChanged();
        }
    }

    public void showErrorPagination() {
        if (mShowPagination != PaginationViewState.ERROR) {
            mShowPagination = PaginationViewState.ERROR;
            mChildAdapter.notifyDataSetChanged();
        }
    }

    public void hidePagination() {
        if (mShowPagination != null) {
            mShowPagination = null;
            mChildAdapter.notifyDataSetChanged();
        }
    }

    enum PaginationViewState {
        PROGRESS,
        ERROR
    }

} @SuppressWarnings("unchecked")
public class WrapperAdapter<T extends RecyclerView.ViewHolder> extends OmegaRecyclerView.Adapter<RecyclerView.ViewHolder> {

    protected RecyclerView.Adapter mChildAdapter;

    public WrapperAdapter(RecyclerView.Adapter adapter) {
        super.setHasStableIds(adapter.hasStableIds());
        this.mChildAdapter = adapter;
        adapter.registerAdapterDataObserver(new ForwardingDataSetObserver());
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return mChildAdapter.onCreateViewHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        mChildAdapter.onBindViewHolder(holder, position);
    }

    @Override
    public int getItemCount() {
        return mChildAdapter.getItemCount();
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) {
        mChildAdapter.onBindViewHolder(holder, position, payloads);
    }

    public RecyclerView.Adapter getWrappedAdapter() {
        return mChildAdapter;
    }

    public RecyclerView.Adapter getLastWrappedAdapter() {
        RecyclerView.Adapter childAdapter = mChildAdapter;
        while (childAdapter instanceof WrapperAdapter) {
            childAdapter = ((WrapperAdapter) childAdapter).mChildAdapter;
        }
        return childAdapter;
    }

    @Override
    public int getItemViewType(int position) {
        return mChildAdapter.getItemViewType(position);
    }

    @Override
    public void setHasStableIds(boolean hasStableIds) {
        mChildAdapter.setHasStableIds(hasStableIds);
    }

    @Override
    public long getItemId(int position) {
        return mChildAdapter.getItemId(position);
    }

    @Override
    public void onViewRecycled(RecyclerView.ViewHolder holder) {
        mChildAdapter.onViewRecycled(holder);
    }

    @Override
    public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
        return mChildAdapter.onFailedToRecycleView(holder);
    }

    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        mChildAdapter.onViewAttachedToWindow(holder);
    }

    @Override
    public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
        mChildAdapter.onViewDetachedFromWindow(holder);
    }

    @Override
    public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
        mChildAdapter.registerAdapterDataObserver(observer);
    }

    @Override
    public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
        mChildAdapter.unregisterAdapterDataObserver(observer);
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        mChildAdapter.onAttachedToRecyclerView(recyclerView);
    }

    @Override
    public boolean isDividerAllowedAbove(int position) {
        if (mChildAdapter instanceof OmegaRecyclerView.Adapter) {
            return ((OmegaRecyclerView.Adapter) mChildAdapter).isDividerAllowedAbove(position);
        }
        return super.isDividerAllowedBelow(position);
    }

    @Override
    public boolean isDividerAllowedBelow(int position) {
        if (mChildAdapter instanceof OmegaRecyclerView.Adapter) {
            return ((OmegaRecyclerView.Adapter) mChildAdapter).isDividerAllowedBelow(position);
        }
        return super.isDividerAllowedBelow(position);
    }

    @Override
    public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
        mChildAdapter.onDetachedFromRecyclerView(recyclerView);
    }

    private class ForwardingDataSetObserver extends RecyclerView.AdapterDataObserver {
        @Override
        public void onChanged() {
            tryNotifyDataSetChanged();
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            notifyItemRangeChanged(positionStart, itemCount);
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            tryNotifyItemRangeChanged(positionStart, itemCount, payload);
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            tryNotifyItemRangeInserted(positionStart, itemCount);
        }

        @Override
        public void onItemRangeRemoved(int position, int itemCount) {
            tryNotifyItemRangeRemoved(position, itemCount);
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            tryNotifyDataSetChanged();
        }
    }



} @SuppressWarnings("rawtypes")
public abstract class BaseStickyDecoration extends RecyclerView.ItemDecoration {

    public static final long NO_STICKY_ID = -1L;

    @Nullable
    protected StickyAdapter mStickyAdapter;
    protected int mItemSpace;

    public BaseStickyDecoration(@Nullable StickyAdapter adapter) {
        mStickyAdapter = adapter;
    }

    public boolean onTouchEvent(@NonNull RecyclerView parent, @NonNull MotionEvent ev, boolean defaultResult) {
        return defaultResult;
    }

    @CallSuper
    public final void setStickyAdapter(@Nullable StickyAdapter adapter) {
        mStickyAdapter = adapter;
    }

    public final void setItemSpace(int itemSpace) {
        mItemSpace = itemSpace;
    }

    protected final boolean hasSticker(int position) {
        return mStickyAdapter != null && mStickyAdapter.getStickyId(position) != NO_STICKY_ID;
    }

} @SuppressWarnings("rawtypes")
public class HeaderStickyDecoration extends StickyDecoration {

    public HeaderStickyDecoration(StickyAdapter adapter) {
        super(adapter);
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
                               @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        RecyclerView.Adapter adapter = parent.getAdapter();
        if (adapter == null) return;

        int position = parent.getChildAdapterPosition(view);
        int topOffset = 0;
        if (position != RecyclerView.NO_POSITION && hasSticker(position)
                && showHeaderAboveItem(parent, position)) {

            RecyclerView.ViewHolder stickyHolder = getStickyHolder(parent, position);
            if(stickyHolder != null) {
                View header = stickyHolder.itemView;
                topOffset = header.getHeight();
                if (isReverseLayout(parent)) {
                    if (position != adapter.getItemCount() - 1) topOffset -= mItemSpace;
                } else {
                    if (position != 0) {
                        if (mItemSpace > topOffset) {
                            topOffset = 0;
                        } else {
                            topOffset -= mItemSpace;
                        }
                    }
                }
            }
        }
        outRect.set(0, topOffset, 0, 0);
    }

    private boolean showHeaderAboveItem(@NonNull RecyclerView parent, int position) {
        if (mStickyAdapter == null) return false;
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager == null) return false;

        if (isReverseLayout(parent)) {
            int itemCount = layoutManager.getItemCount();
            return position == (itemCount - 1) || mStickyAdapter.getStickyId(position + 1)
                    != mStickyAdapter.getStickyId(position);
        } else {
            return position == 0 || mStickyAdapter.getStickyId(position - 1)
                    != mStickyAdapter.getStickyId(position);
        }
    }

    @Override
    int getOffset(@Nullable RecyclerView.ViewHolder stickyHolder, int stickerHeight, View next) {
        if (stickyHolder == null) return 0;
        return ((int) next.getY()) - (stickerHeight + stickyHolder.itemView.getHeight());
    }

    @Override
    protected int getStickerTop(boolean isReverseLayout, View child, View sticker, int layoutPos) {
        int stickerHeight = sticker.getHeight();

        int topMargin = 0;
        ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
        if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
            topMargin = ((ViewGroup.MarginLayoutParams) layoutParams).topMargin;
        }

        if (layoutPos == 0) stickerHeight = Math.max(stickerHeight, mItemSpace);
        return (int) Math.max(0, child.getY() - stickerHeight  - topMargin);
    }

} public class MiddleStickyDecoration extends StickyDecoration {

    public MiddleStickyDecoration(@NonNull StickyAdapter adapter) {
        super(adapter);
    }

    @Override
    protected int getStickerTop(boolean isReverseLayout, View child, View sticker, int layoutPos) {
        int top = (int) child.getY();
        if (!isReverseLayout && layoutPos == 0 && top > 0) {
            return Math.max(0, top - mItemSpace);
        }
        return Math.max(0, top);
    }

    @Override
    int getOffset(@Nullable RecyclerView.ViewHolder stickyHolder, int stickerHeight, View next) {
        return (int) (next.getY() - stickerHeight);
    }

} public interface StickyAdapter<T extends RecyclerView.ViewHolder> {

    long getStickyId(int position);

    T onCreateStickyViewHolder(ViewGroup parent);

    void onBindStickyViewHolder(T viewHolder, int position);

    void onClickStickyViewHolder(long id);

    interface Mode {

        int HEADER = 0;
        int MIDDLE = 1;

    }

} public class SwipeMenuHelper {

    private static final int INVALID_POSITION = -1;

    private final Callback mCallback;
    @Nullable
    private SwipeHorizontalMenuLayout mOldSwipedView;
    private int mOldTouchedPosition = INVALID_POSITION;

    public SwipeMenuHelper(Callback callback) {
        mCallback = callback;
    }

    public boolean handleTouchEventForSecurity(MotionEvent ev, boolean defaultResult) {
        if (mOldSwipedView == null ||
                ev.getActionIndex() == 0 ||
                ev.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN) return defaultResult;

        int pointerId = ev.getPointerId(ev.getActionIndex());
        SwipeHorizontalMenuLayout touchingView = getTouchingView(ev.getX(pointerId), ev.getY(pointerId));
        return mOldSwipedView.equals(touchingView) && defaultResult;
    }

    public boolean handleInterceptTouchEvent(MotionEvent ev, boolean defaultResult) {
        if (ev.getActionIndex() != 0 || ev.getAction() != MotionEvent.ACTION_DOWN) return defaultResult;

        boolean isIntercepted = defaultResult;
        View touchingView = findChildViewUnder((int) ev.getX(), (int) ev.getY());
        int touchingPosition;

        if (touchingView != null) {
            touchingPosition = mCallback.getPositionForView(touchingView);
        } else {
            touchingPosition = INVALID_POSITION;
        }

        if (touchingPosition != mOldTouchedPosition && mOldSwipedView != null) {
            if (mOldSwipedView.isMenuOpen() || mOldSwipedView.isNotInPlace()) {
                mOldSwipedView.smoothCloseMenu();
                isIntercepted = true;
            }
        }

        touchingView = extractSwipeMenuView(mCallback.transformTouchView(touchingPosition, touchingView));
        if (touchingView != null) {
            mOldSwipedView = (SwipeHorizontalMenuLayout) touchingView;
            mOldTouchedPosition = touchingPosition;
        }
        if (isIntercepted) {
            mOldSwipedView = null;
            mOldTouchedPosition = INVALID_POSITION;
        }

        return isIntercepted;
    }

    private SwipeHorizontalMenuLayout getTouchingView(float eventX, float eventY) {
        View touchingView = findChildViewUnder(eventX, eventY);
        if (touchingView != null) {
            int touchingPosition = mCallback.getPositionForView(touchingView);
            touchingView = mCallback.transformTouchView(touchingPosition, touchingView);
        }
        return extractSwipeMenuView(touchingView);
    }

    @Nullable
    private SwipeHorizontalMenuLayout extractSwipeMenuView(@Nullable View view) {
        if (view instanceof SwipeHorizontalMenuLayout) return (SwipeHorizontalMenuLayout) view;

        List<View> unvisited = new ArrayList<>();
        if (view != null) unvisited.add(view);

        while (!unvisited.isEmpty()) {
            View child = unvisited.remove(0);

            if (child instanceof SwipeHorizontalMenuLayout) return (SwipeHorizontalMenuLayout) child;
            if (!(child instanceof ViewGroup)) continue;

            ViewGroup group = (ViewGroup) child;
            int childCount = group.getChildCount();

            for (int i = 0; i < childCount; i++) {
                unvisited.add(group.getChildAt(i));
            }
        }

        return null;
    }

    private View findChildViewUnder(float x, float y) {
        int count = mCallback.getRealChildCount();

        for (int i = count - 1; i >= 0; i--) {
            View child = mCallback.getRealChildAt(i);
            float translationX = child.getTranslationX();
            float translationY = child.getTranslationY();

            if (x >= child.getLeft() + translationX &&
                    x <= child.getRight() + translationX &&
                    y >= child.getTop() + translationY &&
                    y <= child.getBottom() + translationY) {

                return child;
            }
        }

        return null;
    }

    public interface Callback {

        int getPositionForView(View view);

        int getRealChildCount();

        View getRealChildAt(int index);

        View transformTouchView(int touchPosition, View touchView);

    }
}
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.ExpandedRecyclerView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.omega_r.libs.omegarecyclerview.header.HeaderFooterWrapperAdapter;
import com.omega_r.libs.omegarecyclerview.item_decoration.DividerItemDecoration;
import com.omega_r.libs.omegarecyclerview.item_decoration.BaseSpaceItemDecoration;
import com.omega_r.libs.omegarecyclerview.pagination.OnPageRequestListener;
import com.omega_r.libs.omegarecyclerview.pagination.PageRequester;
import com.omega_r.libs.omegarecyclerview.pagination.PaginationAdapter;
import com.omega_r.libs.omegarecyclerview.pagination.WrapperAdapter;
import com.omega_r.libs.omegarecyclerview.sticky_decoration.BaseStickyDecoration;
import com.omega_r.libs.omegarecyclerview.sticky_decoration.HeaderStickyDecoration;
import com.omega_r.libs.omegarecyclerview.sticky_decoration.MiddleStickyDecoration;
import com.omega_r.libs.omegarecyclerview.sticky_decoration.StickyAdapter;
import com.omega_r.libs.omegarecyclerview.swipe_menu.SwipeMenuHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;

                addItemDecoration(mDividerItemDecoration);
            }
        }
    }

    private void initPagination(TypedArray a) {
        if (a.hasValue(R.styleable.OmegaRecyclerView_paginationLayout)) {
            mPaginationLayout = a.getResourceId(R.styleable.OmegaRecyclerView_paginationLayout, R.layout.pagination_omega_layout);
        }
        if (a.hasValue(R.styleable.OmegaRecyclerView_paginationErrorLayout)) {
            mPaginationErrorLayout = a.getResourceId(R.styleable.OmegaRecyclerView_paginationErrorLayout, R.layout.pagination_error_omega_layout);
        }
    }

    private void initStickyMode(TypedArray a) {
        mStickyMode = a.getInt(R.styleable.OmegaRecyclerView_stickyMode, mStickyMode);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void setAdapter(RecyclerView.Adapter adapter) {
        mIsAdapterConnected = true;
        unregisterObservers();

        if (adapter == null) {
            super.setAdapter(null);
            updateStickyDecoration(null);
            return;
        }

        if (adapter instanceof OnPageRequestListener) {
            setPaginationCallback((OnPageRequestListener) adapter);
        }

        RecyclerView.Adapter shellAdapter = adapter;
        if (!(adapter instanceof HeaderFooterWrapperAdapter)) {
            if (mPageRequester.getCallback() != null && !(adapter instanceof PaginationAdapter)) {
                shellAdapter = new PaginationAdapter(adapter, mPaginationLayout, mPaginationErrorLayout);
            }
            if (!mHeadersList.isEmpty() || !mFooterList.isEmpty()) {
                shellAdapter = new HeaderFooterWrapperAdapter(shellAdapter);
                ((HeaderFooterWrapperAdapter) shellAdapter).setHeaders(mHeadersList);
                ((HeaderFooterWrapperAdapter) shellAdapter).setFooters(mFooterList);
            }
        }
        super.setAdapter(shellAdapter);
        mPageRequester.reset();

        registerObservers(shellAdapter);
        updateStickyDecoration(shellAdapter);
    }

    @Nullable
    public RecyclerView.Adapter getRealAdapter() {
        return getRealAdapter(getAdapter());
    }

    private RecyclerView.Adapter getRealAdapter(RecyclerView.Adapter adapter) {
        if (adapter instanceof HeaderFooterWrapperAdapter) {
            return getRealAdapter(((HeaderFooterWrapperAdapter) adapter).getWrappedAdapter());
        } else if (adapter instanceof WrapperAdapter) {
            return getRealAdapter(((WrapperAdapter) adapter).getLastWrappedAdapter());
        }
        return adapter;
    }

    private void unregisterObservers() {
        RecyclerView.Adapter currentAdapter = super.getAdapter();
        if (currentAdapter != null) {
            currentAdapter.unregisterAdapterDataObserver(mEmptyObserver);
            if (currentAdapter instanceof HeaderFooterWrapperAdapter) {
                ((HeaderFooterWrapperAdapter) currentAdapter).getWrappedAdapter().unregisterAdapterDataObserver(mHeaderObserver);
            }
        }
        mEmptyObserver.onChanged();
        mHeaderObserver.onChanged();
    }

    private void registerObservers(RecyclerView.Adapter adapter) {
        if (adapter instanceof HeaderFooterWrapperAdapter) {
            ((HeaderFooterWrapperAdapter) adapter).getWrappedAdapter().registerAdapterDataObserver(mHeaderObserver);
        }
        adapter.registerAdapterDataObserver(mEmptyObserver);
    }

    protected void updateStickyDecoration(@Nullable RecyclerView.Adapter adapter) {
        if (adapter == null) {
            if (mBaseStickyDecoration != null) removeItemDecoration(mBaseStickyDecoration);
        } else {
            StickyAdapter stickyAdapter = null;
            if (adapter instanceof WrapperAdapter) {
                RecyclerView.Adapter wrappedAdapter = ((WrapperAdapter) adapter).getLastWrappedAdapter();
                if (wrappedAdapter instanceof StickyAdapter) {
                    stickyAdapter = (StickyAdapter) wrappedAdapter;
                }
            } else if (adapter instanceof HeaderFooterWrapperAdapter) {
                if (((HeaderFooterWrapperAdapter) adapter).getStickyAdapter() != null) {
                    stickyAdapter = (StickyAdapter) adapter;
                }
            } else if (adapter instanceof StickyAdapter) {
                stickyAdapter = (StickyAdapter) adapter;
            }
            if (mBaseStickyDecoration == null) {
                mBaseStickyDecoration = provideStickyDecoration(adapter, stickyAdapter);
                if (mBaseStickyDecoration == null) return;
                mBaseStickyDecoration.setItemSpace(mItemSpace + mDividerSize);
                addItemDecoration(mBaseStickyDecoration);
            } else {
                mBaseStickyDecoration.setStickyAdapter(stickyAdapter);
                invalidateItemDecorations();
            }
        }
    }

    @Nullable
    protected BaseStickyDecoration provideStickyDecoration(@NonNull RecyclerView.Adapter adapter, @Nullable StickyAdapter stickyAdapter) {
        if (stickyAdapter == null) return null;
        switch (mStickyMode) {
            case StickyAdapter.Mode.MIDDLE:
                return new MiddleStickyDecoration(stickyAdapter);