public class FastScrollRecyclerView extends RecyclerView implements RecyclerView.OnItemTouchListener {

    private FastScroller mScrollbar;

    /**
     * The current scroll state of the recycler view.  We use this in onUpdateScrollbar()
     * and scrollToPositionAtProgress() to determine the scroll position of the recycler view so
     * that we can calculate what the scroll bar looks like, and where to jump to from the fast
     * scroller.
     */
    public static class ScrollPositionState {
        // The index of the first visible row
        public int rowIndex;
        // The offset of the first visible row
        public int rowTopOffset;
        // The height of a given row (they are currently all the same height)
        public int rowHeight;
    }

    private ScrollPositionState mScrollPosState = new ScrollPositionState();

    private int mDownX;
    private int mDownY;
    private int mLastY;

    private SparseIntArray mScrollOffsets;

    private ScrollOffsetInvalidator mScrollOffsetInvalidator;
    private OnFastScrollStateChangeListener mStateChangeListener;

    public FastScrollRecyclerView(Context context) {
        this(context, null);
    }

    public FastScrollRecyclerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FastScrollRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mScrollbar = new FastScroller(context, this, attrs);
        mScrollOffsetInvalidator = new ScrollOffsetInvalidator();
        mScrollOffsets = new SparseIntArray();
    }

    public int getScrollBarWidth() {
        return mScrollbar.getWidth();
    }

    public int getScrollBarThumbHeight() {
        return mScrollbar.getThumbHeight();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        addOnItemTouchListener(this);
    }

    @Override
    public void setAdapter(Adapter adapter) {
        if (getAdapter() != null) {
            getAdapter().unregisterAdapterDataObserver(mScrollOffsetInvalidator);
        }

        if (adapter != null) {
            adapter.registerAdapterDataObserver(mScrollOffsetInvalidator);
        }

        super.setAdapter(adapter);
    }

    /**
     * We intercept the touch handling only to support fast scrolling when initiated from the
     * scroll bar.  Otherwise, we fall back to the default RecyclerView touch handling.
     */
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
        return handleTouchEvent(ev);
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
        handleTouchEvent(ev);
    }

    /**
     * Handles the touch event and determines whether to show the fast scroller (or updates it if
     * it is already showing).
     */
    private boolean handleTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                // Keep track of the down positions
                mDownX = x;
                mDownY = mLastY = y;
                mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY, mStateChangeListener);
                break;
            case MotionEvent.ACTION_MOVE:
                mLastY = y;
                mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY, mStateChangeListener);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY, mStateChangeListener);
                break;
        }
        return mScrollbar.isDragging();
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }

    /**
     * Returns the available scroll height:
     * AvailableScrollHeight = Total height of the all items - last page height
     * <p>
     * This assumes that all rows are the same height.
     *
     * @param yOffset the offset from the top of the recycler view to start tracking.
     */
    protected int getAvailableScrollHeight(int rowCount, int rowHeight, int yOffset) {
        return getAvailableScrollHeight(rowCount * rowHeight, yOffset);
    }

    protected int getAvailableScrollHeight(int adapterHeight, int yOffset) {
        int visibleHeight = getHeight();
        int scrollHeight = getPaddingTop() + yOffset + adapterHeight + getPaddingBottom();
        int availableScrollHeight = scrollHeight - visibleHeight;
        return availableScrollHeight;
    }

    /**
     * Returns the available scroll bar height:
     * AvailableScrollBarHeight = Total height of the visible view - thumb height
     */
    protected int getAvailableScrollBarHeight() {
        int visibleHeight = getHeight();
        int availableScrollBarHeight = visibleHeight - mScrollbar.getThumbHeight();
        return availableScrollBarHeight;
    }

    @Override
    public void draw(Canvas c) {
        super.draw(c);
        onUpdateScrollbar();
        mScrollbar.draw(c);
    }

    /**
     * Updates the scrollbar thumb offset to match the visible scroll of the recycler view.  It does
     * this by mapping the available scroll area of the recycler view to the available space for the
     * scroll bar.
     *
     * @param scrollPosState the current scroll position
     * @param rowCount       the number of rows, used to calculate the total scroll height (assumes that
     *                       all rows are the same height)
     * @param yOffset        the offset to start tracking in the recycler view (only used for all apps)
     * @see #updateThumbPositionWithMeasurableAdapter(ScrollPositionState, int) If a
     *                       {@link MeasurableAdapter} is attached.
     */
    protected void updateThumbPosition(ScrollPositionState scrollPosState, int rowCount, int yOffset) {
        int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight, yOffset);
        int availableScrollBarHeight = getAvailableScrollBarHeight();

        // Only show the scrollbar if there is height to be scrolled
        if (availableScrollHeight <= 0) {
            mScrollbar.setThumbPosition(-1, -1);
            return;
        }

        // Calculate the current scroll position, the scrollY of the recycler view accounts for the
        // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
        // padding)
        int scrollY = getPaddingTop() + yOffset + (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
        int scrollBarY = (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);

        // Calculate the position and size of the scroll bar
        int scrollBarX;
        if (Utils.isRtl(getResources())) {
            scrollBarX = 0;
        } else {
            scrollBarX = getWidth() - mScrollbar.getWidth();
        }
        mScrollbar.setThumbPosition(scrollBarX, scrollBarY);
    }

    /**
     * Updates the scrollbar thumb offset to match the visible scroll of the recycler view.  It does
     * this by mapping the available scroll area of the recycler view to the available space for the
     * scroll bar.
     * <p>
     * The height of the adapter is calculated using the attached {@link MeasurableAdapter}.
     *
     * @param scrollPosState the current scroll position
     * @param yOffset        the offset to start tracking in the recycler view (only used for all apps)
     */
    protected void updateThumbPositionWithMeasurableAdapter(ScrollPositionState scrollPosState, int yOffset) {
        int adapterHeight = calculateAdapterHeight();

        int availableScrollHeight = getAvailableScrollHeight(adapterHeight, yOffset);
        int availableScrollBarHeight = getAvailableScrollBarHeight();

        // Only show the scrollbar if there is height to be scrolled
        if (availableScrollHeight <= 0) {
            mScrollbar.setThumbPosition(-1, -1);
            return;
        }

        // Calculate the current scroll position, the scrollY of the recycler view accounts for the
        // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
        // padding)
        int scrolledPastHeight = calculateScrollDistanceToPosition(scrollPosState.rowIndex);
        int scrollY = getPaddingTop() + yOffset + scrolledPastHeight - scrollPosState.rowTopOffset;
        int scrollBarY = (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);

        // Calculate the position and size of the scroll bar
        int scrollBarX;
        if (Utils.isRtl(getResources())) {
            scrollBarX = 0;
        } else {
            scrollBarX = getWidth() - mScrollbar.getWidth();
        }
        mScrollbar.setThumbPosition(scrollBarX, scrollBarY);
    }

    /**
     * Maps the touch (from 0..1) to the adapter position that should be visible.
     */
    public String scrollToPositionAtProgress(float touchFraction) {
        int itemCount = getAdapter().getItemCount();
        if (itemCount == 0) {
            return "";
        }
        int spanCount = 1;
        int rowCount = itemCount;
        if (getLayoutManager() instanceof GridLayoutManager) {
            spanCount = ((GridLayoutManager) getLayoutManager()).getSpanCount();
            rowCount = (int) Math.ceil((double) rowCount / spanCount);
        }

        // Stop the scroller if it is scrolling
        stopScroll();

        getCurScrollState(mScrollPosState);

        float itemPos = itemCount * touchFraction;

        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);

        //The exact position of our desired item
        int exactItemPos = (int) (availableScrollHeight * touchFraction);

        //Scroll to the desired item. The offset used here is kind of hard to explain.
        //If the position we wish to scroll to is, say, position 10.5, we scroll to position 10,
        //and then offset by 0.5 * rowHeight. This is how we achieve smooth scrolling.
        LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
        layoutManager.scrollToPositionWithOffset(spanCount * exactItemPos / mScrollPosState.rowHeight,
                -(exactItemPos % mScrollPosState.rowHeight));

        if (!(getAdapter() instanceof SectionedAdapter)) {
            return "";
        }

        int posInt = (int) ((touchFraction == 1) ? itemPos - 1 : itemPos);

        SectionedAdapter sectionedAdapter = (SectionedAdapter) getAdapter();
        return sectionedAdapter.getSectionName(posInt);
    }

    /**
     * Updates the bounds for the scrollbar.
     */
    public void onUpdateScrollbar() {

        if (getAdapter() == null) {
            return;
        }

        int rowCount = getAdapter().getItemCount();
        if (getLayoutManager() instanceof GridLayoutManager) {
            int spanCount = ((GridLayoutManager) getLayoutManager()).getSpanCount();
            rowCount = (int) Math.ceil((double) rowCount / spanCount);
        }
        // Skip early if, there are no items.
        if (rowCount == 0) {
            mScrollbar.setThumbPosition(-1, -1);
            return;
        }

        // Skip early if, there no child laid out in the container.
        getCurScrollState(mScrollPosState);
        if (mScrollPosState.rowIndex < 0) {
            mScrollbar.setThumbPosition(-1, -1);
            return;
        }

        if (getAdapter() instanceof MeasurableAdapter) {
            updateThumbPositionWithMeasurableAdapter(mScrollPosState, 0);
        } else {
            updateThumbPosition(mScrollPosState, rowCount, 0);
        }
    }

    /**
     * Calculates the total height of the recycler view. This method should only be called when the
     * attached adapter implements {@link MeasurableAdapter}.
     * @return The total height of all rows in the RecyclerView
     */
    private int calculateAdapterHeight() {
        return calculateScrollDistanceToPosition(getAdapter().getItemCount());
    }

    /**
     * Calculates the total height of all views above a position in the recycler view. This method
     * should only be called when the attached adapter implements {@link MeasurableAdapter}.
     * @param adapterIndex The index in the adapter to find the total height above the
     *                     corresponding view
     * @return The total height of all views above {@code adapterIndex} in pixels
     */
    private int calculateScrollDistanceToPosition(int adapterIndex) {
        if (mScrollOffsets.indexOfKey(adapterIndex) >= 0) {
            return mScrollOffsets.get(adapterIndex);
        }

        int totalHeight = 0;
        MeasurableAdapter measurer = (MeasurableAdapter) getAdapter();

        // TODO Take grid layouts into account

        for (int i = 0; i < adapterIndex; i++) {
            mScrollOffsets.put(i, totalHeight);
            int viewType = getAdapter().getItemViewType(i);
            totalHeight += measurer.getViewTypeHeight(this, viewType);
        }

        mScrollOffsets.put(adapterIndex, totalHeight);
        return totalHeight;
    }

    /**
     * Returns the current scroll state of the apps rows.
     */
    private void getCurScrollState(ScrollPositionState stateOut) {
        stateOut.rowIndex = -1;
        stateOut.rowTopOffset = -1;
        stateOut.rowHeight = -1;

        int itemCount = getAdapter().getItemCount();

        // Return early if there are no items, or no children.
        if (itemCount == 0 || getChildCount() == 0) {
            return;
        }

        View child = getChildAt(0);

        stateOut.rowIndex = getChildAdapterPosition(child);
        if (getLayoutManager() instanceof GridLayoutManager) {
            stateOut.rowIndex = stateOut.rowIndex / ((GridLayoutManager) getLayoutManager()).getSpanCount();
        }
        stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
        stateOut.rowHeight = child.getHeight() + getLayoutManager().getTopDecorationHeight(child)
                + getLayoutManager().getBottomDecorationHeight(child);
    }

    public void setThumbColor(@ColorInt int color) {
        mScrollbar.setThumbColor(color);
    }

    public void setTrackColor(@ColorInt int color) {
        mScrollbar.setTrackColor(color);
    }

    public void setPopupBgColor(@ColorInt int color) {
        mScrollbar.setPopupBgColor(color);
    }

    public void setPopupTextColor(@ColorInt int color) {
        mScrollbar.setPopupTextColor(color);
    }

    public void setPopupTextSize(int textSize) {
        mScrollbar.setPopupTextSize(textSize);
    }

    public int getPopupTextSize() {
        return mScrollbar.getPopupTextSize();
    }

    public void setPopUpTypeface(Typeface typeface) {
        mScrollbar.setPopupTypeface(typeface);
    }

    public void setAutoHideDelay(int hideDelay) {
        mScrollbar.setAutoHideDelay(hideDelay);
    }

    public void setAutoHideEnabled(boolean autoHideEnabled) {
        mScrollbar.setAutoHideEnabled(autoHideEnabled);
    }

    public void setStateChangeListener(OnFastScrollStateChangeListener stateChangeListener) {
        mStateChangeListener = stateChangeListener;
    }

    /**
     * Set the FastScroll Popup position. This is either {@link FastScroller.FastScrollerPopupPosition#ADJACENT},
     * meaning the popup moves adjacent to the FastScroll thumb, or {@link FastScroller.FastScrollerPopupPosition#CENTER},
     * meaning the popup is static and centered within the RecyclerView.
     */
    public void setPopupPosition(@FastScroller.FastScrollerPopupPosition int popupPosition) {
        mScrollbar.setPopupPosition(popupPosition);
    }

    private class ScrollOffsetInvalidator extends AdapterDataObserver {
        private void invalidateAllScrollOffsets() {
            mScrollOffsets.clear();
        }

        @Override
        public void onChanged() {
            invalidateAllScrollOffsets();
        }

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

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

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

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            invalidateAllScrollOffsets();
        }

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

    public interface SectionedAdapter {
        @NonNull
        String getSectionName(int position);
    }

    /**
     * FastScrollRecyclerView by default assumes that all items in a RecyclerView will have
     * ItemViews with the same heights so that the total height of all views in the RecyclerView
     * can be calculated. If your list uses different view heights, then make your adapter implement
     * this interface.
     */
    public interface MeasurableAdapter {
        /**
         * Gets the height of a specific view type, including item decorations
         * @param recyclerView The recyclerView that this item view will be placed in
         * @param viewType The view type to get the height of
         * @return The height of a single view for the given view type in pixels
         */
        int getViewTypeHeight(RecyclerView recyclerView, int viewType);
    }
} public class MAVApplication extends MultiDexApplication {

    static {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
    }

    private static MAVApplication application = null;

    private RefWatcher refWatcher;
    private BoxStore boxStore;
    private DataRepository mDataRepository;
    private SharedPreferences mPrefs;

    public static MAVApplication getInstance() {
        return application;
    }

    public static File getCacheDirectory() {
        return application.getCacheDir();
    }

    public static RefWatcher getRefWatcher(Context context) {
        MAVApplication application = (MAVApplication) context.getApplicationContext();
        return application.refWatcher;
    }

    public BoxStore getBoxStore() {
        return boxStore;
    }

    public DataRepository getRepository() {
        return mDataRepository;
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }

    @Override
    public void onCreate() {
        super.onCreate();

        application = this;

        //ACRA.init(this);
        initializeLeakCanary();
        initializeTimber();
        initializeObjectBox();
        ImageLoaderManager.getInstance(application);
        mDataRepository = new ObjectBoxRepository();
        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
        setupDataPaths();
        ParseManager.getInstance(getApplicationContext());
    }

    private void initializeObjectBox() {
        boxStore = MyObjectBox.builder().androidContext(this).build();
    }

    private void initializeLeakCanary() {
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        refWatcher = LeakCanary.install(this);
    }

    private void initializeTimber() {
        if (BuildConfig.DEBUG) {
            Timber.plant(new Timber.DebugTree() {
                // Add the line number to the tag
                @Override
                protected String createStackElementTag(StackTraceElement element) {
                    return String.format("[Line - %s] [Method - %s] [Class - %s]",
                            element.getLineNumber(),
                            element.getMethodName(),
                            super.createStackElementTag(element));
                }
            });
        } else {
            Timber.plant(new ReleaseTree());
        }
    }

    private void setupDataPaths() {
        String dataDirPath = mPrefs.getString(Constants.KEY_LIBRARY_PATH, "");
        File file = null;
        if (TextUtils.isEmpty(dataDirPath)) {
            file = new File(mPrefs.getBoolean(Constants.KEY_LIBRARY_FORCE2SD, false) ?
                    StorageUtils.getExtSDCard(this) : Environment.getExternalStorageDirectory(),
                    StorageUtils.DEFAULT_LIBRARY_FOLDER);
            file.mkdirs();
            dataDirPath = file.getAbsolutePath();
            mPrefs.edit().putString(Constants.KEY_LIBRARY_PATH, dataDirPath).apply();
        }
        if (!TextUtils.isEmpty(dataDirPath))
            StorageUtils.setDataDirectory(new File(dataDirPath));
        if (StorageUtils.getDataDirectory() == null)
            StorageUtils.setDataDirectory(new File(mPrefs.getBoolean(Constants.KEY_LIBRARY_FORCE2SD, false) ?
                    StorageUtils.getExtSDCard(this) : Environment.getExternalStorageDirectory(), StorageUtils.DEFAULT_LIBRARY_FOLDER));

        if (StorageUtils.getCacheDir() == null) {
            StorageUtils.setCacheDir(new File(StorageUtils.getExtSDCard(this), StorageUtils.DEFAULT_LIBRARY_FOLDER + "Cache/"));
        }
    }

    public SharedPreferences getPreferences() {
        return mPrefs;
    }

    /**
     * A tree which logs important information for crash reporting.
     */
    private static class ReleaseTree extends Timber.Tree {

        private static final int MAX_LOG_LENGTH = 4000;

        @Override
        protected boolean isLoggable(int priority) {
            if (priority == Log.VERBOSE || priority == Log.DEBUG) {
                return false;
            }

            // Only log WARN, INFO, ERROR, WTF
            return true;
        }

        @Override
        protected void log(int priority, String tag, String message, Throwable t) {
            if (isLoggable(priority)) {
                // Message is short enough, does not need to be broken into chunks
                if (message.length() < MAX_LOG_LENGTH) {
                    if (priority == Log.ASSERT) {
                        Log.wtf(tag, message);
                    } else {
                        Log.println(priority, tag, message);
                    }
                    return;
                }

                // Split by line, then ensure each line can fit into Log's maximum length
                for (int i = 0, length = message.length(); i < length; i++) {
                    int newline = message.indexOf('\n', i);
                    newline = newline != -1 ? newline : length;
                    do {
                        int end = Math.min(newline, i + MAX_LOG_LENGTH);
                        String part = message.substring(i, end);
                        if (priority == Log.ASSERT) {
                            Log.wtf(tag, part);
                        } else {
                            Log.println(priority, tag, part);
                        }
                        i = end;
                    } while (i < newline);
                }
            }
        }
    }
} public abstract class FlexibleAdapter<VH extends RecyclerView.ViewHolder, T>
		extends SelectableAdapter<VH>
		implements Filterable {

	private static final String TAG = FlexibleAdapter.class.getSimpleName();
	public static final long UNDO_TIMEOUT = 5000L;

	/**
	 * Lock used to modify the content of {@link #mItems}. Any write operation performed on the array should be
	 * synchronized on this lock. This lock is also used by the filter (see {@link #getFilter()} to make a synchronized
	 * copy of the original array of data.
	 */
	private final Object mLock = new Object();

	protected List<T> mItems;
	protected List<T> mDeletedItems;
	protected List<Integer> mOriginalPosition;
	private Timer mUndoTimer;
	//Searchable fields
	protected static String mSearchText; //Static: It can exist only 1 searchText
	private FlexibleFilter mFilter;
	//A copy of the original mObjects array, initialized from and then used instead as soon as
	// the mFilter ArrayFilter is used. mObjects will then only contain the filtered values.
	private ArrayList<T> mOriginalValues;


	public FlexibleAdapter() {
	}
	
	/**
	 * This method will refresh the entire DataSet content.<br/>
	 * The parameter is useful to filter the DataSet. It can be removed
	 * or the type can be changed accordingly (String is the most used value).
	 * <br/>Pass null value in case not used.
	 * 
	 * @param param A custom parameter to filter the DataSet
	 */
	public abstract void updateDataSet(String param);
	
	/**
	 * Returns the custom object "Item".
	 * @param position
	 * @return the custom "Item" object
	 */
	public T getItem(int position) {
		return mItems.get(position);
	}
	
	/**
	 * Retrieve the position of the Item in the Adapter
	 *
	 * @param item
	 * @return the position in the Adapter if found, -1 otherwise
	 */
	public int getPositionForItem(T item) {
		return mItems != null && mItems.size() > 0 ? mItems.indexOf(item) : -1;
	}


	private int getOriginalPositionForItem(T item) {
		return mOriginalValues != null && mOriginalValues.size() > 0 ? mOriginalValues.indexOf(item) : -1;
	}

	public boolean contains(T item) {
		if (mOriginalValues != null) {
			return mOriginalValues.contains(item);
		} else {
			return mItems != null && mItems.contains(item);
		}
	}
	
	@Override
	public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
	
	@Override
	public abstract void onBindViewHolder(VH holder, final int position);

	@Override
	public int getItemCount() {
		return mItems.size();
	}
	
	public void updateItem(int position, T item) {
		if (position < 0) return;
		synchronized (mLock) {
			if (mOriginalValues != null) {
				mOriginalValues.set(getOriginalPositionForItem(item), item);
			}
			mItems.set(position, item);
		}
		Log.d(TAG, "updateItem notifyItemChanged on position "+position);
		notifyItemChanged(position);
	}

	/**
	 * Insert given Item at position or Add Item at last position.
	 *
	 * @param position
	 * @param item
	 */
	public void addItem(int position, T item) {
		if (position < 0) return;
		//Insert Item
		if (position < mItems.size()) {
			Log.d(TAG, "addItem notifyItemInserted on position " + position);
			synchronized (mLock) {
				if (mOriginalValues != null) {
					mOriginalValues.add(position, item);
				}
				mItems.add(position, item);
			}

		//Add Item at the last position
		} else {
			Log.d(TAG, "addItem notifyItemInserted on last position");
			synchronized (mLock) {
				if (mOriginalValues != null) {
					mOriginalValues.add(item);
				}
				mItems.add(item);
				position = mItems.size();
			}
		}

		notifyItemInserted(position);
	}

	/**
	 * Add Item at last position.
	 *
	 * @param item
	 */
	public void addItem(T item) {
		int position = 0;
		Log.d(TAG, "addItem notifyItemInserted on last position");
		synchronized (mLock) {
			if (mOriginalValues != null) {
				mOriginalValues.add(item);
			}
			mItems.add(item);
			position = mItems.size();
		}

		notifyItemInserted(position);
	}

	/* DELETE ITEMS METHODS */
	
	public void removeItem(int position) {
		if (position < 0) return;
		if (position < mItems.size()) {
			Log.d(TAG, "removeItem notifyItemRemoved on position " + position);
			synchronized (mLock) {
				saveDeletedItem(position);
				if (mOriginalValues != null) {
					mOriginalValues.remove(getOriginalPositionForItem(mItems.get(position)));
				}
				mItems.remove(position);
			}
			notifyItemRemoved(position);
		} else {
			Log.w(TAG, "removeItem WARNING! Position OutOfBound! Review the position to remove!");
		}
	}
	
	public void removeItems(List<Integer> selectedPositions) {
		Log.d(TAG, "removeItems reverse Sorting positions --------------");
		// Reverse-sort the list
		Collections.sort(selectedPositions, new Comparator<Integer>() {
			@Override
			public int compare(Integer lhs, Integer rhs) {
				return rhs - lhs;
			}
		});

		// Split the list in ranges
		while (!selectedPositions.isEmpty()) {
			if (selectedPositions.size() == 1) {
				removeItem(selectedPositions.get(0));
				//Align the selection list when removing the item
				selectedPositions.remove(0);
			} else {
				int count = 1;
				while (selectedPositions.size() > count && selectedPositions.get(count).equals(selectedPositions.get(count - 1) - 1)) {
					++count;
				}

				if (count == 1) {
					removeItem(selectedPositions.get(0));
				} else {
					removeRange(selectedPositions.get(count - 1), count);
				}

				for (int i = 0; i < count; ++i) {
					selectedPositions.remove(0);
				}
			}
			Log.d(TAG, "removeItems current selection " + getSelectedItems());
		}
	}

	private void removeRange(int positionStart, int itemCount) {
		Log.d(TAG, "removeRange positionStart="+positionStart+ " itemCount="+itemCount);
		for (int i = 0; i < itemCount; ++i) {
			synchronized (mLock) {
				saveDeletedItem(positionStart);
				if (mOriginalValues != null) {
					mOriginalValues.remove(getOriginalPositionForItem(mItems.get(positionStart)));
				}
				mItems.remove(positionStart);
			}
		}
		Log.d(TAG, "removeRange notifyItemRangeRemoved");
		notifyItemRangeRemoved(positionStart, itemCount);
	}

	/* UNDO METHODS */

	/**
	 * Save temporary Items for an eventual Undo.
	 *
	 * @param position
	 */
	private void saveDeletedItem(int position) {
		if (mDeletedItems == null) {
			mDeletedItems = new ArrayList<T>();
			mOriginalPosition = new ArrayList<Integer>();
		}
		Log.d(TAG, "Recycled "+getItem(position)+" on position="+position);
		mDeletedItems.add(mItems.get(position));
		mOriginalPosition.add(position);
	}

	/**
	 * Restore items just removed.
	 */
	public void restoreDeletedItems() {
		stopUndoTimer();
		//Reverse insert (list was reverse ordered on Delete)
		for (int i = mOriginalPosition.size()-1; i >= 0; i--) {
			addItem(mOriginalPosition.get(i), mDeletedItems.get(i));
		}
		emptyBin();
	}

	/**
	 * Clean memory.
	 * <br/><b>Note:</b> This method is automatically called after timer is over and after a restoration.
	 */
	public void emptyBin() {
		mDeletedItems = null;
		mOriginalPosition = null;
	}

	/**
	 * Convenience method to start Undo timer with default timeout of 5''
	 */
	public void startUndoTimer() {
		startUndoTimer(0);
	}

	/**
	 * Start Undo timer with custom timeout
	 *
	 * @param timeout
	 */
	public void startUndoTimer(long timeout) {
		stopUndoTimer();
		this.mUndoTimer = new Timer();
		this.mUndoTimer.schedule(new UndoTimer(), timeout > 0 ? timeout : UNDO_TIMEOUT);
	}

	/**
	 * Stop Undo timer.
	 * <br/><b>Note:</b> This method is automatically called in case of restoration.
	 */
	private void stopUndoTimer() {
		if (this.mUndoTimer != null) {
			this.mUndoTimer.cancel();
			this.mUndoTimer = null;
		}
	}

	/**
	 * Inner class for TimerTask.
	 */
	private class UndoTimer extends TimerTask {
		public void run() {
			mUndoTimer = null;
			emptyBin();
		}
	}

	/* SEARCH-FILTER */

	public static String getSearchText() {
		return mSearchText != null ? mSearchText : "";
	}

	public static void setSearchText(String searchText) {
		if (searchText != null)
			mSearchText = searchText.trim().toLowerCase(Locale.getDefault());
		else mSearchText = "";
	}

	public static boolean hasSearchText() {
		return mSearchText != null && mSearchText.length() > 0;
	}

	/**
	 * {@inheritDoc}
	 */
	public Filter getFilter() {
		if (mFilter == null) {
			mFilter = new FlexibleFilter();
		}
		return mFilter;
	}

	/**
	 * DEFAULT IMPLEMENTATION, OVERRIDE TO HAVE OWN FILTER!
	 *
	 * <br/><br/>Performs filtering on the provided object and returns true, if the object should be in the filtered collection,
	 * or false if it shouldn't.
	 *
	 * @param myObject The object to be inspected
	 * @param constraint Constraint, that the object has to fulfil
	 * @return true, if the object should be in the filteredResult, false otherwise
	 */
	protected boolean filterObject(T myObject, String constraint) {
		final String valueText = myObject.toString().toLowerCase();

		//First match against the whole, non-splitted value
		if (valueText.startsWith(constraint)) {
			return true;
		} else {
			final String[] words = valueText.split(" ");
			final int wordCount = words.length;

			//Start at index 0, in case valueText starts with space(s)
			for (int k = 0; k < wordCount; k++) {
				if (words[k].startsWith(constraint)) {
					return true;
				}
			}
		}

		//No match, so don't add to collection
		return false;
	}


	private class FlexibleFilter extends Filter {

		@Override
		protected FilterResults performFiltering(CharSequence prefix) {
			final FilterResults results = new FilterResults();

			//Save Original objects
			if (mOriginalValues == null) {
				synchronized (mLock) {
					mOriginalValues = new ArrayList<T>(mItems);
				}
			}

			//Prepare base list for each search
			List<T> values;
			synchronized (mLock) {
				values = new ArrayList<T>(mOriginalValues);
			}

			if (prefix == null || prefix.length() == 0) {
				//Restore original objects when filter is empty
				results.values = values;
				results.count = values.size();
				//This is extremely important otherwise it continues
				// to add/remove from OriginalValues
				mOriginalValues = null;

			} else { //FilterIn the objects
				String prefixString = prefix.toString().toLowerCase();

				final int count = values.size();
				final ArrayList<T> newValues = new ArrayList<T>();

				for (int i = 0; i < count; i++) {
					final T value = values.get(i);
					if (filterObject(value, prefixString)) {
						newValues.add(value);
					}
				}

				results.values = newValues;
				results.count = newValues.size();
			}

			Log.d(TAG, "performFiltering Size="+results.count+ " values="+results.values);
			return results;
		}

		@Override
		protected void publishResults(CharSequence constraint, FilterResults results) {
			//noinspection unchecked
			mItems = (List<T>) results.values;
			notifyDataSetChanged();
		}
	}

} public interface OnItemClickListener {
        /**
         *
         * @param position
         * @return true if MULTI selection is enabled, false for SINGLE selection
         */
        boolean onListItemClick(int position);

        /**
         *
         * @param position
         */
        void onListItemLongClick(int position);
    } @Entity
public class Anime {

    @Id
    private long id = 0;
    private String title;
    private String url;
    private String cover;

    private String type;
    private String aliases;
    private String creator;
    private String genres;
    private String summary;
    @Transient
    private String warning;
    private Date date;
    private int status = -1;


    @Backlink(to = "animeId")
    private List<Episode> episodes = new ArrayList<>();

    private String latestEpisode;
    private String latestUpdateTime;
    private int tagId = -1;

    @Transient
    private int newEpisodes = -1;
    @Transient
    private int unviewedEpisodes = -1;
    @Transient
    private int episodeCount = -1;
    @Transient
    private int catalogPosition;

    /** Used to resolve relations */
    @Internal
    @Generated(1307364262)
    transient BoxStore __boxStore;

    @Generated(2088667730)
    @Internal
    /** This constructor was generated by ObjectBox and may change any time. */
    public Anime(long id, String title, String url, String cover, String type,
            String aliases, String creator, String genres, String summary,
            Date date, int status, String latestEpisode, String latestUpdateTime,
            int tagId) {
        this.id = id;
        this.title = title;
        this.url = url;
        this.cover = cover;
        this.type = type;
        this.aliases = aliases;
        this.creator = creator;
        this.genres = genres;
        this.summary = summary;
        this.date = date;
        this.status = status;
        this.latestEpisode = latestEpisode;
        this.latestUpdateTime = latestUpdateTime;
        this.tagId = tagId;
    }

    @Generated(1101169224)
    public Anime() {
    }

    public String getCover() {
        return cover;
    }

    public void setCover(String cover) {
        this.cover = cover;
    }

    @Override
    public String toString() {
        return "Anime [name=" + title + ", "
                + "url=" + url + ", "
                + "cover=" + cover + ", "
                + "aliases=" + aliases + ", "
                + "creator=" + creator + ", "
                + "summary=" + summary + ", "
                + "genres=" + genres + ", "
                + "warning=" + warning + ", "
                + "episodes=" + episodes + "]";
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getAliases() {
        return aliases;
    }

    public void setAliases(String aliases) {
        this.aliases = aliases;
    }

    public String getCreator() {
        return creator;
    }

    public void setCreator(String creator) {
        this.creator = creator;
    }

    public String getGenres() {
        return genres;
    }

    public void setGenres(String genres) {
        this.genres = genres;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getWarning() {
        return warning;
    }

    public void setWarning(String warning) {
        this.warning = warning;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public List<Episode> getEpisodes() {
        return episodes;
    }

    public void setEpisodes(List<Episode> episodes) {
        this.episodes = episodes;
    }

    public String getLatestEpisode() {
        return latestEpisode;
    }

    public void setLatestEpisode(String latestEpisode) {
        this.latestEpisode = latestEpisode;
    }

    public String getLatestUpdateTime() {
        return latestUpdateTime;
    }

    public void setLatestUpdateTime(String latestUpdateTime) {
        this.latestUpdateTime = latestUpdateTime;
    }

    public int getTagId() {
        return tagId;
    }

    public void setTagId(int tagId) {
        this.tagId = tagId;
    }

    public int getNewEpisodes() {
        return newEpisodes;
    }

    public void setNewEpisodes(int newEpisodes) {
        this.newEpisodes = newEpisodes;
    }

    public int getUnviewedEpisodes() {
        return unviewedEpisodes;
    }

    public void setUnviewedEpisodes(int unviewedEpisodes) {
        this.unviewedEpisodes = unviewedEpisodes;
    }

    public int getEpisodeCount() {
        return episodeCount;
    }

    public void setEpisodeCount(int episodeCount) {
        this.episodeCount = episodeCount;
    }

    public boolean hasNewEpisodes() {
        return newEpisodes > 0;
    }

    public int getCatalogPosition() {
        return catalogPosition;
    }

    public void setCatalogPosition(int catalogPosition) {
        this.catalogPosition = catalogPosition;
    }

    public enum Order implements Comparator<Anime> {
        ByNameAZ() {
            @Override
            public int compare(Anime anime1, Anime anime2) {

                String lTitle1 = anime1.getTitle().toLowerCase();
                String lTitle2 = anime2.getTitle().toLowerCase();

                // ascending order
                return lTitle1.compareTo(lTitle2);
            }
        },
        ByNameZA() {
            @Override
            public int compare(Anime anime1, Anime anime2) {

                String lTitle1 = anime1.getTitle().toLowerCase();
                String lTitle2 = anime2.getTitle().toLowerCase();

                // ascending order
                return lTitle2.compareTo(lTitle1);
            }
        },
        ByTagIdL2H() {
            @Override
            public int compare(Anime anime1, Anime anime2) {
                int lPriority1 = anime1.getTagId();
                int lPriority2 = anime2.getTagId();

                if (lPriority1 == lPriority2)
                    return 0;
                else if (lPriority1 > lPriority2)
                    return 1;
                else
                    return -1;
            }
        },
        ByTagIdL2H_AZ() {
            @Override
            public int compare(Anime anime1, Anime anime2) {
                int lPriority1 = anime1.getTagId();
                int lPriority2 = anime2.getTagId();

                if (lPriority1 > lPriority2)
                    return 1;
                else if (lPriority1 < lPriority2)
                    return -1;
                else {
                    return ByNameAZ.compare(anime1, anime2);
                }
            }
        },
        ByTagIdH2L_AZ() {
            @Override
            public int compare(Anime anime1, Anime anime2) {
                int tagId1 = anime1.getTagId();
                int tagId2 = anime2.getTagId();

                if (tagId1 < tagId2)
                    return 1;
                else if (tagId1 > tagId2)
                    return -1;
                else {
                    return ByNameAZ.compare(anime1, anime2);
                }
            }
        },
        ByLastUpdate() {
            @Override
            public int compare(Anime anime1, Anime anime2) {
                //long lUpdateTime1 = anime1.getLastUpdate();
                //long lUpdateTime2 = anime2.getLastUpdate();
                long lUpdateTime1 = 0;
                long lUpdateTime2 = 0;
                if (lUpdateTime1 == lUpdateTime2)
                    return 0;
                else if (lUpdateTime1 > lUpdateTime2)
                    return -1;
                else
                    return 1;
            }
        },
    }
} @Entity
public class FavoriteRecord {
    @Id
    private long id = 0;
    private String animeUrl = "";
    private int tagId = -1;

    @Relation
    private Anime anime;
    /**
     * Used to resolve relations
     */
    @Internal
    @Generated(1307364262)
    transient BoxStore __boxStore;

    @Generated(1556071527)
    @Internal
    /** This constructor was generated by ObjectBox and may change any time. */
    public FavoriteRecord(long id, String animeUrl, int tagId, long animeId) {
        this.id = id;
        this.animeUrl = animeUrl;
        this.tagId = tagId;
        this.animeToOne.setTargetId(animeId);
    }

    @Generated(1447135516)
    public FavoriteRecord() {
    }

    @Internal
    @Generated(1223043601)
    transient ToOne<Anime> animeToOne = new ToOne<>(this, FavoriteRecord_.anime);

    @Override
    public String toString() {
        return "FavoriteObject [animeUrl=" + animeUrl + ", tagId=" + tagId + "]";
    }

    /**
     * @return the ID
     */
    public long getId() {
        return id;
    }

    /**
     * @param id the ID to set
     */
    public void setId(long id) {
        this.id = id;
    }

    /**
     * @return the mAnimeUrl
     */
    public String getAnimeUrl() {
        return animeUrl;
    }

    /**
     * @param animeUrl the AnimeURL to set
     */
    public void setAnimeUrl(String animeUrl) {
        this.animeUrl = animeUrl;
    }

    public int getTagId() {
        return tagId;
    }

    public void setTagId(int tagId) {
        this.tagId = tagId;
    }

    /**
     * To-one relationship, resolved on first access.
     */
    @Generated(697412469)
    public Anime getAnime() {
        anime = animeToOne.getTarget();
        return anime;
    }

    /**
     * Set the to-one relation including its ID property.
     */
    @Generated(319411835)
    public void setAnime(Anime anime) {
        animeToOne.setTarget(anime);
        this.anime = anime;
    }
} @Entity
public class FavoriteTag {

    @Id
    private long id = 0;
    private int tagId = -1;
    private String title = "";
    private int ribbonId = -1;

    @Generated(30053616)
    @Internal
    /** This constructor was generated by ObjectBox and may change any time. */
    public FavoriteTag(long id, int tagId, String title, int ribbonId) {
        this.id = id;
        this.tagId = tagId;
        this.title = title;
        this.ribbonId = ribbonId;
    }

    @Generated(786092308)
    public FavoriteTag() {
    }

    public FavoriteTag(int tagId, String title, int ribbonId) {
        this.tagId = tagId;
        this.title = title;
        this.ribbonId = ribbonId;
    }

    @Override
    public String toString() {
        return "FavoriteTag [tagId=" + tagId + ", title=" + title + ", ribbonId=" + ribbonId + "]";
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getRibbonID() {
        return ribbonId;
    }

    public void setRibbonID(int ribbonID) {
        ribbonId = ribbonID;
    }

    public int getRibbonId() {
        return ribbonId;
    }

    public void setRibbonId(int ribbonId) {
        this.ribbonId = ribbonId;
    }

    public int getTagId() {
        return tagId;
    }

    public void setTagId(int tagId) {
        this.tagId = tagId;
    }

    public enum Order implements Comparator<FavoriteTag> {
        ByTagIdLowToHigh() {
            @Override
            public int compare(FavoriteTag tag1, FavoriteTag tag2) {
                int tagId1 = tag1.getTagId();
                int tagId2 = tag2.getTagId();

                if (tagId1 == tagId2)
                    return 0;
                else if (tagId1 > tagId2)
                    return 1;
                else
                    return -1;
            }
        }
    }
} public abstract class Parser {

    private static Parser instance = null;

    public String name;
    public boolean hasCatalog = true;
    public boolean isCatalogFilterable = true;
    public boolean isCatalogSortSupported = false;
    public boolean isLatestSortSupported = false;
    public boolean isShowAllSupported = false;
    public boolean isGridViewSupported = false;
    public boolean isGenreSortSupported = false;
    public boolean isCloudFlareDDOSEnabled = false;
    public boolean isCloudFlareDDOSPassed = false;
    public String mCookies = null;
    private static final int mHardCount = 1;

    public static String openloadLogin = "92c9a7c7a6fce945";
    public static String openloadKey = "nbqBkkQI";

    @Expose
    public String mCustomUserAgent = "";

    public int mAnimeCount = -1;
    private static int parserType = 0;

    public boolean isPremium = false;

    public static int mParseTimeOut = 120000;
    public static int mConnectTimeOut = 60000;

    protected static ArrayList<Parser> mParserArray = new ArrayList<Parser>();

    static {
        if (mParserArray.size() == 0) {
            Parser lParser = null;
            for (int i = 0; i < mHardCount; i++) {
                lParser = getNewInstance(i);
                if (lParser != null) {
                    lParser.mAnimeCount = getCountByURL(lParser.getServerUrl());
                    mParserArray.add(lParser);
                }
            }
        }
        Collections.sort(mParserArray, Order.ByNameAZ);
    }

    public static int getParserCount() {
        if (mParserArray != null)
            return mParserArray.size();
        else
            return -1;
    }

    OkHttpClient client = new OkHttpClient.Builder()
            .readTimeout(mParseTimeOut, TimeUnit.MILLISECONDS)
            .retryOnConnectionFailure(true).build();

    private static Parser getNewInstance(int pType) {
        switch (pType) {
            default:
                return new GoGoAnime();
        }
    }

    /**
     * @param pURL The type by url of a parser, if the type is different from the current parser, it replaces the old one.
     * @return A singleton instance of the parser
     */
    public static Parser getInstance(String pURL) {
        int lType = getTypeByURL(pURL);
        if (instance == null) {
            instance = getExistingInstance(lType);
        }
        return instance;
    }

    /**
     * @param pType The type of parser, if the type is different from the current parser, it replaces the old one.
     * @return A singleton instance of the parser
     */
    public static Parser getInstance(int pType) {
        if (instance == null || parserType != pType) {
            parserType = pType;
            instance = getParser();
        }
        WriteLog.appendLog("Parser Instance " + pType + " " + instance);
        return instance;
    }

    /**
     * @return The current parser, as set by {@link #getInstance(int)}
     */
    private static Parser getParser() {
        if (parserType > -1 && parserType < mParserArray.size())
            return getExistingInstance(parserType);
        else {
            WriteLog.appendLog("getParser() invalid parser id " + parserType);
            return getExistingInstance(0);
        }
    }

    public static ArrayList<Parser> getParserArray() {
        return mParserArray;
    }

    public static int getParseTimeOut() {
        return mParseTimeOut;
    }

    public static void setParseTimeOut(int mParseTimeOut) {
        Parser.mParseTimeOut = mParseTimeOut;
    }

    public String getUserAgent() {
        return TextUtils.isEmpty(mCustomUserAgent) ? mUserAgent : mCustomUserAgent;
    }

    public static String mUserAgent = "Mozilla/5.0 (Linux; Android 4.2.2; en-us; SAMSUNG GT-I9195 Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Version/1.0 Chrome/18.0.1025.308 Mobile Safari/535.19";

    public abstract String getServerUrl();

    public abstract ArrayList<Anime> getAnimeList(String url);

    public abstract Anime getAnimeDetails(String url);

    public abstract String getEpisodeVideo(String url);

    public abstract String[] getDirectoryLinks();

    public abstract String[] getDirectoryTitles();

    public abstract String getDirectoryURL(int pType);

    public abstract String getDirectoryURL(int pType, int pPage);

    public abstract String getDirectoryURL(String pUrl, int pPage);

    public abstract int getDirectoryLowerBound();

    public abstract int getDirectoryUpperBound();

    public boolean getIsPremium() {
        return isPremium;
    }

    public int getLanguageResId() {
        return R.string.english;
    }

    @Deprecated
    public ArrayList<Episode> getEpisodeList() {
        return null;
    }

    public Date getDateFormat(String date) {
        return null;
    }

    public String getRandomAnimeURL() {
        return null;
    }

    public ArrayList<Anime> getFullAnimeList(String pURL) {
        return null;
    }

    public String getFullAnimeListURL() {
        return null;
    }

    public String getLatestAnimeListURL() {
        return null;
    }

    public ArrayList<Anime> getLatestAnimeList(String pURL) {
        return null;
    }

    public static String getNameByUrl(String pURL) {
        if (pURL.contains("animefreak")) {
            return "Anime Freak";
        } else if (pURL.contains("watchanime.me")) {
            return "Watch Anime";
        } else if (pURL.contains("animeshow")) {
            return "Anime Show";
        } else if (pURL.contains("animetv.to")) {
            return "Anime Tv";
        } else if (pURL.contains("gogoanime.io")) {
            return "GoGo Anime";
        }
        return "Unsupported " + pURL;
    }

    public static int getTypeByURL(String pURL) {
        for (int i = 0; i < mParserArray.size(); i++) {
            if (pURL.contains(mParserArray.get(i).getServerUrl()
                    .replace("https://", "")
                    .replace("http://", "")
                    .replace("www.", ""))) {
                return i;
            }
        }
        return -1;
    }

    /**
     * @param pURL       The url associated with a manga to find proper parser type for.
     * @param pLangResID
     * @return The type of parser which supports it.
     */
    public static int getTypeByURL(String pURL, int pLangResID) {
        for (int i = 0; i < mParserArray.size(); i++) {
            if (pURL.contains(mParserArray.get(i).getServerUrl().replace("http://", "").replace("www.", ""))
                    && mParserArray.get(i).getLanguageResId() == pLangResID) {
                return i;
            }
        }

        return -1;
    }

    public static Parser getExistingInstance(String typeByUrl) {
        return mParserArray.get(getTypeByURL(typeByUrl));
    }

    public static Parser getExistingInstance(int parserId) {
        return mParserArray.get(parserId);
    }

    public static int getCountByURL(String pURL) {
        if (pURL.contains("anime-joy.tv") || pURL.contains("animejoy.tv")) {
            return 2478;
        } else if (pURL.contains("animefreak.tv")) {
            return 1856;
        } else if (pURL.contains("watchanimesonline.net")) {
            return 3653;
        }
        return -1;
    }

    public String getName() {
        return name;
    }

    public static enum Order implements Comparator<Parser> {
        ByNameAZ() {
            @Override
            public int compare(Parser pParser1, Parser pParser2) {

                String lTitle1 = pParser1.name.toLowerCase();
                String lTitle2 = pParser2.name.toLowerCase();

                // ascending order
                return lTitle1.compareTo(lTitle2);
            }
        },
        BySizeH2L() {
            @Override
            public int compare(Parser pParser1, Parser pParser2) {
                int lPriority1 = getCountByURL(pParser1.getServerUrl());
                int lPriority2 = getCountByURL(pParser2.getServerUrl());

                if (lPriority1 == lPriority2)
                    return 0;
                else if (lPriority1 > lPriority2)
                    return 1;
                else
                    return -1;
            }
        }
    }

    public static boolean isDeadSource(Parser existingInstance) {
        // TODO Auto-generated method stub
        return false;
    }

    protected String getVideoFromProviderByName(String name, String data) {
        if (name.equals("Estream")) {
            return getEstreamToVideo("https://estream.to/"+data);
        } else if (name.equals("Yourupload")) {
            return getYourUploadVideo("https://yourupload.com/embed/"+data);
        } else if (name.equals("Mp4Upload"))
            return getMP4UploadVideo("https://mp4upload.com/"+data);
        //yourupload.com/play/1332473.mp4?busted=1501129123931
        return "";
    }

    public String getVideoFromProvider(String url) {
        WriteLog.appendLog(name, "Video Provider: " + url);
        if (url.contains("auengine")) {
            return getAuEngineVideo(url);
        } else if (url.contains("mp4upload.com")) {
            return getMP4UploadComVideo(url);
        } else if (url.contains("mp4upload")) {
            return getMP4UploadVideo(url);
        } else if (url.contains("safeupload")) {
            return getSafeUploadVideo(url);
        } else if (url.contains("openload.co")) {
            //return getOpenloadCoVideo(url);
        } else if (url.contains("estream.to")) {
            return getEstreamToVideo(url);
        } else if (url.contains("streamango.com")) {
            return getStreamangoVideo(url);
        } else if (url.contains("vidstreaming.io")) {
            return getVidStreamingVideo(url);
        }
        WriteLog.appendLog("Unsupported Video Provider: " + url);
        return url;
    }

    private String getVidStreamingVideo(String url) {
        String videoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(getUserAgent())
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = docdata.select("div[class=videocontent]").select("video[id=my-video-player]").select("source");
            String tempUrl = "";
            if (eles != null && !eles.isEmpty()) {
                for (Element ele : eles) {
                    tempUrl = ele.attr("abs:src");
                    if (!TextUtils.isEmpty(tempUrl))
                        videoUrl = tempUrl;
                }
            } else {
                eles = docdata.select("div[class=videocontent]").select("iframe");
                if (eles != null && !eles.isEmpty()) {
                    videoUrl = eles.attr("src");
                    return getVideoFromProvider(videoUrl);
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return videoUrl;
    }

    public static String getOpenloadCoVideo(String url) {
        String fileId = "";
        String lVideoUrl = "";
        if (url.startsWith("https://openload.co/f/")) {
            String[] splitUrl = url.replace("https://openload.co/f/", "").split("/");
            fileId = splitUrl[0];
        }
        if (url.startsWith("https://openload.co/embed/")) {
            String[] splitUrl = url.replace("https://openload.co/embed/", "").split("/");
            fileId = splitUrl[0];
        }
        /*
        DownloadTicket ticket = OpenloadManager.getInstance().getDownloadTicket(fileId);
        if (ticket.getStatus() == 200) {
            DownloadLink link = OpenloadManager.getInstance().getDownloadLink(fileId, ticket);
            if (link != null) {
                if (link.getStatus() == 200) {
                    lVideoUrl = link.getResult().getUrl();
                }
            }
        }
        */
        return lVideoUrl;
    }

    public String getMP4UploadComVideo(String url) {
        String lVideoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(mUserAgent)
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = null;
            if (!url.contains("embed")) {
                eles = docdata.select("div[class=leftcol]").select("iframe");
                if (eles != null && !eles.isEmpty()) {
                    url = eles.get(0).attr("src");
                    return getMP4UploadComVideo(url);
                }
            } else {
                eles = docdata.select("div[id=player_code]").select("script");
                lVideoUrl = eles.get(0).html();
                if (!TextUtils.isEmpty(lVideoUrl)) {
                    final Pattern pattern = Pattern.compile("\"file\": \"(.+?)\"");
                    final Matcher matcher = pattern.matcher(lVideoUrl);
                    matcher.find();
                    lVideoUrl = matcher.group(1);
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    public String getAuEngineVideo(String url) {
        String lVideoUrl = "";
        Document docdata;
        try {
            docdata = Jsoup.connect(url).userAgent(mUserAgent)
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements ele = docdata.select("script");
            Pattern p = Pattern.compile("http(s)?://([\\w-]+.)+[\\w-]+(/[\\w- ./?%&=])?", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);

            String innerHtml = "";
            String videoUrl = "";
            for (int i = 0; i < ele.size(); i++) {
                innerHtml = ele.get(i).html();
                if (!innerHtml.contains("file"))
                    continue;
                Matcher m = p.matcher(innerHtml);
                while (m.find()) {
                    videoUrl = m.group();
                    if (videoUrl.contains(".mp4")) {
                        WriteLog.appendLog("" + " matcher found url " + m.group());
                        lVideoUrl = videoUrl;
                    } else {
                        continue;
                    }
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    public String getMP4UploadVideo(String url) {
        String lVideoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(mUserAgent)
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = docdata.select("script + script");
            if (eles != null && !eles.isEmpty()) {
                lVideoUrl = eles.get(eles.size() - 2).html();
                if (!TextUtils.isEmpty(lVideoUrl)) {
                    final Pattern pattern = Pattern.compile("'file': '(.+?)'");
                    final Matcher matcher = pattern.matcher(lVideoUrl);
                    matcher.find();
                    lVideoUrl = matcher.group(1);
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    public String getSafeUploadVideo(String url) {
        //http://www.safeupload.org/getembed/f93fb4096e0875979215c0307dd53ff5
        String lVideoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(mUserAgent)
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = docdata.select("body").select("div + script");
            if (eles != null && !eles.isEmpty()) {
                lVideoUrl = eles.get(eles.size() - 2).html();
                if (!TextUtils.isEmpty(lVideoUrl)) {
                    final Pattern pattern = Pattern.compile("'file': '(.+?)'");
                    final Matcher matcher = pattern.matcher(lVideoUrl);
                    matcher.find();
                    lVideoUrl = matcher.group(1);
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    public String getEstreamToVideo(String url) {
        String lVideoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(mUserAgent)
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = null;
            if (!url.contains("embed")) {
                eles = docdata.select("div[class=leftcol]").select("iframe");
                if (eles != null && !eles.isEmpty()) {
                    url = eles.get(0).attr("src");
                    return getMP4UploadComVideo(url);
                }
            } else {
                eles = docdata.select("video").select("source");
                for (Element element : eles) {
                    if (element.hasAttr("type") && element.attr("type").startsWith("video")) {
                        return element.attr("abs:src");
                    }
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    public String getStreamangoVideo(String url) {
        String lVideoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(mUserAgent)
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = null;
            if (!url.contains("embed")) {
                eles = docdata.select("div[class=leftcol]").select("iframe");
                if (eles != null && !eles.isEmpty()) {
                    url = eles.get(0).attr("src");
                    return getMP4UploadComVideo(url);
                }
            } else {
                eles = docdata.select("div[class=videocontainer] + script");
                Pattern p = Pattern.compile("http(s)?://([\\w-]+.)+[\\w-]+(/[\\w- ./?%&=])?", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);

                String innerHtml = "";
                String videoUrl = "";
                for (int i = 0; i < eles.size(); i++) {
                    innerHtml = eles.get(i).html();
                    String[] innerHtmlSplit = innerHtml.split("\n");
                    for (String s : innerHtmlSplit) {
                        //https://streamango.com/v/d/lnorrkmcdnacnqsa~1500704504~108.21.0.0~uJnSFqaq/720
                        if (!s.contains("video/mp4"))
                            continue;
                        s = s.replace("srces.push({type:\"video/mp4\",src:\"", "https:").trim();
                        lVideoUrl = s.substring(0, s.lastIndexOf("\""));
                        break;
                    }
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    private String getYourUploadVideo(String url) {
        if (url.contains(".cdn."))
            return url;
        String lVideoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(getUserAgent())
                    .referrer("https://www.yourupload.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = null;
            if (url.contains("embed")) {
                eles = docdata.select("video").select("source");
                for (Element element : eles) {
                    if (element.hasAttr("type") && element.attr("type").startsWith("video")) {
                        return element.attr("abs:src");
                    }
                }
            } else {
                eles = docdata.select("video").select("source");
                for (Element element : eles) {
                    if (element.hasAttr("type") && element.attr("type").startsWith("video")) {
                        return element.attr("abs:src");
                    }
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    public static boolean isValidSource(Parser lParser) {
        return true;
        //return !lParser.getIsPremium();
    }

    public Document load(String url) throws IOException {
        Request request = new Request.Builder().url(url)
                .build();
        Response response = client.newCall(request).execute();
        if (response.isSuccessful())
            return parseHtml(response.body().string(), response.request().url().toString());
        else {
            WriteLog.appendLog(name + ": load(" + url + ") didn't respond with a proper response " + response.code());
            return loadDocument(url);
        }
    }

    public Document load(String url, boolean userCookies) throws IOException {
        Request request = new Request.Builder().url(url)
                .header("User-Agent", getUserAgent())
                .addHeader("Referer", getServerUrl()).build();
        Response response = client.newCall(request).execute();
        if (response.isSuccessful())
            return parseHtml(response.body().string(), response.request().url().toString());
        else {
            WriteLog.appendLog(name + ":load(" + url + ") didn't respond with a proper response " + response.code());
            return loadDocument(url);
        }
    }

    public Document load(String url, String referral) throws IOException {
        Request request = new Request.Builder().url(url)
                .header("User-Agent", getUserAgent())
                .addHeader("Referer", referral).build();
        Response response = client.newCall(request).execute();
        if (response.isSuccessful())
            return parseHtml(response.body().string(), response.request().url().toString());
        else {
            WriteLog.appendLog(name + ":load(" + url + ") didn't respond with a proper response " + response.code());
            return loadDocument(url);
        }
    }

    public Document loadDocument(String pURL) throws IOException {
        Document docdata = Jsoup.connect(pURL).userAgent(TextUtils.isEmpty(mCustomUserAgent) ? mUserAgent : mCustomUserAgent).referrer("http://www.google.com").timeout(mParseTimeOut).get();
        return docdata;
    }

    public Document parseHtml(String body, String url) {
        return Jsoup.parse(body, url);
    }

    public Map<String, String> getCategories() {
        return Collections.EMPTY_MAP;
    }
} public final class PaletteTransformation implements Transformation {
    private static final PaletteTransformation INSTANCE = new PaletteTransformation();
    private static final Map<Bitmap, Palette> CACHE = new WeakHashMap<Bitmap, Palette>();

    /**
     * A {@link Target} that receives {@link Palette} information in its callback.
     *
     * @see Target
     */
    public static abstract class PaletteTarget implements Target {
        /**
         * Callback when an image has been successfully loaded.
         * Note: You must not recycle the bitmap.
         *
         * @param palette The extracted {@linkplain Palette}
         */
        protected abstract void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from, Palette palette);

        @Override
        public final void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
            final Palette palette = getPalette(bitmap);
            onBitmapLoaded(bitmap, from, palette);
        }
    }

    public static Palette getPalette(Bitmap bitmap) {
        return CACHE.get(bitmap);
    }

    /**
     * A {@link Callback} that receives {@link Palette} information in its callback.
     *
     * @see Callback
     */
    public static abstract class PaletteCallback implements Callback {
        private WeakReference<ImageView> mImageView;

        public PaletteCallback(@NonNull ImageView imageView) {
            mImageView = new WeakReference<ImageView>(imageView);
        }

        protected abstract void onSuccess(Palette palette);

        @Override
        public final void onSuccess() {
            if (getImageView() == null) {
                return;
            }
            final Bitmap bitmap = ((BitmapDrawable) getImageView().getDrawable()).getBitmap(); // Ew!
            final Palette palette = getPalette(bitmap);
            onSuccess(palette);
        }

        private ImageView getImageView() {
            return mImageView.get();
        }
    }

    /**
     * Obtains a {@link PaletteTransformation} to extract {@link Palette} information.
     *
     * @return A {@link PaletteTransformation}
     */
    public static PaletteTransformation instance() {
        return INSTANCE;
    }

    //# Transformation Contract
    @Override
    public final Bitmap transform(Bitmap source) {
        if (Constants.USE_RGB565) {
            Bitmap resultBitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.RGB_565);
            Canvas canvas = new Canvas(resultBitmap);
            Paint paint = new Paint();
            paint.setFilterBitmap(true);
            canvas.drawBitmap(source, 0, 0, paint);
            source.recycle();
            final Palette palette = Palette.generate(resultBitmap);
            CACHE.put(resultBitmap, palette);
            return resultBitmap;
        } else {
            final Palette palette = Palette.generate(source);
            CACHE.put(source, palette);
            return source;
        }
    }

    @Override
    public String key() {
        return ""; // Stable key for all requests. An unfortunate requirement.
    }

    private PaletteTransformation() {
    }
} public class AnimeUtils {

    public final static String mTag = AnimeUtils.class.getSimpleName();

    public synchronized static void saveAsync(Anime... pAnime) {
        if (pAnime == null || pAnime.length == 0)
            return;
        AsyncTask<Anime, Void, Void> task = new AsyncTask<Anime, Void, Void>() {

            @Override
            protected Void doInBackground(Anime... params) {
                Anime lAnime = null;
                for (int i = 0; i < params.length; i++) {
                    lAnime = params[i];
                    if (lAnime == null)
                        continue;
                    File pFileName = new File(
                            StorageUtils.getDataDirectory(),
                            FileUtils.getValidFileName(lAnime.getTitle())
                                    + "/"
                                    + "anime.json");
                    if (!pFileName.getParentFile().exists())
                        continue;
                    try {
                        Gson gson = new Gson();
                        FileWriter writer = new FileWriter(pFileName.getAbsolutePath());
                        // convert java object to JSON format,
                        // and returned as JSON formatted string
                        String json = gson.toJson(lAnime);
                        WriteLog.appendLog(mTag, "saveAsync called" + pFileName.getAbsolutePath() + "\n" + json);
                        writer.write(json);
                        writer.close();
                    } catch (IOException e) {
                        WriteLog.appendLog(Log.getStackTraceString(e));
                        e.printStackTrace();
                    }
                }
                return null;
            }

        };
        AsyncTaskUtils.executeAsyncTask(task, pAnime);
    }

    public static void saveAsync(final Context context, Anime... pAnime) {
        if (pAnime == null || pAnime.length == 0)
            return;
        AsyncTask<Anime, Void, Void> task = new AsyncTask<Anime, Void, Void>() {

            @Override
            protected Void doInBackground(Anime... params) {
                Anime lAnime = null;
                for (int i = 0; i < params.length; i++) {
                    lAnime = params[i];
                    if (lAnime == null)
                        continue;
                    File lFileName = new File(
                            StorageUtils.getDataDirectory(),
                            FileUtils.getValidFileName(lAnime.getTitle())
                                    + "/"
                                    + "anime.json");
                    if (!lFileName.getParentFile().exists())
                        continue;
                    try {
                        Gson gson = new Gson();
                        FileWriter writer = new FileWriter(lFileName.getAbsolutePath());
                        // convert java object to JSON format,
                        // and returned as JSON formatted string
                        String json = gson.toJson(lAnime);
                        writer.write(json);
                        writer.close();
                        /**
                         if (DropboxManager.getInstance(context).isSyncLibraryEnabled())
                         {
                         String lAnimeFile = FileUtils.getValidFileName(lAnime.getTitle())
                         + "/"
                         + "manga.json";
                         DropboxManager.getInstance(context).storeFile(lAnimeFile, lFileName);
                         }
                         **/
                    } catch (IOException e) {
                        WriteLog.appendLog(Log.getStackTraceString(e));
                        e.printStackTrace();
                    }
                }
                return null;
            }

        };
        AsyncTaskUtils.executeAsyncTask(task, pAnime);
    }

    public static void viewAnime(Activity activity, String animeUrl) {
        viewAnime(activity, animeUrl, null, null);
    }

    public static void viewAnime(@NonNull Activity activity, @NonNull Anime anime, @Nullable View transitionView, @Nullable String transitionName) {
        viewAnime(activity, anime.getUrl(), transitionView, transitionName);
    }

    public static void viewAnime(Context context, AppCompatImageView imageView, Anime anime) {
        if (context == null) {
            WriteLog.appendLog("viewAnime called with null context");
            return;
        }
        if (anime == null) {
            WriteLog.appendLog("viewAnime called with null data");
            return;
        }
        String transitionName = context.getString(R.string.transition_anime_cover);
        Intent lIntent = new Intent();
        lIntent.setClass(context, AnimeDetailsActivity.class);
        lIntent.putExtra(Constants.ANIME_ID, anime.getId());
        lIntent.putExtra(Constants.ANIME_URL, anime.getUrl());
        //TransitionManager.setTransitionName(imageView, transitionName);
        ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation((AppCompatActivity) context, Pair.create((View) imageView, transitionName));
        ActivityCompat.startActivity(context, lIntent, options.toBundle());
        if (context instanceof AppCompatActivity) {
            ((AppCompatActivity) context).overridePendingTransition(0, 0);
        }
    }

    public static void viewAnime(@NonNull Activity activity, @NonNull String animeUrl, @Nullable View transitionView, @Nullable String transitionName) {
        Intent lIntent = new Intent(activity, AnimeDetailsActivity.class);
        lIntent.putExtra(Constants.ANIME_ID, animeUrl.hashCode());
        lIntent.putExtra(Constants.ANIME_URL, animeUrl);
        if (transitionView != null && !TextUtils.isEmpty(transitionName)) {
            ActivityOptionsCompat options =
                    ActivityOptionsCompat.makeSceneTransitionAnimation(activity,
                            transitionView,   // The view which starts the transition
                            transitionName    // The transitionName of the view we’re transitioning to
                    );
            ActivityCompat.startActivity(activity, lIntent, options.toBundle());
        } else
            ActivityCompat.startActivity(activity, lIntent, null);
    }
} public class Constants {

    public static final int ANIME_COMPLETED = 1;

    public static class RequestCodes {
        public static final int LOGIN = 71245;
    }

    public static class ProfileCodes {
        public static final int PROFILE_ADD_ACCOUNT = 13200;
        public static final int PROFILE_VIEW = 13202;
        public static final int PROFILE_LOGOUT = 13203;
    }

    public static class Intents {
        public static final String ANIME_UPDATED = BuildConfig.APPLICATION_ID + ".ANIME_UPDATED";
        public static final String EPISODE_PARSED = BuildConfig.APPLICATION_ID + ".EPISODE_PARSED";
        public static final String EPISODE_DOWNLOAD = BuildConfig.APPLICATION_ID + ".EPISODE_DOWNLOAD";
    }

    public static class DownloadIntents {

        public static final String TYPE = "type";
        public static final String SUBTYPE = "subtype";
        public static final String PROCESS_SPEED = "process_speed";
        public static final String PROCESS_PROGRESS = "process_progress";
        public static final String URL = "url";
        public static final String TITLE = "mTitle";
        public static final String PATH = "path";
        public static final String FILENAME = "filename";
        public static final String ERROR_CODE = "error_code";
        public static final String ERROR_INFO = "error_info";
        public static final String IS_PAUSED = "is_paused";

        public class Types {

            public static final int PROCESS = 0;
            public static final int COMPLETE = 1;

            public static final int START = 2;
            public static final int PAUSE = 3;
            public static final int DELETE = 4;
            public static final int CONTINUE = 5;
            public static final int ADD = 6;
            public static final int STOP = 7;
            public static final int ERROR = 9;

            public static final int PAUSE_ALL = 10;
            public static final int DELETE_ALL = 11;
            public static final int CONTINUE_ALL = 12;
            public static final int COMPLETE_ALL = 13;
        }

        public class SubTypes {
            public static final int DOWNLOADING = 0;
            public static final int COMPLETED = 1;
            public static final int QUEUED = 2;
            public static final int PAUSED = 3;
        }
    }

    public static final int HOME_SCREEN_CATALOG = 0;
    public static final int HOME_SCREEN_LIBRARY = 1;
    public static final int HOME_SCREEN_FAVORITES = 2;
    public static final int HOME_SCREEN_HISTORY = 3;

    // App Related
    public static final String KEY_USER_AGREEMENT = "user_agreement";
    public static final String KEY_SOURCE_FILTERS = "source_filter";
    public static final String KEY_ANIME_SOURCE = "anime_source";
    public static final String KEY_ANIME_SOURCE_URL = "anime_source_url";
    public static final String KEY_ANIME_SOURCE_LANG = "anime_source_lang";
    public static final String KEY_HOME_SCREEN_TYPE = "home_screen_type";
    public static final String KEY_SMART_HOME_SCREEN = "smart_home_screen";

    public static final String KEY_SYS_UNLOCK_CODE = "sys_unlock_code";
    public static final String KEY_SEARCH_FILTER_BY_LANG = "sys_search_filter_by_lang";

    public static final String USE_DEFAULT_ANIME_SOURCE = "use_default_anime_source";
    public static final int DEFAULT_ANIME_SOURCE = 0;

    public static final int DISPLAY_TYPE_GRID = 0;
    public static final int DISPLAY_TYPE_LIST_NO_IMAGES = 1;
    public static final int DISPLAY_TYPE_LIST = 2;
    public static final int DISPLAY_TYPE_LIST_LARGE = 3;
    public static final String KEY_GRID_COLUMN_COUNT = "grid_column_count";
    public static final String KEY_LIST_COLUMN_COUNT = "list_column_count";
    public static final String KEY_SHOW_CATALOG_AS_GRID = "catalog_as_grid";
    public static final String KEY_SHOW_FAVORITES_AS_GRID = "favorites_as_grid";

    public static final String KEY_USE_SERVER_DATABASE = "use_server_database";
    public static final String KEY_PARSE_SERVER = "parse_server";

    public static final String LOAD_TYPE = "load_type";
    public static final String GENRE_TYPE = "genre_type";
    public static final String DIRECTORY_URL = "directory_url";

    public static final String KEY_EPISODE_DEFAULT_SORT = "episode_default_sort";
    public static final String KEY_EPISODE_HISTORY = "episode_history";
    public static final String KEY_EPISODE_VIEWED_COLOR = "episode_viewed_color";
    public static final String KEY_EPISODE_UNVIEWED_COLOR = "episode_unviewed_color";
    public static final String KEY_EPISODE_LAST_VIEWED_COLOR = "episode_last_viewed_color";
    public static final String KEY_EPISODE_SORT_BEFORE_DOWNLOAD = "episode_sort_before_download";
    public static final String KEY_EPISODE_SHOW_WARNING = "episode_show_warning";

    // Library Specific
    public static final String KEY_LIBRARY_DISPLAY_TYPE = "library_display_type";
    public static final String KEY_LIBRARY_FORCE2SD = "library_data_extsd";
    public static final String KEY_LIBRARY_PATH = "library_path";
    public static final String KEY_LIBRARY_SHOW_COVERS = "library_show_covers";
    public static final String KEY_LIBRARY_SHOW_DATA = "library_show_data";
    public static final String KEY_LIBRARY_SHOW_BACKUPS = "library_show_backups";

    // Favorites Specific
    public static final String KEY_FAVORITES_SORT = "favorites_sort";
    public static final String KEY_FAVORITES_DISPLAY_TYPE = "favorites_display_type";
    public static final String KEY_FAVORITES_UPDATE_NOTIFICATION = "favorites_update_notification";

    public static final String KEY_DOWNLOAD_WIFI_ONLY = "sys_wifi_download";

    public static boolean USE_RGB565 = false;
    public static final String KEY_LOADER_TYPE = "loader_type";
    public static final String KEY_LOADER_IMAGE_QUALITY_HIGH = "loader_image_quality_high";

    public static final String ANIME_ID = "anime_id";
    public static final String ANIME_TITLE = "anime_title";
    public static final String ANIME_URL = "anime_url";
    public static final String ANIME_PATH = "anime_path";
    public static final String EPISODE_ID = "episode_id";
    public static final String EPISODE_TITLE = "episode_title";
    public static final String EPISODE_URL = "episode_url";
    public static final String EPISODE_PATH = "episode_path";
    public static final String EPISODE_VIDEO_URL = "episode_video_url";
    public static final String EPISODE_POSITION = "episode_position";
    public static final String EPISODE_START_POSITION = "episode_startPosition";

    public static final String ANIME_PREV_PATH_LIST = "anime_prev_path_list";

    public static final int UPDATE_NOTIFICATION_NONE = 0;
    public static final int UPDATE_NOTIFICATION_ONCE = 1;
    public static final int UPDATE_NOTIFICATION_PER_MANGA = 2;


    public static final int RibbonDrawable[] = {
            R.drawable.ribbon0,
            R.drawable.ribbon1,
            R.drawable.ribbon2,
            R.drawable.ribbon3,
            R.drawable.ribbon4,
            R.drawable.ribbon5,
            R.drawable.ribbon6,
            R.drawable.ribbon7,
            R.drawable.ribbon8,
            R.drawable.ribbon9,
            R.drawable.ribbon10,
            R.drawable.ribbon11,
            R.drawable.ribbon12,
            R.drawable.ribbon13,
            R.drawable.ribbon14

    };

    public static final int SELECT_SINGLE = 0;
    public static final int SELECT_MULTIPLE = 1;
    public static final int SELECT_ALL = 2;

    public static final String[] SELECTION_CHOICES = {
            "Single",
            "Multiple",
            "All"
    };
} public class DisplayUtils {
    public static float convertPixelsToDp(float px){
        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
        float dp = px / (metrics.densityDpi / 160f);
        return Math.round(dp);
    }

    public static float convertDpToPixel(float dp){
        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
        float px = dp * (metrics.densityDpi / 160f);
        return Math.round(px);
    }

    private int convertDpToPx(Context context, int dp){
        return Math.round(dp*(context.getResources().getDisplayMetrics().xdpi/DisplayMetrics.DENSITY_DEFAULT));

    }

    private int convertPxToDp(int px){
        return Math.round(px/(Resources.getSystem().getDisplayMetrics().xdpi/DisplayMetrics.DENSITY_DEFAULT));
    }

    public static int dpToPx(int dp) {
        return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
    }

    public static float dpFromPx(Context context,float px)
    {
        return px / context.getResources().getDisplayMetrics().density;
    }

    public static float pxFromDp(Context context, float dp)
    {
        return dp * context.getResources().getDisplayMetrics().density;
    }

    private static int screenWidth = 0;
    private static int screenHeight = 0;
    public static int getScreenHeight(Context c) {
        if (screenHeight == 0) {
            WindowManager wm = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE);
            Display display = wm.getDefaultDisplay();
            Point size = new Point();
            if (BuildUtils.isHoneycombOrLater()) {
                display.getSize(size);
                screenHeight = size.y;
            } else
                screenHeight = display.getHeight();
        }

        return screenHeight;
    }

    public static int getScreenWidth(Context c) {
        if (screenWidth == 0) {
            WindowManager wm = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE);
            Display display = wm.getDefaultDisplay();
            Point size = new Point();
            display.getSize(size);
            screenWidth = size.x;
        }

        return screenWidth;
    }

    @SuppressWarnings("deprecation")
    /**
     * Returns the screen/display size
     *
     */
    public static Point getDisplaySize(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        int width = display.getWidth();
        int height = display.getHeight();
        return new Point(width, height);
    }
} public class ImageLoaderManager {

    private static final String mTag = ImageLoaderManager.class.getSimpleName();

    private static ImageLoaderManager mInstance;
    private Context mContext;

    private Handler mHandler;

    private SharedPreferences mPrefs;

    private int mTryCount = 0;
    private String mLastURL = "";

    private boolean mBlurryImageMessage = false;
    private Picasso mPicasso;

    public static ImageLoaderManager getInstance(Context ctx) {
        /**
         * use the application context as suggested by CommonsWare. this will
         * ensure that you dont accidentally leak an Activitys context (see this
         * article for more information:
         * http://developer.android.com/resources/articles
         * /avoiding-memory-leaks.html)
         */
        if (mInstance == null) {
            mInstance = new ImageLoaderManager(ctx.getApplicationContext());
        }
        return mInstance;
    }

    public static ImageLoaderManager getInstance() {
        return mInstance;
    }

    private ImageLoaderManager(Context pContext) {
        mContext = pContext;
        mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
        mHandler = new Handler();
        initPicasso();
    }

    private void initPicasso() {
        mPicasso = new Picasso.Builder(mContext)
                .downloader(new UrlConnectionDownloader(mContext))
                .loggingEnabled(WriteLog.IsEnabled())
                .build();
    }

    public void loadImage(String url, ImageView imageView) {
        loadImage(url, imageView, null, null);
    }

    public void loadImage(String url, ImageView imageView, @DrawableRes int placeHolderRes, PaletteTransformation.PaletteCallback callback) {
        if (TextUtils.isEmpty(url)) {
            WriteLog.appendLog(mTag, "loadImage invalid url supplied " + url);
            return;
        } else if (imageView == null) {
            WriteLog.appendLog(mTag, "loadImage invalid ImageView " + url);
            return;
        }
        url = url.replace(" ", "%20");
        if (callback != null) {
            Picasso.with(mContext)
                    .load(url)
                    .transform(PaletteTransformation.instance())
                    .placeholder(placeHolderRes)
                    .error(android.R.drawable.ic_delete)
                    .into(imageView, callback);
        } else {
            Picasso.with(mContext)
                    .load(url)
                    .transform(PaletteTransformation.instance())
                    .placeholder(placeHolderRes)
                    .error(android.R.drawable.ic_delete)
                    .into(imageView);
        }
    }

    public void loadImage(final String url, final ImageView imageView, final PaletteTransformation.PaletteCallback callback, final AnimeRecyclerAdapter.ViewHolder viewHolder) {
        if (TextUtils.isEmpty(url)) {
            WriteLog.appendLog(mTag + ": loadImage invalid url supplied " + url);
            return;
        } else if (imageView == null) {
            WriteLog.appendLog(mTag + ": loadImage invalid ImageView " + url);
            return;
        }
        int loaderType = Integer.parseInt(mPrefs.getString(Constants.KEY_LOADER_TYPE, "0"));
        if (loaderType == 0) {
            Glide.with(mContext)
                    .load(url)
                    .asBitmap()
                    .into(new BitmapImageViewTarget(imageView) {
                        @Override
                        public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
                            super.onResourceReady(bitmap, anim);
                            if (viewHolder != null) {
                                Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() {
                                    @Override
                                    public void onGenerated(Palette palette) {
                                        int colorTemp = ThemeManager.getInstance().getBackgroundColor(mContext);
                                        if (viewHolder.mTitleView != null && palette != null) {
                                            viewHolder.mTitleView.setTextColor(ThemeManager.getInstance().getTextColor());
                                            if (palette.getVibrantSwatch() != null)
                                                colorTemp = palette.getVibrantSwatch().getRgb();
                                            else if (palette.getDarkMutedSwatch() != null)
                                                colorTemp = palette.getDarkMutedSwatch().getRgb();
                                        }
                                        Integer colorFrom = ThemeManager.getInstance().getBackgroundColor(mContext);
                                        Integer colorTo = colorTemp;
                                        viewHolder.mPaletteColor = colorTo;
                                        ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
                                        colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                                            @Override
                                            public void onAnimationUpdate(ValueAnimator animator) {
                                                viewHolder.mTitleView.setBackgroundColor((Integer) animator.getAnimatedValue());
                                            }

                                        });
                                        colorAnimation.start();
                                    }
                                });
                            }
                        }
                    });
        } else {
            ViewTreeObserver vto = imageView.getViewTreeObserver();
            if (vto == null || !vto.isAlive()) return;
            vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                public boolean onPreDraw() {
                    imageView.getViewTreeObserver().removeOnPreDrawListener(this);
                    final int height = imageView.getMeasuredHeight();
                    final int width = imageView.getMeasuredWidth();

                    // get the target image view width and height
                    final int heightDp = (int) DisplayUtils.dpFromPx(mContext, height);
                    final int widthDp = (int) DisplayUtils.dpFromPx(mContext, width);

                    if (mPrefs.getBoolean(Constants.KEY_LOADER_IMAGE_QUALITY_HIGH, false)) {
                        // download resized image
                        mPicasso
                                .load(url)
                                .resize(widthDp, heightDp)
                                .transform(PaletteTransformation.instance())
                                .error(android.R.drawable.ic_delete)
                                .into(imageView, callback);
                    } else {
                        // download resized image
                        mPicasso
                                .load(url)
                                .resize(widthDp, heightDp)
                                .config(Bitmap.Config.RGB_565)
                                .transform(PaletteTransformation.instance())
                                .error(android.R.drawable.ic_delete)
                                .into(imageView, callback);
                    }
                    return true;
                }
            });
        }
    }

    @Deprecated
    public void scrollListener(AbsListView gridView) {
    }

    @Deprecated
    public void onDestroy() {
    }
} public class GridRecyclerView extends FastScrollRecyclerView {

    private View emptyView;
    private AdapterDataObserver emptyObserver = new AdapterDataObserver() {


        @Override
        public void onChanged() {
            Adapter<?> adapter = getAdapter();
            if (adapter != null && emptyView != null) {
                if (adapter.getItemCount() == 0) {
                    emptyView.setVisibility(View.VISIBLE);
                    GridRecyclerView.this.setVisibility(View.GONE);
                } else {
                    emptyView.setVisibility(View.GONE);
                    GridRecyclerView.this.setVisibility(View.VISIBLE);
                }
            }

        }
    };

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

    public GridRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public GridRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void setLayoutManager(RecyclerView.LayoutManager layout) {
        if (!isInEditMode())
            if (layout instanceof GridLayoutManager) {
                super.setLayoutManager(layout);
            } else {
                throw new ClassCastException("You should only use a GridLayoutManager with GridRecyclerView.");
            }
    }

    @Override
    protected void attachLayoutAnimationParameters(View child, ViewGroup.LayoutParams params, int index, int count) {
        if (!isInEditMode())
            if (getAdapter() != null && getLayoutManager() instanceof GridLayoutManager) {

                GridLayoutAnimationController.AnimationParameters animationParams =
                        (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;

                if (animationParams == null) {
                    animationParams = new GridLayoutAnimationController.AnimationParameters();
                    params.layoutAnimationParameters = animationParams;
                }

                int columns = ((GridLayoutManager) getLayoutManager()).getSpanCount();

                animationParams.count = count;
                animationParams.index = index;
                animationParams.columnsCount = columns;
                animationParams.rowsCount = count / columns;

                final int invertedIndex = count - 1 - index;
                animationParams.column = columns - 1 - (invertedIndex % columns);
                animationParams.row = animationParams.rowsCount - 1 - invertedIndex / columns;

            } else {
                super.attachLayoutAnimationParameters(child, params, index, count);
            }
    }

    @Override
    public boolean isShown() {
        return super.isShown() && getVisibility() == VISIBLE;
    }

    @Override
    public void setAdapter(Adapter adapter) {
        if (getAdapter() != null)
            getAdapter().unregisterAdapterDataObserver(emptyObserver);
        super.setAdapter(adapter);

        if (adapter != null) {
            adapter.registerAdapterDataObserver(emptyObserver);
        }

        emptyObserver.onChanged();
    }

    public void setEmptyView(View emptyView) {
        this.emptyView = emptyView;
    }
} public class SquareImageView extends AppCompatImageView {
    public SquareImageView(Context context) {
        super(context);
    }

    public SquareImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SquareImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
            int width = MeasureSpec.getSize(widthMeasureSpec);
            int height = width;
            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
            }
            setMeasuredDimension(width, height);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
} public class ThemeManager {
    private static final String KEY_THEME = "theme";
    private static final String KEY_BG_THEME = "bg_theme";
    private static ThemeManager mInstance = null;

    private ThemeType currentTheme = ThemeType.Light;
    private boolean isLightBackground = true;

    public void setCurrentTheme(AppCompatActivity activity) {
        SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(activity);
        int themeType = Integer.parseInt(mPrefs.getString(KEY_THEME, String.valueOf(ThemeType.Blue.getValue())));
        boolean isLight = mPrefs.getBoolean(KEY_BG_THEME, false);
        setCurrentTheme(activity, themeType, isLight);
    }

    public enum ThemeType {
        Light(0),
        Dark(1),
        Amber(2),
        Blue(3),
        Blue_Grey(4),
        Brown(5),
        Cyan(6),
        Deep_Orange(7),
        Deep_Purple(8),
        Green(9),
        Indigo(10),
        Light_Blue(11),
        Light_Green(12),
        Lime(13),
        Orange(14),
        Pink(15),
        Purple(16),
        Red(17),
        Teal(18),
        Yellow(19);

        private int value;

        ThemeType(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }

    private int mPrimaryColors[] = {
            R.color.holo_blue_light,
            R.color.holo_blue_dark,
            R.color.material_amber_primary,
            R.color.material_blue_primary,
            R.color.material_blue_grey_primary,
            R.color.material_brown_primary,
            R.color.material_cyan_primary,
            R.color.material_deep_orange_primary,
            R.color.material_deep_purple_primary,
            R.color.material_green_primary,
            R.color.material_indigo_primary,
            R.color.material_light_blue_primary,
            R.color.material_light_green_primary,
            R.color.material_lime_primary,
            R.color.material_orange_primary,
            R.color.material_pink_primary,
            R.color.material_purple_primary,
            R.color.material_red_primary,
            R.color.material_teal_primary,
            R.color.material_yellow_primary
    };

    public int mPrimaryDarkColors[] = {
            R.color.holo_blue_light,
            R.color.holo_blue_dark,
            R.color.material_amber_dark,
            R.color.material_blue_dark,
            R.color.material_blue_grey_dark,
            R.color.material_brown_dark,
            R.color.material_cyan_dark,
            R.color.material_deep_orange_dark,
            R.color.material_deep_purple_dark,
            R.color.material_green_dark,
            R.color.material_indigo_dark,
            R.color.material_light_blue_dark,
            R.color.material_light_green_dark,
            R.color.material_lime_dark,
            R.color.material_orange_dark,
            R.color.material_pink_dark,
            R.color.material_purple_dark,
            R.color.material_red_dark,
            R.color.material_teal_dark,
            R.color.material_yellow_dark
    };

    private int mAccentColors[] = {
            R.color.holo_blue_light,
            R.color.holo_blue_dark,
            R.color.material_amber_accent,
            R.color.material_blue_accent,
            R.color.material_blue_grey_accent,
            R.color.material_brown_accent,
            R.color.material_cyan_accent,
            R.color.material_deep_orange_accent,
            R.color.material_deep_purple_accent,
            R.color.material_green_accent,
            R.color.material_indigo_accent,
            R.color.material_light_blue_accent,
            R.color.material_light_green_accent,
            R.color.material_lime_accent,
            R.color.material_orange_accent,
            R.color.material_pink_accent,
            R.color.material_purple_accent,
            R.color.material_red_accent,
            R.color.material_teal_accent,
            R.color.material_yellow_accent
    };

    private ThemeManager() {
    }

    public static ThemeManager getInstance(Context ctx) {
        /**
         * use the application context as suggested by CommonsWare. this will
         * ensure that you dont accidentally leak an Activitys context (see this
         * article for more information:
         * http://developer.android.com/resources/articles
         * /avoiding-memory-leaks.html)
         */
        if (mInstance == null) {
            mInstance = new ThemeManager();
        }
        return mInstance;
    }

    public static ThemeManager getInstance() {
        /**
         * use the application context as suggested by CommonsWare. this will
         * ensure that you dont accidentally leak an Activitys context (see this
         * article for more information:
         * http://developer.android.com/resources/articles
         * /avoiding-memory-leaks.html)
         */
        return mInstance;
    }

    public void setCurrentTheme(int theme) {
        switch (theme) {
            default:
                currentTheme = ThemeType.values()[theme];
                break;
        }
    }

    public void setCurrentTheme(Activity act, int theme, boolean isLight) {
        isLightBackground = isLight;
        if (!isLight) {
            switch (ThemeType.values()[theme]) {
                case Dark:
                    isLightBackground = false;
                    currentTheme = ThemeType.Dark;
                    act.setTheme(R.style.DarkTheme);
                    break;
                case Light:
                    currentTheme = ThemeType.Light;
                    act.setTheme(R.style.LightTheme);
                    break;
                case Amber:
                    currentTheme = ThemeType.Amber;
                    act.setTheme(R.style.AmberTheme);
                    break;
                case Blue:
                    currentTheme = ThemeType.Blue;
                    act.setTheme(R.style.BlueTheme);
                    break;
                case Blue_Grey:
                    currentTheme = ThemeType.Blue_Grey;
                    act.setTheme(R.style.BlueGreyTheme);
                    break;
                case Brown:
                    currentTheme = ThemeType.Brown;
                    act.setTheme(R.style.BrownTheme);
                    break;
                case Cyan:
                    currentTheme = ThemeType.Cyan;
                    act.setTheme(R.style.CyanTheme);
                    break;
                case Deep_Orange:
                    currentTheme = ThemeType.Deep_Orange;
                    act.setTheme(R.style.DeepOrangeTheme);
                    break;
                case Deep_Purple:
                    currentTheme = ThemeType.Deep_Purple;
                    act.setTheme(R.style.DeepPurpleTheme);
                    break;
                case Green:
                    currentTheme = ThemeType.Green;
                    act.setTheme(R.style.GreenTheme);
                    break;
                case Indigo:
                    currentTheme = ThemeType.Indigo;
                    act.setTheme(R.style.IndigoTheme);
                    break;
                case Light_Blue:
                    currentTheme = ThemeType.Light_Blue;
                    act.setTheme(R.style.LightBlueTheme);
                    break;
                case Light_Green:
                    currentTheme = ThemeType.Light_Green;
                    act.setTheme(R.style.LightGreenTheme);
                    break;
                case Lime:
                    currentTheme = ThemeType.Lime;
                    act.setTheme(R.style.LimeTheme);
                    break;
                case Orange:
                    currentTheme = ThemeType.Orange;
                    act.setTheme(R.style.OrangeTheme);
                    break;
                case Pink:
                    currentTheme = ThemeType.Pink;
                    act.setTheme(R.style.PinkTheme);
                    break;
                case Purple:
                    currentTheme = ThemeType.Purple;
                    act.setTheme(R.style.PurpleTheme);
                    break;
                case Red:
                    currentTheme = ThemeType.Red;
                    act.setTheme(R.style.RedTheme);
                    break;
                case Teal:
                    currentTheme = ThemeType.Teal;
                    act.setTheme(R.style.TealTheme);
                    break;
                case Yellow:
                    currentTheme = ThemeType.Yellow;
                    act.setTheme(R.style.YellowTheme);
                    break;
            }
        } else {
            switch (ThemeType.values()[theme]) {
                case Dark:
                    isLightBackground = false;
                    currentTheme = ThemeType.Dark;
                    act.setTheme(R.style.DarkTheme);
                    break;
                case Light:
                    currentTheme = ThemeType.Light;
                    act.setTheme(R.style.LightTheme);
                    break;
                case Amber:
                    currentTheme = ThemeType.Amber;
                    act.setTheme(R.style.AmberThemeL);
                    break;
                case Blue:
                    currentTheme = ThemeType.Blue;
                    act.setTheme(R.style.BlueThemeL);
                    break;
                case Blue_Grey:
                    currentTheme = ThemeType.Blue_Grey;
                    act.setTheme(R.style.BlueGreyThemeL);
                    break;
                case Brown:
                    currentTheme = ThemeType.Brown;
                    act.setTheme(R.style.BrownThemeL);
                    break;
                case Cyan:
                    currentTheme = ThemeType.Cyan;
                    act.setTheme(R.style.CyanThemeL);
                    break;
                case Deep_Orange:
                    currentTheme = ThemeType.Deep_Orange;
                    act.setTheme(R.style.DeepOrangeThemeL);
                    break;
                case Deep_Purple:
                    currentTheme = ThemeType.Deep_Purple;
                    act.setTheme(R.style.DeepPurpleThemeL);
                    break;
                case Green:
                    currentTheme = ThemeType.Green;
                    act.setTheme(R.style.GreenThemeL);
                    break;
                case Indigo:
                    currentTheme = ThemeType.Indigo;
                    act.setTheme(R.style.IndigoThemeL);
                    break;
                case Light_Blue:
                    currentTheme = ThemeType.Light_Blue;
                    act.setTheme(R.style.LightBlueThemeL);
                    break;
                case Light_Green:
                    currentTheme = ThemeType.Light_Green;
                    act.setTheme(R.style.LightGreenThemeL);
                    break;
                case Lime:
                    currentTheme = ThemeType.Lime;
                    act.setTheme(R.style.LimeThemeL);
                    break;
                case Orange:
                    currentTheme = ThemeType.Orange;
                    act.setTheme(R.style.OrangeThemeL);
                    break;
                case Pink:
                    currentTheme = ThemeType.Pink;
                    act.setTheme(R.style.PinkThemeL);
                    break;
                case Purple:
                    currentTheme = ThemeType.Purple;
                    act.setTheme(R.style.PurpleThemeL);
                    break;
                case Red:
                    currentTheme = ThemeType.Red;
                    act.setTheme(R.style.RedThemeL);
                    break;
                case Teal:
                    currentTheme = ThemeType.Teal;
                    act.setTheme(R.style.TealThemeL);
                    break;
                case Yellow:
                    currentTheme = ThemeType.Yellow;
                    act.setTheme(R.style.YellowThemeL);
                    break;
            }
        }
    }

    public ThemeType getCurrentTheme() {
        return currentTheme;
    }

    public boolean isLightBackground() {
        return (currentTheme == ThemeType.Light) || isLightBackground && (currentTheme != ThemeType.Dark);
    }

    public int getTextColor() {
        switch (currentTheme) {
            case Light:
                return Color.BLACK;
            case Dark:
                return Color.WHITE;
            default:
                if (!isLightBackground)
                    return Color.WHITE;
                else
                    return Color.BLACK;
        }
    }

    public int getInvertedTextColor() {
        switch (currentTheme) {
            case Dark:
                return Color.BLACK;
            case Light:
                return Color.WHITE;
            default:
                if (isLightBackground)
                    return Color.WHITE;
                else
                    return Color.BLACK;
        }
    }

    public int getPrimaryTextColor(Context context) {
        switch (currentTheme) {
            case Light:
                return Color.BLACK;
            case Dark:
                return Color.WHITE;
            default:
                return context.getResources().getColor(mPrimaryColors[currentTheme.value]);
        }
    }

    public boolean isValidTextColor(int color) {
        switch (currentTheme) {
            case Light:
                return (color != Color.WHITE);
            case Dark:
                return (color != Color.BLACK);
            default:
                if (!isLightBackground)
                    return (color != Color.BLACK);
                else
                    return (color != Color.WHITE);
        }
    }

    public int getBackgroundColor(Context context) {
        switch (currentTheme) {
            case Light:
                return context.getResources().getColor(R.color.background_holo_light);
            case Dark:
                return context.getResources().getColor(R.color.background_holo_dark);
            default:
                if (!isLightBackground)
                    return context.getResources().getColor(R.color.background_floating_material_dark);
                else
                    return context.getResources().getColor(R.color.background_floating_material_light);
        }
    }

    public int getBackgroundColorResId() {
        switch (currentTheme) {
            case Light:
                return R.color.background_holo_light;
            case Dark:
                return R.color.background_holo_dark;
            default:
                return android.R.color.transparent;
        }
    }

    public int getIndicatorColor(Context context) {
        switch (currentTheme) {
            case Light:
                return context.getResources().getColor(R.color.holo_blue_light);
            case Dark:
                return context.getResources().getColor(R.color.holo_blue_dark);
            default:
                return context.getResources().getColor(mPrimaryDarkColors[currentTheme.value]);
        }
    }

    public int getAccentColorResId() {
        switch (currentTheme) {
            case Light:
                return R.color.background_holo_light;
            case Dark:
                return R.color.background_holo_dark;
            default:
                return mAccentColors[currentTheme.value];
        }
    }

    public int getAccentColor(Context context) {
        switch (currentTheme) {
            case Light:
                return context.getResources().getColor(R.color.holo_blue_light);
            case Dark:
                return context.getResources().getColor(R.color.holo_blue_dark);
            default:
                return context.getResources().getColor(mAccentColors[currentTheme.value]);
        }
    }

    public int getPrimaryDarkColorResId() {
        switch (currentTheme) {
            case Light:
                return R.color.background_holo_light;
            case Dark:
                return R.color.background_holo_dark;
            default:
                return mPrimaryDarkColors[currentTheme.value];
        }
    }

    public int getPrimaryDarkColor(Context context) {
        switch (currentTheme) {
            case Light:
                return context.getResources().getColor(R.color.holo_blue_light);
            case Dark:
                return context.getResources().getColor(R.color.holo_blue_dark);
            default:
                return context.getResources().getColor(mPrimaryDarkColors[currentTheme.value]);
        }
    }

    public int getPrimaryColorResId() {
        switch (currentTheme) {
            case Light:
                return R.color.primary_material_light;
            case Dark:
                return R.color.primary_dark_material_dark;
            default:
                return mPrimaryColors[currentTheme.value];
        }
    }

    public int getPrimaryColor(Context context) {
        switch (currentTheme) {
            case Light:
                return context.getResources().getColor(R.color.primary_material_light);
            case Dark:
                return context.getResources().getColor(R.color.primary_material_dark);
            default:
                return context.getResources().getColor(mPrimaryColors[currentTheme.value]);
        }
    }

}
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Typeface;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.support.v7.graphics.Palette;
import android.support.v7.widget.AppCompatCheckBox;
import android.support.v7.widget.AppCompatImageView;
import android.support.v7.widget.AppCompatTextView;
import android.support.v7.widget.CardView;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import com.bumptech.glide.Glide;
import com.readystatesoftware.viewbadger.BadgeView;
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
import com.squareup.picasso.Picasso;
import com.taskdesignsinc.android.myanimeviewer.MAVApplication;
import com.taskdesignsinc.android.myanimeviewer.R;
import com.taskdesignsinc.android.myanimeviewer.adapter.base.FlexibleAdapter;
import com.taskdesignsinc.android.myanimeviewer.adapter.base.OnItemClickListener;
import com.taskdesignsinc.android.myanimeviewer.model.Anime;
import com.taskdesignsinc.android.myanimeviewer.model.FavoriteRecord;
import com.taskdesignsinc.android.myanimeviewer.model.FavoriteTag;
import com.taskdesignsinc.android.myanimeviewer.parser.Parser;
import com.taskdesignsinc.android.myanimeviewer.picasso.PaletteTransformation;
import com.taskdesignsinc.android.myanimeviewer.model.helper.AnimeUtils;
import com.taskdesignsinc.android.myanimeviewer.util.Constants;
import com.taskdesignsinc.android.myanimeviewer.util.DisplayUtils;
import com.taskdesignsinc.android.myanimeviewer.util.ImageLoaderManager;
import com.taskdesignsinc.android.myanimeviewer.view.GridRecyclerView;
import com.taskdesignsinc.android.myanimeviewer.view.SquareImageView;
import com.taskdesignsinc.android.thememanager.ThemeManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
                FavoriteRecord lRecord = null;
                for (Anime anime : animeList) {
                    if (mCatalogType != -1) {
                        //anime.setCatalogPosition(AnimeHelper.getInstance(mContext).getAnimePositionByUrl(mCatalogType, anime.getUrl()));
                    }
                    if (anime.getTagId() == -1) {
                        lRecord = MAVApplication.getInstance().getRepository().getFavoriteByAnimeUrl(anime.getUrl());
                        if (lRecord != null) {
                            anime.setTagId(lRecord.getTagId());
                        }
                    }
                    if (anime.getTagId() != -1 && !mRibbonIDs.containsKey(anime.getTagId())) {
                        lTag = MAVApplication.getInstance().getRepository().getFavoriteTag(anime.getTagId());
                        if (lTag != null)
                            mRibbonIDs.put(lTag.getId(), lTag.getRibbonID());
                    }
                }
            }
        }
    }

    public int getOriginalItemCount() {
        return mItems.size();
    }

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

    public Anime getItem(int position) {
        if (position >= mResults.size() || position < 0)
            return null;
        return mResults.get(position);
    }

    public Anime getItem(String url) {
        for (Anime lAnime : mResults) {
            if (lAnime.getUrl().equals(url))
                return lAnime;
        }
        return null;
    }

    public int getItemPosition(String url) {
        for (int i = 0; i < mResults.size(); i++) {
            if (mResults.get(i).getUrl().equals(url))
                return i;
        }
        return -1;
    }

    @Override
    public void setMode(int mode) {
        super.setMode(mode);
        notifyDataSetChanged();
        if (mode == MODE_SINGLE) mLastItemInActionMode = true;
    }

    @Override
    public void selectAll() {
        mSelectAll = true;
        super.selectAll();
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //Log.d(TAG, "onCreateViewHolder for viewType " + viewType);
        if (mInflater == null) {
            mInflater = LayoutInflater.from(parent.getContext());
        }
        View v = null;
        ViewHolder viewHolder = null;
        switch (mDisplayType) {
            case 1:
            case 2:
                v = mInflater.inflate(R.layout.anime_list_card, parent, false);
                viewHolder = new ViewHolder(v, this);
                if (mShowBadge) {
                    viewHolder.mTopLeftBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.alc_badge_view_left));
                    viewHolder.mTopLeftBadgeView.setBadgePosition(BadgeView.POSITION_TOP_LEFT);
                    viewHolder.mTopRightBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.alc_badge_view_right));
                    viewHolder.mTopRightBadgeView.setBadgePosition(BadgeView.POSITION_TOP_RIGHT);
                }
                break;
            case 3:
                v = mInflater.inflate(R.layout.anime_list_card2, parent, false);
                viewHolder = new ViewHolder(v, this);
                if (mShowBadge) {
                    viewHolder.mTopLeftBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.alc_badge_view_left));
                    viewHolder.mTopLeftBadgeView.setBadgePosition(BadgeView.POSITION_TOP_LEFT);
                    viewHolder.mTopRightBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.alc_badge_view_right));
                    viewHolder.mTopRightBadgeView.setBadgePosition(BadgeView.POSITION_TOP_RIGHT);
                }
                break;
            default:
                if (mLoaderType < 4)
                    v = mInflater.inflate(R.layout.anime_grid_item, parent, false);
                viewHolder = new ViewHolder(v, this);
                if (mShowBadge) {
                    viewHolder.mTopLeftBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.agi_badge_view_left));
                    viewHolder.mTopLeftBadgeView.setBadgePosition(BadgeView.POSITION_TOP_LEFT);
                    viewHolder.mTopRightBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.agi_badge_view_right));
                    viewHolder.mTopRightBadgeView.setBadgePosition(BadgeView.POSITION_TOP_RIGHT);
                }
                break;
        }
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
        final Anime anime = mResults.get(position);

        //When user scrolls this bind the correct selection status
        viewHolder.bindAnime(position, anime);
        ViewCompat.setActivated(viewHolder.itemView, isSelected(position));
        //viewHolder.itemView.setActivated(isSelected(position));
        if (!TextUtils.isEmpty(anime.getCover()) && !(mDisplayType == 1)) {
            viewHolder.mImageView.setVisibility(View.VISIBLE);
            ImageLoaderManager.getInstance().loadImage(anime.getCover(), viewHolder.mImageView, new PaletteTransformation.PaletteCallback(viewHolder.mImageView) {