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 class LibraryRecyclerAdapter extends FlexibleAdapter<LibraryRecyclerAdapter.ViewHolder, File> implements Filterable {

    private static final String TAG = LibraryRecyclerAdapter.class.getSimpleName();

    public void sort(Comparator<File> comparator) {
        Collections.sort(mItems, comparator);
        notifyDataSetChanged();
    }

    public void changeViewedStatus(String path, boolean isViewed) {
        if (mViewedMap != null)
            mViewedMap.put(path, isViewed);
        //mLastEpisodeViewed = MAVApplication.getInstance().getRepository().getLastViewedByParent(path);
    }

    public void setAnime(Anime anime, HashMap<String, Episode> episodes) {
        mAnime = anime;
        mEpisodes = episodes;
    }

    @Override
    public Filter getFilter() {
        if (mFilter == null)
            mFilter = new FileFilter();

        return mFilter;
    }

    @Override
    public void removeItem(int position) {
        mResults.remove(position);
        notifyItemRemoved(position);
    }

    private Context mContext;
    List<File> mResults;
    private LayoutInflater mInflater;
    private OnItemClickListener mClickListener;
    //Selection fields
    private boolean
            mLastItemInActionMode = false,
            mSelectAll = false;

    SharedPreferences mPrefs;

    private FileFilter mFilter;
    private Comparator<? super File> mComparator;
    private String mLastEpisodeViewed;
    private HashMap<String, Boolean> mViewedMap;
    private int mBackStackLevel = 0;

    Anime mAnime = null;
    HashMap<String, Episode> mEpisodes = null;
    private SparseIntArray mRibbonIDs = null;
    private HashMap<String, Anime> mAnimeMap = null;

    private boolean mShowBadge = false;
    int mDisplayType = 0;
    private int mSecondaryType = 0;

    public static final int NONE = -1;
    public static final int GENRE = 0;
    public static final int AUTHOR = 1;
    public static final int LATEST_EPISODE = 2;
    public static final int SOURCE = 3;
    public static final int SOURCE_AND_LANG = 4;

    public LibraryRecyclerAdapter(Context context, int displayType, OnItemClickListener listener) {
        this.mContext = context;
        this.mDisplayType = displayType;
        this.mClickListener = listener;
        this.mItems = new ArrayList<File>();
        this.mResults = new ArrayList<File>();
        this.mViewedMap = new HashMap<String, Boolean>();
        mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
        mViewedColor = mPrefs.getInt(Constants.KEY_EPISODE_VIEWED_COLOR, Color.GRAY);
        mUnreadColor = mPrefs.getInt(Constants.KEY_EPISODE_UNVIEWED_COLOR, ThemeManager.getInstance(context).getTextColor());
        if (!ThemeManager.getInstance().isValidTextColor(mUnreadColor))
            mUnreadColor = ThemeManager.getInstance(context).getTextColor();
        mLastViewedColor = mPrefs.getInt(Constants.KEY_EPISODE_LAST_VIEWED_COLOR, Color.RED);
    }

    public LibraryRecyclerAdapter(List<File> mangaList, Context context, OnItemClickListener listener) {
        this.mContext = context;
        this.mClickListener = listener;
        mResults = new ArrayList<File>();
        mViewedMap = new HashMap<String, Boolean>();
        this.mItems = mangaList;
        mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
        mViewedColor = mPrefs.getInt(Constants.KEY_EPISODE_VIEWED_COLOR, Color.GRAY);
        mUnreadColor = mPrefs.getInt(Constants.KEY_EPISODE_UNVIEWED_COLOR, ThemeManager.getInstance(context).getTextColor());
        if (!ThemeManager.getInstance().isValidTextColor(mUnreadColor))
            mUnreadColor = ThemeManager.getInstance(context).getTextColor();
        mLastViewedColor = mPrefs.getInt(Constants.KEY_EPISODE_LAST_VIEWED_COLOR, Color.RED);
    }

    public void updateDataSet(String param) {
        //Fill mItems with your custom list
        //this.mItems = data;
    }

    public void clearData() {
        int size = mResults.size();
        if (size > 0) {
            mItems.clear();
            mResults.clear();
            this.notifyDataSetChanged();
        }
    }

    public void addFileList(List<File> data, int backStackLevel) {
        mBackStackLevel = backStackLevel;
        mItems.clear();
        mResults.clear();
        mViewedMap.clear();
        if (data != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                mItems.addAll(data);
            } else {
                for (File obj : data) {
                    mItems.add(obj);
                }
            }
            if (mResults == null || mResults.isEmpty())
                mResults = (List<File>) ((ArrayList<File>) mItems).clone();
            checkViewedStatus();
        } else {
            mItems.clear();
            mResults.clear();
        }
        notifyDataSetChanged();
    }

    public void checkViewedStatus() {
        if (mResults == null || mResults.isEmpty())
            return;
        WriteLog.appendLog("Checking episode status");
        mLastEpisodeViewed = MAVApplication.getInstance().getRepository().getLastViewedByPath(mResults.get(0).getParent());
        for (File lEpisode : mResults) {
            mViewedMap.put(lEpisode.getAbsolutePath(),
                    MAVApplication.getInstance().getRepository().isPathViewed(lEpisode.getAbsolutePath()));
        }
    }

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

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

    public int getItemPosition(String path) {
        for (int i = 0; i < mResults.size(); i++) {
            if (mResults.get(i).getAbsolutePath().equals(path))
                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:
                v = mInflater.inflate(R.layout.library_grid_item, parent, false);
                viewHolder = new ViewHolder(v, this);
                if (mShowBadge) {
                    viewHolder.mTopRightBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.lgi_badge_view_right));
                    viewHolder.mTopRightBadgeView.setBadgePosition(BadgeView.POSITION_TOP_LEFT);
                }
                break;
        }
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, final int position) {
        Log.d(TAG, "onBindViewHolder for position " + position);
        final File lFile = mResults.get(position);
        Anime lAnime = null;

        if (viewHolder != null) {
            //When user scrolls this bind the correct selection status
            //if (!ThemeManager.getInstance().isLightBackground() && viewHolder.mTitleView != null)
            //viewHolder.mTitleView.setTextColor(ThemeManager.getInstance().getInvertedTextColor());
            viewHolder.bindFile(lFile, mViewedMap.get(lFile.getAbsolutePath()), ((mDisplayType != 3) && mBackStackLevel == 0));
            if (mBackStackLevel > 0) {
                if (mResults.get(position).getAbsolutePath().equals(mLastEpisodeViewed))
                    viewHolder.mTitleView.setTextColor(mLastViewedColor);
            }
            viewHolder.itemView.setActivated(isSelected(position));

            if (mAnimeMap != null)
                lAnime = mAnimeMap.get(mResults.get(position).getAbsolutePath());
            else
                lAnime = null;
            if (mAnime != null)
                lAnime = mAnime;

            if (lAnime != null) {
                if (mDisplayType > 0) {
                    if (mDisplayType != 3) {
                        if (viewHolder.mSubTextView != null) {
                            if (!ThemeManager.getInstance().isLightBackground())
                                viewHolder.mSubTextView.setTextColor(ThemeManager.getInstance().getInvertedTextColor());
                            String lSubText = "";
                            switch (mSecondaryType) {
                                default:
                                case NONE:
                                    break;
                                case GENRE:
                                    lSubText = lAnime.getGenres();
                                    break;
                                case AUTHOR:
                                    lSubText = lAnime.getCreator();
                                    break;
                                case LATEST_EPISODE:
                                    lSubText = lAnime.getLatestEpisode();
                                    break;
                                case SOURCE:
                                    lSubText = Parser.getNameByUrl(lAnime.getUrl());
                                    break;
                                case SOURCE_AND_LANG:
                                    String lLanguage = mContext.getResources().getString(Parser.getExistingInstance(Parser.getTypeByURL(lAnime.getUrl())).getLanguageResId());
                                    lSubText = Parser.getNameByUrl(lAnime.getUrl()) + " - "
                                            + lLanguage;
                                    break;
                            }
                            if (!TextUtils.isEmpty(lSubText)) {
                                viewHolder.mSubTextView.setText(lSubText);
                                viewHolder.mSubTextView.setVisibility(View.VISIBLE);
                            } else
                                viewHolder.mSubTextView.setVisibility(View.INVISIBLE);
                        }
                        if (viewHolder.mSourceTextView != null) {
                            if (!ThemeManager.getInstance().isLightBackground())
                                viewHolder.mSourceTextView.setTextColor(ThemeManager.getInstance().getInvertedTextColor());
                            viewHolder.mSourceTextView.setText(Parser.getNameByUrl(lAnime.getUrl()));
                        }
                    } else {
                        if (viewHolder.mAuthorView != null) {
                            if (!ThemeManager.getInstance().isLightBackground())
                                viewHolder.mAuthorView.setTextColor(ThemeManager.getInstance().getInvertedTextColor());
                            if (!TextUtils.isEmpty(lAnime.getCreator())) {
                                viewHolder.mAuthorView.setTypeface(null, Typeface.ITALIC);
                                viewHolder.mAuthorView.setText(lAnime.getCreator());
                            } else {
                                viewHolder.mAuthorView.setText("");
                            }
                        }
                        if (viewHolder.mGenreView != null) {
                            if (!ThemeManager.getInstance().isLightBackground())
                                viewHolder.mGenreView.setTextColor(ThemeManager.getInstance().getInvertedTextColor());
                            if (!TextUtils.isEmpty(lAnime.getGenres())) {
                                viewHolder.mGenreView.setText(lAnime.getGenres());
                            } else {
                                viewHolder.mGenreView.setText("");
                            }
                        }
                        if (viewHolder.mLatestView != null) {
                            if (!ThemeManager.getInstance().isLightBackground())
                                viewHolder.mLatestView.setTextColor(ThemeManager.getInstance().getInvertedTextColor());
                            if (!TextUtils.isEmpty(lAnime.getLatestEpisode())) {
                                viewHolder.mLatestView.setText(lAnime.getLatestEpisode());
                                viewHolder.mLatestView.setSelected(true);
                            } else {
                                viewHolder.mLatestView.setText("");
                            }
                        }
                        if (viewHolder.mSourceView != null) {
                            if (!ThemeManager.getInstance().isLightBackground())
                                viewHolder.mSourceView.setTextColor(ThemeManager.getInstance().getInvertedTextColor());
                            viewHolder.mSourceView.setText(Parser.getNameByUrl(lAnime.getUrl()));
                        }
                    }
                }
            }

            if (mSelectAll || mLastItemInActionMode) {
                //Reset the flags with delay
                if (viewHolder.mCheckView != null)
                    viewHolder.mCheckView.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mSelectAll = mLastItemInActionMode = false;
                        }
                    }, 200L);
                //Consume the Animation
                //flip(holder.mImageView, isSelected(position), 200L);
            } else {
                //Display the current flip status
                //setFlipped(holder.mImageView, isSelected(position));
            }

            if (viewHolder.mCheckView != null) {
                //This "if-else" is just an example
                if (isSelected(position)) {
                    viewHolder.mCheckView.setChecked(true);
                } else {
                    viewHolder.mCheckView.setChecked(false);
                }
            }
        }
    }

    static int mViewedColor = Color.GRAY;
    static int mUnreadColor = Color.WHITE;
    static int mLastViewedColor = Color.RED;

    /**
     * Provide a reference to the views for each data item.
     * Complex data labels may need more than one view per item, and
     * you provide access to all the views for a data item in a view holder.
     */
    static final class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,
            View.OnLongClickListener {

        LibraryRecyclerAdapter mAdapter;

        public TextView mTitleView;
        public SquareImageView mImageView;
        public TextView mSubTextView = null;
        public TextView mSourceTextView = null;

        public TextView mAuthorView = null;
        public TextView mGenreView = null;
        public TextView mLatestView = null;
        public TextView mSourceView = null;
        public BadgeView mTopLeftBadgeView = null;
        public BadgeView mTopRightBadgeView = null;
        public CheckBox mCheckView = null;

        public File mFile;

        ViewHolder(View itemView, final LibraryRecyclerAdapter adapter) {
            super(itemView);

            this.mAdapter = adapter;
            switch (mAdapter.mDisplayType) {
                case 1:
                case 2:
                case 3:
                    mTitleView = (TextView) itemView.findViewById(R.id.alc_text);
                    mSubTextView = (TextView) itemView.findViewById(R.id.alc_text2);
                    mSourceTextView = (TextView) itemView.findViewById(R.id.alc_text3);
                    mImageView = (SquareImageView) itemView.findViewById(R.id.alc_image);
                    mCheckView = (CheckBox) itemView.findViewById(R.id.alc_checkbox);

                    mAuthorView = mSubTextView;
                    mGenreView = mSourceTextView;
                    mLatestView = (TextView) itemView.findViewById(R.id.alc_text4);
                    mSourceView = (TextView) itemView.findViewById(R.id.alc_text5);
                    break;
                default:
                    mCheckView = (CheckBox) itemView.findViewById(R.id.lgi_checkBox);
                    mTitleView = (TextView) itemView.findViewById(R.id.lgi_text);
                    mImageView = (SquareImageView) itemView.findViewById(R.id.lgi_image);
                    break;
            }

            this.itemView.setOnClickListener(this);
            this.itemView.setOnLongClickListener(this);
        }

        public void bindFile(File manga, boolean pIsViewed, boolean pPaletteEnabled) {
            mFile = manga;
            if (mCheckView != null && mAdapter != null) {
                if (mAdapter.getMode() == MODE_MULTI)
                    mCheckView.setVisibility(View.VISIBLE);
                else
                    mCheckView.setVisibility(View.GONE);
            }
            if (mTitleView != null) {
                if (mAdapter.mBackStackLevel > 0) {
                    if (pIsViewed && !pPaletteEnabled)
                        mTitleView.setTextColor(mViewedColor);
                    else
                        mTitleView.setTextColor(mUnreadColor);
                }
                mTitleView.setText(manga.getName());
                File lCoverFile = mAdapter.mDisplayType == 1 ? null : getDisplayImage(manga.getAbsolutePath());
                if (lCoverFile != null) {
                    String ext = FileUtils.getFileExtension(lCoverFile.getName());
                    if (FileUtils.isImage(ext)) {
                        String lCoverURL = "file:///" + lCoverFile.getAbsolutePath();
                        if (pPaletteEnabled) {
                            ImageLoaderManager.getInstance().loadImage(lCoverURL, mImageView, new PaletteTransformation.PaletteCallback(mImageView) {

                                @Override
                                public void onError() {
                                    WriteLog.appendLog("LibraryRecyclerAdapter", "Error while generating palette");
                                }

                                @Override
                                protected void onSuccess(Palette palette) {
                                    int colorTemp = ThemeManager.getInstance().getBackgroundColor(mImageView.getContext());
                                    if (mTitleView != null && palette != null)
                                        if (palette.getVibrantSwatch() != null)
                                            colorTemp = palette.getVibrantSwatch().getRgb();
                                        else if (palette.getDarkMutedSwatch() != null)
                                            colorTemp = palette.getDarkMutedSwatch().getRgb();
                                    Integer colorFrom = ThemeManager.getInstance().getBackgroundColor(mTitleView.getContext());
                                    Integer colorTo = colorTemp;
                                    ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
                                    colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

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

                                    });
                                    colorAnimation.start();

                                }
                            }, null);
                        } else {
                            if (mAdapter.mDisplayType < 1) {
                                /**
                                if (ThemeManager.getInstance().getCurrentTheme() == ThemeManager.ThemeType.Light)
                                    mTitleView.setBackgroundResource(R.color.light_shadow_box_color);
                                else
                                    mTitleView.setBackgroundResource(R.color.shadow_box_color);
                                 **/
                            }
                            ImageLoaderManager.getInstance().loadImage(lCoverURL, mImageView);
                        }
                    }
                } else {
                    if (mAdapter.mDisplayType == 1)
                        mImageView.setVisibility(View.INVISIBLE);
                    else {
                        mImageView.setVisibility(View.VISIBLE);
                        mImageView.setImageResource(R.mipmap.ic_launcher);
                    }
                }
            }
        }

        private File getDisplayImage(String pPath) {
            File dir = new File(pPath);
            if (!dir.isDirectory())
                return dir;
            File[] file = dir.listFiles(new FilenameFilter() {

                @Override
                public boolean accept(File dir, String fileName) {
                    String ext = FileUtils.getFileExtension(fileName);
                    return FileUtils.isImage(ext);
                }
            });
            if (file != null) {
                for (File f : file) {
                    if (f.getName().contains("cover")) {
                        return f;
                    }
                }
                if (file.length > 0)
                    return file[0];
            }
            return null;
        }

        /**
         * Perform animation and selection on the current ItemView.
         * <br/><br/>
         * <b>IMPORTANT NOTE!</b> <i>setActivated</i> changes the selection color of the item
         * background if you added<i>android:background="?attr/selectableItemBackground"</i>
         * on the row layout AND in the style.xml.
         * <br/><br/>
         * This must be called after the listener consumed the event in order to add the
         * item number in the selection list.<br/>
         * Adapter must have a reference to its instance to check selection state.
         * <br/><br/>
         * If you do this, it's not necessary to invalidate the row (with notifyItemChanged): In this way
         * <i>onBindViewHolder</i> is NOT called on selection and custom animations on objects are NOT interrupted,
         * so you can SEE the animation in the Item and have the selection smooth with ripple.
         */
        private void toggleActivation() {
            itemView.setActivated(mAdapter.isSelected(getAdapterPosition()));
            if (itemView.isActivated()) {
                mCheckView.setChecked(true);
            } else {
                mCheckView.setChecked(false);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onClick(View view) {
            if (mAdapter.mClickListener.onListItemClick(getAdapterPosition()))
                toggleActivation();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean onLongClick(View view) {
            mAdapter.mClickListener.onListItemLongClick(getAdapterPosition());
            toggleActivation();
            return true;
        }
    }

    private class FileFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults results = new FilterResults();
            // We implement here the filter logic
            if (constraint == null || constraint.length() == 0) {
                // No filter implemented we return all the list
                results.values = mItems;
                results.count = mItems.size();
            } else {
                // We perform filtering operation
                List<File> nAnimeList = new ArrayList<File>();

                for (File p : mItems) {
                    if (p.getName().toUpperCase().startsWith(constraint.toString().toUpperCase()))
                        nAnimeList.add(p);
                }

                results.values = nAnimeList;
                results.count = nAnimeList.size();

            }
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint,
                                      FilterResults results) {

            // Now we have to inform the adapter about the new list filtered
            if (results.count == 0)
                notifyDataSetChanged();
            else {
                mResults = (List<File>) 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);
    } public class SlideInLeftAnimator extends BaseItemAnimator {

  public SlideInLeftAnimator() {

  }

  public SlideInLeftAnimator(Interpolator interpolator) {
    mInterpolator = interpolator;
  }

  @Override protected void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
    ViewPropertyAnimator.animate(holder.itemView)
        .translationX(-holder.itemView.getRootView().getWidth())
        .setDuration(getRemoveDuration())
        .setInterpolator(mInterpolator)
        .setListener(new DefaultRemoveVpaListener(holder))
        .start();
  }

  @Override protected void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
    ViewCompat.setTranslationX(holder.itemView, -holder.itemView.getRootView().getWidth());
  }

  @Override protected void animateAddImpl(final RecyclerView.ViewHolder holder) {
      ViewPropertyAnimator.animate(holder.itemView)
        .translationX(0)
        .setDuration(getAddDuration())
        .setInterpolator(mInterpolator)
        .setListener(new DefaultAddVpaListener(holder))
        .start();
  }
} @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 Episode {

    @Id
    long id;
    @Relation
    Anime anime;
    int index;
    String title;
    String url;
    Date date;
    String videoUrl;
    boolean isViewed = false;
    String localPath;
    long playPosition = 0;
    String animeUrl;

    @Override
    public String toString() {
        return "Episode [name=" + title + ", "
                + "url=" + url + ", "
                + "animeUrl=" + animeUrl + ", "
                + "index=" + index + ", "
                + "videoUrl=" + videoUrl + ", "
                + "isViewed=" + isViewed + ", "
                + "date=" + date + ", "
                + "localPath=" + localPath + " "
                + "playPosition=" + localPath + " "
                + "]";
    }

    /** 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. */
    @Keep
    public void setAnime(Anime anime) {
        animeToOne.setTarget(anime);
        this.anime = anime;
        this.animeUrl = anime.getUrl();
    }

    public static Comparator<Episode> SortByPosition = new Comparator<Episode>() {

        public int compare(Episode pEpisode1, Episode pEpisode2) {

            int diff = pEpisode1.index - pEpisode2.index;

            // ascending order
            return diff;
        }
    };
    /** Used to resolve relations */
    @Internal
    @Generated(1307364262)
    transient BoxStore __boxStore;

    @Generated(122010498)
    @Internal
    /** This constructor was generated by ObjectBox and may change any time. */
    public Episode(long id, int index, String title, String url, Date date,
            String videoUrl, boolean isViewed, String localPath, long playPosition,
            String animeUrl, long animeId) {
        this.id = id;
        this.index = index;
        this.title = title;
        this.url = url;
        this.date = date;
        this.videoUrl = videoUrl;
        this.isViewed = isViewed;
        this.localPath = localPath;
        this.playPosition = playPosition;
        this.animeUrl = animeUrl;
        this.animeToOne.setTargetId(animeId);
    }

    @Generated(1336866052)
    public Episode() {
    }

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

    public long getId() {
        return id;
    }

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

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    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 Date getDate() {
        return date;
    }

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

    public String getVideoUrl() {
        return videoUrl;
    }

    public void setVideoUrl(String videoUrl) {
        this.videoUrl = videoUrl;
    }

    public boolean isViewed() {
        return isViewed;
    }

    public void setViewed(boolean viewed) {
        isViewed = viewed;
    }

    public String getLocalPath() {
        return localPath;
    }

    public void setLocalPath(String localPath) {
        this.localPath = localPath;
    }

    public long getPlayPosition() {
        return playPosition;
    }

    public void setPlayPosition(long playPosition) {
        this.playPosition = playPosition;
    }

    public String getAnimeUrl() {
        return animeUrl;
    }

    public void setAnimeUrl(String animeUrl) {
        this.animeUrl = animeUrl;
    }
} public class EpisodeUtils {

    public static void viewEpisode(String tag, Context context, Anime anime, Episode episode, CoordinatorLayout coordinatorLayout) {
        if (context == null) {
            WriteLog.appendLog(tag, "viewEpisode context is null");
            return;
        }
        if (anime == null) {
            WriteLog.appendLog(tag, "viewEpisode anime is null");
            return;
        }
        if (episode == null) {
            WriteLog.appendLog(tag, "viewEpisode episode is null");
            return;
        }
        WriteLog.appendLog(tag, "viewEpisode() anime: " + anime.toString() + " episode: " + episode.toString());
        Intent intent = new Intent(context, VideoPlayerActivity.class);
        intent.putExtra(Constants.ANIME_ID, anime.getId());
        intent.putExtra(Constants.ANIME_URL, anime.getUrl());
        intent.putExtra(Constants.EPISODE_TITLE, episode.getTitle());
        intent.putExtra(Constants.EPISODE_URL, episode.getUrl());
        intent.putExtra(Constants.EPISODE_POSITION, episode.getIndex());
        intent.putExtra(Constants.EPISODE_START_POSITION, episode.getPlayPosition());
        context.startActivity(intent);
    }

    public static void viewEpisodeOffline(String tag, Context context, Anime anime, Episode episode, CoordinatorLayout coordinatorLayout) {
        if (!TextUtils.isEmpty(episode.getLocalPath())) {
            File temp = new File(episode.getLocalPath());
            Intent intent = new Intent(context, VideoDetailsActivity.class);
            intent.putExtra("shouldStart", false);
            if (anime != null) {
                intent.putExtra(Constants.ANIME_ID, anime.getId());
                intent.putExtra(Constants.ANIME_URL, anime.getUrl());
                intent.putExtra(Constants.ANIME_TITLE, anime.getTitle());
            }
            intent.putExtra(Constants.EPISODE_TITLE, episode.getTitle());
            intent.putExtra(Constants.EPISODE_URL, episode.getUrl());
            intent.putExtra(Constants.EPISODE_PATH, episode.getLocalPath());
            intent.putExtra(Constants.EPISODE_VIDEO_URL, episode.getLocalPath());
            intent.putExtra(Constants.EPISODE_START_POSITION, episode.getPlayPosition());
            context.startActivity(intent);
        } else {
            if (coordinatorLayout != null)
                Snackbar.make(coordinatorLayout, "Anime not loaded yet.", Snackbar.LENGTH_SHORT).show();
        }

    }

    public static void downloadEpisode(Context context, final Episode episode) {
        Intent downloadIntent = new Intent(context, DownloadService.class);
        downloadIntent.putExtra(Constants.DownloadIntents.TYPE, Constants.DownloadIntents.Types.ADD);
        downloadIntent.putExtra(Constants.DownloadIntents.URL, episode.getUrl());
        context.startService(downloadIntent);
    }

    public static void downloadEpisodes(@NonNull Activity activity, @NonNull Anime anime) {
        if (activity != null) {
            if (anime != null) {
                List<Episode> episodes = MAVApplication.getInstance().getRepository().getEpisodes(anime.getUrl());

                for (Episode episode : episodes) {
                    downloadEpisode(activity, episode);
                }
            }
        }
    }

    public static void viewEpisode(String tag, Context context, Anime anime, Episode episode) {
        viewEpisode(tag, context, anime, episode, null);
    }
} 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 BuildUtils {

    public static boolean isFroyoOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
    }

    public static boolean isGingerbreadOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
    }

    public static boolean isHoneycombOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
    }

    public static boolean isHoneycombMR2OrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2;
    }

    public static boolean isIceCreamSandwichOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
    }

    public static boolean isJellyBeanOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
    }

    public static boolean isJellyBeanMR2OrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
    }

    public static boolean isKitKatOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    }

    public static boolean isLollipopOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
    }

    public static boolean isMarshmallowOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
    }

    public static void setBackground(View view, Drawable d) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            view.setBackground(d);
        } else {
            view.setBackgroundDrawable(d);
        }
    }

} 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 final class FileUtils {

    public static final String DEBUG_TAG = FileUtils.class.getSimpleName();

    private static final int BUFFER_SIZE = 8 * 1024; // 8 KB

    private FileUtils() {
    }

    //sorts based on the files name
    public static class SortByFileName implements Comparator<File> {
        AlphanumComparator mInteralComparator = new AlphanumComparator();

        @Override
        public int compare(File f1, File f2) {
            // Sort alphabetically by lower case, which is much cleaner
            return mInteralComparator.compare(f1.getName().toLowerCase(), f2.getName().toLowerCase());
        }
    }

    //sorts based on a file or folder. folders will be listed first
    public static class SortByFolder implements Comparator<File> {
        @Override
        public int compare(File f1, File f2) {
            if (f1.isDirectory() == f2.isDirectory())
                return 0;
            else if (f1.isDirectory() && !f2.isDirectory())
                return -1;
            else
                return 1;
        }
    }

    public static String getFileExtension(String fileName) {
        String[] splitExtension = fileName.split("\\.");
        if (splitExtension.length > 1) {
            String extension = splitExtension[splitExtension.length - 1];
            return extension.toLowerCase();
        } else {
            return "";
        }
    }

    public static String getFileName(String filePath) {
        String[] split = filePath.split("/");
        if (split.length > 1) {
            String fileName = split[split.length - 1];
            return fileName;
        } else {
            return "";
        }
    }

    public static String dirSize(String path) {

        File dir = new File(path);

        if (dir.exists()) {
            long bytes = getFolderSize(dir);
            if (bytes < 1024) return bytes + " B";
            int exp = (int) (Math.log(bytes) / Math.log(1024));
            String pre = ("KMGTPE").charAt(exp - 1) + "";

            return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre);
        }

        return "0";
    }

    public static long getFolderSize(File dir) {
        if (dir.exists()) {
            long result = 0;
            File[] fileList = dir.listFiles();
            for (int i = 0; i < fileList.length; i++) {
                // Recursive call if it's a directory
                if (fileList[i].isDirectory()) {
                    result += getFolderSize(fileList[i]);
                } else {
                    // Sum the file size in bytes
                    result += fileList[i].length();
                }
            }
            return result; // return the file size
        }
        return 0;
    }

    public static long getFolderCount(File dir) {
        if (dir.exists()) {
            long result = 0;
            File[] fileList = dir.listFiles();
            for (int i = 0; i < fileList.length; i++) {
                if (fileList[i].isDirectory())
                    result++;
            }
            return result; // return the file size
        }
        return 0;
    }

    public static boolean isReadable(String ext) {
        return "avi".equalsIgnoreCase(ext) || "mp4".equalsIgnoreCase(ext);
    }

    public static boolean isFont(String fileName) {
        String extension = getExtension(fileName);
        return "ttf".equalsIgnoreCase(extension);
    }

    public static boolean isImageUrl(String url) {
        String extension = URLConnection.guessContentTypeFromName(url);
        return "jpg".equalsIgnoreCase(extension) || "jpeg".equalsIgnoreCase(extension) || "png".equalsIgnoreCase(extension) || "gif".equalsIgnoreCase(extension) || "bmp".equalsIgnoreCase(extension);
    }

    public static boolean isImage(String ext) {
        String extension = getExtension(ext);
        return "jpg".equalsIgnoreCase(extension) || "jpeg".equalsIgnoreCase(extension) || "png".equalsIgnoreCase(extension) || "gif".equalsIgnoreCase(extension) || "bmp".equalsIgnoreCase(extension);
    }

    public static boolean isVideo(String ext) {
        return "mp4".equalsIgnoreCase(ext) || "avi".equalsIgnoreCase(ext) || "webm".equalsIgnoreCase(ext) || "mpeg".equalsIgnoreCase(ext);
    }

    public static String getExtension(String file) {
        return file.substring(file.lastIndexOf(".")+1);
    }

    public static String stripExtension(String extension, String filename) {
        extension = "." + extension;
        if (filename.endsWith(extension)) {
            return filename.substring(0, filename.length() - extension.length());
        }
        return filename;
    }

    public static boolean validateFileName(String fileName) {
        return fileName.matches("^[^.\\\\/:*?\"<>|]?[^\\\\/:*?\"<>|]*")
                && getValidFileName(fileName).length() > 0;
    }

    public static String getValidFileName(String fileName) {
        String newFileName = fileName.replace(":", "")
                .replace("?", "")
                .replace("\"", "")
                .replace("\\", "")
                .replace("<", "")
                .replace(">", "")
                .replace("\\|", "")
                .replace(":", "")
                .replace(";", "")
                .replace("*", "").replace("/", "-").replaceAll("\\s$", "").trim();
        return newFileName;
    }

    public static void copyStream(InputStream is, OutputStream os) throws IOException {
        byte[] bytes = new byte[BUFFER_SIZE];
        while (true) {
            int count = is.read(bytes, 0, BUFFER_SIZE);
            if (count == -1) {
                break;
            }
            os.write(bytes, 0, count);
        }
    }

    public static boolean moveFile(String srcPath, String dstPath) {
        File file = new File(srcPath);
        // Destination directory
        return file.renameTo(new File(dstPath));
    }

    public static void copyDirectory(File sourceLocation, File targetLocation, FilenameFilter pFilternameFilter, FileFilter pFileFilter,
                                     boolean pDeleteSrc)
            throws IOException {

        if (sourceLocation.isDirectory()) {
            if (!targetLocation.exists()) {
                targetLocation.mkdir();
            }

            String[] children = sourceLocation.list(pFilternameFilter);
            for (int i = 0; i < sourceLocation.listFiles(pFileFilter).length; i++) {

                copyDirectory(new File(sourceLocation, children[i]),
                        new File(targetLocation, children[i]), pFilternameFilter, pFileFilter, pDeleteSrc);
            }
            if (pDeleteSrc)
                deleteDirectory(sourceLocation);
        } else {

            InputStream in = new FileInputStream(sourceLocation);

            OutputStream out = new FileOutputStream(targetLocation);

            // Copy the bits from instream to outstream
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
            in.close();
            out.close();
        }

    }

    /**
     * Overloaded with String path input
     *
     * @param dirPath
     * @return
     */
    public static boolean deleteDirectory(String dirPath) {
        // convert to file handler
        File path = new File(dirPath);
        return deleteDirectory(path);
    }

    /**
     * This function will recursively delete directories and files.
     *
     * @param path File or Directory to be deleted
     * @return true indicates success.
     */
    public static boolean deleteDirectory(File path) {

        // check if path exists
        if (path == null)
            return false;
        if (path.exists()) {

            // is directory
            if (path.isDirectory()) {

                // get list of file in directory
                File[] files = path.listFiles();
                if (files != null)
                    for (int i = 0; i < files.length; i++) {

                        // if it a directory make recursive call
                        // to deleteFile method
                        if (files[i].isDirectory()) {
                            deleteDirectory(files[i]);
                        } else {
                            // delete actual file
                            files[i].delete();
                        }
                    }
            }
        }
        // finally delete the path
        return (path.delete());
    }

    public static void deleteAsync(String... pPaths) {
        if (pPaths == null || pPaths.length == 0)
            return;
        AsyncTask<String, Void, Void> task = new AsyncTask<String, Void, Void>() {

            @Override
            protected Void doInBackground(String... params) {
                String lPath = null;
                for (int i = 0; i < params.length; i++) {
                    lPath = params[i];
                    if (TextUtils.isEmpty(lPath))
                        continue;
                    deleteDirectory(lPath);
                }
                return null;
            }

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

    public static boolean checkMD5(String md5, File updateFile) {
        if (md5 == null || md5.equals("") || updateFile == null || !updateFile.exists() || updateFile.isDirectory()) {
            Log.e(DEBUG_TAG, "MD5 String NULL or UpdateFile NULL");
            return false;
        }

        String calculatedDigest = calculateMD5(updateFile);
        if (calculatedDigest == null) {
            Log.e(DEBUG_TAG, "calculatedDigest NULL");
            return false;
        }
        Log.i(DEBUG_TAG, "checkMD5 for " + updateFile.getName());
        Log.i(DEBUG_TAG, "Calculated digest: " + calculatedDigest);
        Log.i(DEBUG_TAG, "Provided digest: " + md5);

        return calculatedDigest.equalsIgnoreCase(md5);
    }

    public static String calculateMD5(File updateFile) {
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            Log.e(DEBUG_TAG, "Exception while getting Digest", e);
            return null;
        }

        InputStream is;
        try {
            is = new FileInputStream(updateFile);
        } catch (FileNotFoundException e) {
            Log.e(DEBUG_TAG, "Exception while getting FileInputStream", e);
            return null;
        }

        byte[] buffer = new byte[8192];
        int read;
        try {
            while ((read = is.read(buffer)) > 0) {
                digest.update(buffer, 0, read);
            }
            byte[] md5sum = digest.digest();
            BigInteger bigInt = new BigInteger(1, md5sum);
            String output = bigInt.toString(16);
            // Fill to 32 chars
            output = String.format("%32s", output).replace(' ', '0');
            return output;
        } catch (IOException e) {
            WriteLog.appendLog("Unable to process file for MD5, removing file");
            WriteLog.appendLog(Log.getStackTraceString(e));
            updateFile.delete();
            return "";
            //throw new RuntimeException("Unable to process file for MD5", e);
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                Log.e(DEBUG_TAG, "Exception on closing MD5 input stream", e);
            }
        }
    }

    public static String fileToMD5(String filePath) {
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(filePath);
            byte[] buffer = new byte[1024];
            MessageDigest digest = MessageDigest.getInstance("MD5");
            int numRead = 0;
            while (numRead != -1) {
                numRead = inputStream.read(buffer);
                if (numRead > 0)
                    digest.update(buffer, 0, numRead);
            }
            byte[] md5Bytes = digest.digest();
            return convertHashToString(md5Bytes);
        } catch (Exception e) {
            return null;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                }
            }
        }
    }

    private static String convertHashToString(byte[] md5Bytes) {
        String returnVal = "";
        for (int i = 0; i < md5Bytes.length; i++) {
            returnVal += Integer.toString((md5Bytes[i] & 0xff) + 0x100, 16).substring(1);
        }
        return returnVal;
    }

    public static String getFileSize(long size) {
        if (size <= 0)
            return "0";
        final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" };
        int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
        return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
    }

    public static File getDisplayImage(String pPath) {
        File dir = new File(pPath);
        if (!dir.isDirectory())
            return dir;
        File[] file = dir.listFiles(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String fileName) {
                String ext = FileUtils.getFileExtension(fileName);
                return FileUtils.isImage(ext);
            }
        });
        if (file != null) {
            for (File f : file) {
                if (f.getName().contains("cover")) {
                    return f;
                }
            }
            if (file.length > 0)
                return file[0];
        }
        return null;
    }

    public static File findParentWithName(File file, String parentName) {
        if (file != null) {
            if (file.getParentFile() != null) {
                if (file.getParentFile().getName().equals(parentName)) {
                    return file;
                } else {
                    return findParentWithName(file.getParentFile(), parentName);
                }
            }
        }
        return null;
    }

    public static class AlphanumComparator implements Comparator
    {
        private final boolean isDigit(char ch)
        {
            return ch >= 48 && ch <= 57;
        }

        /** Length of string is passed in for improved efficiency (only need to calculate it once) **/
        private final String getChunk(String s, int slength, int marker)
        {
            StringBuilder chunk = new StringBuilder();
            char c = s.charAt(marker);
            chunk.append(c);
            marker++;
            if (isDigit(c))
            {
                while (marker < slength)
                {
                    c = s.charAt(marker);
                    if (!isDigit(c))
                        break;
                    chunk.append(c);
                    marker++;
                }
            } else
            {
                while (marker < slength)
                {
                    c = s.charAt(marker);
                    if (isDigit(c))
                        break;
                    chunk.append(c);
                    marker++;
                }
            }
            return chunk.toString();
        }

        @Override
        public int compare(Object o1, Object o2)
        {
            if (!(o1 instanceof String) || !(o2 instanceof String))
            {
                return 0;
            }
            String s1 = (String)o1;
            String s2 = (String)o2;

            int thisMarker = 0;
            int thatMarker = 0;
            int s1Length = s1.length();
            int s2Length = s2.length();

            while (thisMarker < s1Length && thatMarker < s2Length)
            {
                String thisChunk = getChunk(s1, s1Length, thisMarker);
                thisMarker += thisChunk.length();

                String thatChunk = getChunk(s2, s2Length, thatMarker);
                thatMarker += thatChunk.length();

                // If both chunks contain numeric characters, sort them numerically
                int result = 0;
                if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0)))
                {
                    // Simple chunk comparison by length.
                    int thisChunkLength = thisChunk.length();
                    result = thisChunkLength - thatChunk.length();
                    // If equal, the first different number counts
                    if (result == 0)
                    {
                        for (int i = 0; i < thisChunkLength; i++)
                        {
                            result = thisChunk.charAt(i) - thatChunk.charAt(i);
                            if (result != 0)
                            {
                                return result;
                            }
                        }
                    }
                } else
                {
                    result = thisChunk.compareTo(thatChunk);
                }

                if (result != 0)
                    return result;
            }

            return s1Length - s2Length;
        }
    }
} public class StorageUtils {

    public static String DEFAULT_LIBRARY_FOLDER = "My Anime Viewer/";

    private static File cacheDir = null;
    private static File dataDir = null;
    private static File backupDir = null;
    private static File searchDataFile = null;
    private static String lastPath = "";

    public static final String ERROR = "No Space";
    private static boolean storageAllowed = false;

    static {
        if (!BuildUtils.isMarshmallowOrLater())
            StorageUtils.storageAllowed = true;
    }

    /**
     * @return the cacheDir
     */
    public static File getCacheDir() {
        return cacheDir;
    }

    /**
     * @param pCacheDir The cache directory
     */
    public static void setCacheDir(File pCacheDir) {
        StorageUtils.cacheDir = pCacheDir;
    }

    public static File getExtSDCard(Context c) {
        File externalFolder = null;
        File[] test = ContextCompat.getExternalCacheDirs(c.getApplicationContext());
        if (test != null && test.length > 0) {
            externalFolder = test[test.length - 1];
            if (externalFolder != null) {
                if (externalFolder.getAbsolutePath().contains("cache"))
                    externalFolder = externalFolder.getParentFile();
            }
        }
        return externalFolder;
    }

    /**
     * @param pDirectory The data directory
     */
    public static void setDataDirectory(File pDirectory) {
        dataDir = pDirectory;
        if (dataDir != null) {
            if (!dataDir.exists())
                dataDir.mkdirs();
            File nomediaFile = new File(dataDir, ".nomedia");
            if (!nomediaFile.exists())
                try {
                    if (nomediaFile.createNewFile())
                        WriteLog.appendLog(nomediaFile.getAbsolutePath() + " file created.");
                } catch (IOException e) {
                    WriteLog.appendLog(nomediaFile.getAbsolutePath() + " failed to be created.");
                    WriteLog.appendLog(Log.getStackTraceString(e));
                }
            else {
                WriteLog.appendLog(nomediaFile.getAbsolutePath() + " file exists.");
            }
            backupDir = new File(
                    StorageUtils.getDataDirectory(),
                    "Backup/");
            if (!backupDir.exists())
                backupDir.mkdirs();
        }
    }

    public static File getDataDirectory() {
        return dataDir;
    }

    public static File getBackupDirectory() {
        return backupDir;
    }

    /**
     * @return the lastPath
     */
    public static String getLastPath() {
        return lastPath;
    }

    /**
     * @param mLastPath the lastPath to set
     */
    public static void setLastPath(String mLastPath) {
        StorageUtils.lastPath = mLastPath;
    }

    /**
     * @return the searchDataFile
     */
    public static File getSearchDataFile() {
        return searchDataFile;
    }

    /**
     * @param mSearchDataFile the searchDataFile to set
     */
    public static void setSearchDataFile(File mSearchDataFile) {
        StorageUtils.searchDataFile = mSearchDataFile;
    }

    public static long getAvailableStorage() {

        String storageDirectory = null;
        storageDirectory = Environment.getExternalStorageDirectory().toString();

        try {
            StatFs stat = new StatFs(storageDirectory);
            long avaliableSize = ((long) stat.getAvailableBlocks() * (long) stat.getBlockSize());
            return avaliableSize;
        } catch (RuntimeException ex) {
            return 0;
        }
    }

    public static void requestStoragePermissions(Context context) {
        PermissionListener dialogPermissionListener =
                DialogOnDeniedPermissionListener.Builder
                        .withContext(context)
                        .withTitle("Write to Storage Permission")
                        .withMessage("Storage permission is required to save any manga to your phone.")
                        .withButtonText(android.R.string.ok)
                        .withIcon(R.mipmap.ic_launcher)
                        .build();
        Dexter.withActivity((AppCompatActivity) context).withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE).withListener(dialogPermissionListener).check();
    }

    public static void requestStoragePermissions(Context context, PermissionListener listener) {
        Dexter.withActivity((Activity) context).withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE).withListener(listener).check();
    }

    public static void requestStoragePermissions(PermissionListener listener) {
        if (BuildUtils.isMarshmallowOrLater()) {
            //if (!Dexter.isRequestOngoing()) Dexter.checkPermission(listener, Manifest.permission.WRITE_EXTERNAL_STORAGE);
        } else {
            storageAllowed = true;
        }
    }

    public static boolean isStorageAllowed() {
        return storageAllowed;
    }

    public static void setStorageAllowed(boolean storageAllowed) {
        StorageUtils.storageAllowed = storageAllowed;
    }

    public static boolean isSDCardPresent() {
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }

    public static boolean isSDCardWritable() {
        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            return true;
        }
        return false;
    }
} public class WriteLog {
    public static final int VERBOSE = 0;
    public static final int DEBUG = 1;
    private static boolean mIsEnabled = false;
    public static boolean mSystemOut = true;
    public static boolean mPrintPhoneInfo = false;
    public static int mDebugLevel = 0;
    private static Context mContext;

    public static void setContext(Context context) {
        mContext = context;
    }

    public static void setEnabled(boolean isEnabled) {
        mIsEnabled = isEnabled;
        if (mIsEnabled)
            mDebugLevel = DEBUG;
    }

    public final static String getDateTime() {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd_hh:mm:ss");
        return df.format(new Date());
    }

    public static String getPhoneInfo() {
        mPrintPhoneInfo = true;
        String s = getDateTime() + " Phone Info:";
        s += "\n OS Version: " + System.getProperty("os.version") + "(" + android.os.Build.VERSION.INCREMENTAL + ")";
        s += "\n OS API Level: " + android.os.Build.VERSION.SDK_INT;
        s += "\n Device: " + android.os.Build.DEVICE;
        s += "\n Model (and Product): " + android.os.Build.MODEL + " (" + android.os.Build.PRODUCT + ")";
        s += "\n Screen Size Height: " + DisplayUtils.getScreenHeight(mContext) + "  Width: " + DisplayUtils.getScreenWidth(mContext);
        return s;
    }

    public static void appendLog(String... text) {
        if (text != null && text.length > 0) {
            String parsedText = text[0];
            for (int i = 0; i < text.length - 1; i++) {
                if (i + 1 >= text.length)
                    break;
                parsedText = parsedText.replaceAll("\\{" + i + "\\}", text[i + 1]);
            }
            appendLog(parsedText);
        }
    }

    public static void appendLog(String tag, String text) {
        if (mSystemOut)
            Log.d(tag, text);
        printToFile(tag + ": " + text);
    }

    public static void appendLog(String tag, String text, int level) {
        if (level >= mDebugLevel) {
            if (mSystemOut)
                Log.d(tag, text);
            printToFile(tag + ": " + text);
        }
    }

    private static void printToFile(String text) {
        if (mIsEnabled) {
            File logFile = new File(StorageUtils.getDataDirectory(), "Log.txt");
            if (!logFile.exists()) {
                try {
                    logFile.createNewFile();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            try {
                //BufferedWriter for performance, true to set append to file flag
                BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true));
                if (!mPrintPhoneInfo)
                    buf.append(getPhoneInfo());
                buf.append("[" + getDateTime() + "] " + text);
                buf.newLine();
                buf.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public static void appendLog(String text) {
        if (mSystemOut) {
            System.out.println(text);
        }
        printToFile(text);
    }

    public static void appendLogIf(boolean condition, String text) {
        if (condition)
            appendLog(text);
    }

    public static void appendLogDetailedOnly(String text) {
        if (isDetailed())
            appendLog(text);
    }

    public static void appendLogDetailedOnly(String tag, String text) {
        if (isDetailed())
            appendLog(tag, text);
    }

    public static void appendLogException(String tag, String text, Exception exception) {
        WriteLog.appendLog(tag, text);
        WriteLog.appendLog(Log.getStackTraceString(exception));
    }

    public static void appendLogException(String text, Exception exception) {
        WriteLog.appendLog(text);
        WriteLog.appendLog(Log.getStackTraceString(exception));
    }

    public static boolean isDetailed() {
        return mDebugLevel > 1;
    }

    public static boolean IsEnabled() {
        return mIsEnabled;
    }
} public class ControllableAppBarLayout extends AppBarLayout {
    private AppBarLayout.Behavior mBehavior;
    private WeakReference<CoordinatorLayout> mParent;
    private ToolbarChange mQueuedChange = ToolbarChange.NONE;
    private boolean mAfterFirstDraw = false;

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

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

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (!(getLayoutParams() instanceof CoordinatorLayout.LayoutParams) || !(getParent() instanceof CoordinatorLayout)) {
            throw new IllegalStateException("ControllableAppBarLayout must be a direct child of CoordinatorLayout.");
        } else {
            mParent = new WeakReference<CoordinatorLayout>((CoordinatorLayout) getParent());
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mBehavior == null) {
            mBehavior = (Behavior) ((CoordinatorLayout.LayoutParams) getLayoutParams()).getBehavior();
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (r - l > 0 && b - t > 0 && mAfterFirstDraw && mQueuedChange != ToolbarChange.NONE) {
            analyzeQueuedChange();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!mAfterFirstDraw) {
            mAfterFirstDraw = true;
            if (mQueuedChange != ToolbarChange.NONE) {
                analyzeQueuedChange();
            }
        }
    }

    private synchronized void analyzeQueuedChange() {
        switch (mQueuedChange) {
            case COLLAPSE:
                performCollapsingWithoutAnimation();
                break;
            case COLLAPSE_WITH_ANIMATION:
                performCollapsingWithAnimation();
                break;
            case EXPAND:
                performExpandingWithoutAnimation();
                break;
            case EXPAND_WITH_ANIMATION:
                performExpandingWithAnimation();
                break;
        }

        mQueuedChange = ToolbarChange.NONE;
    }

    public void collapseToolbar() {
        collapseToolbar(false);
    }

    public void collapseToolbar(boolean withAnimation) {
        mQueuedChange = withAnimation ? ToolbarChange.COLLAPSE_WITH_ANIMATION : ToolbarChange.COLLAPSE;
        requestLayout();
    }

    public void expandToolbar() {
        expandToolbar(false);
    }

    public void expandToolbar(boolean withAnimation) {
        mQueuedChange = withAnimation ? ToolbarChange.EXPAND_WITH_ANIMATION : ToolbarChange.EXPAND;
        requestLayout();
    }

    private void performCollapsingWithoutAnimation() {
        if (mParent.get() != null) {
            mBehavior.onNestedPreScroll(mParent.get(), this, null, 0, getHeight(), new int[]{0, 0});
        }
    }

    private void performCollapsingWithAnimation() {
        if (mParent.get() != null) {
            mBehavior.onNestedFling(mParent.get(), this, null, 0, getHeight(), true);
        }
    }

    private void performExpandingWithoutAnimation() {
        if (mParent.get() != null) {
            mBehavior.setTopAndBottomOffset(0);
        }
    }

    private void performExpandingWithAnimation() {
        if (mParent.get() != null) {
            mBehavior.onNestedFling(mParent.get(), this, null, 0, -getHeight() * 5, false);
        }
    }

    private enum ToolbarChange {
        COLLAPSE,
        COLLAPSE_WITH_ANIMATION,
        EXPAND,
        EXPAND_WITH_ANIMATION,
        NONE
    }

} 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]);
        }
    }

} public static final int MODE_SINGLE = 1;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.SharedElementCallback;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.SearchView.OnCloseListener;
import android.support.v7.widget.SearchView.OnQueryTextListener;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.Toast;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.taskdesignsinc.android.myanimeviewer.MAVApplication;
import com.taskdesignsinc.android.myanimeviewer.R;
import com.taskdesignsinc.android.myanimeviewer.adapter.LibraryRecyclerAdapter;
import com.taskdesignsinc.android.myanimeviewer.adapter.base.OnItemClickListener;
import com.taskdesignsinc.android.myanimeviewer.recyclerview.animator.SlideInLeftAnimator;
import com.taskdesignsinc.android.myanimeviewer.model.Anime;
import com.taskdesignsinc.android.myanimeviewer.model.Episode;
import com.taskdesignsinc.android.myanimeviewer.model.helper.EpisodeUtils;
import com.taskdesignsinc.android.myanimeviewer.model.helper.AnimeUtils;
import com.taskdesignsinc.android.myanimeviewer.util.BuildUtils;
import com.taskdesignsinc.android.myanimeviewer.util.Constants;
import com.taskdesignsinc.android.myanimeviewer.util.FileUtils;
import com.taskdesignsinc.android.myanimeviewer.util.StorageUtils;
import com.taskdesignsinc.android.myanimeviewer.util.WriteLog;
import com.taskdesignsinc.android.myanimeviewer.widget.ControllableAppBarLayout;
import com.taskdesignsinc.android.thememanager.ThemeManager;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
import static com.taskdesignsinc.android.myanimeviewer.adapter.base.SelectableAdapter.MODE_SINGLE;
            mAnimeMap = new HashMap<String, Anime>();
            mPrevPath = new Stack<String>();
            if (TextUtils.isEmpty(mPath)) {
                if (StorageUtils.getDataDirectory() != null)
                    mPath = StorageUtils.getDataDirectory().getAbsolutePath();
                else
                    mPath = Environment.getExternalStorageDirectory().getAbsolutePath();
            }
            // Create an empty adapter we will use to display the loaded data.
            mAdapter = new LibraryRecyclerAdapter(getActivity(), mDisplayType, mLibraryClickListener);
            mRecyclerView.setAdapter(mAdapter);
        }

        //Restore previous state
        if (savedInstanceState != null) {
            ArrayList<String> mPrevPathList = savedInstanceState.getStringArrayList(Constants.ANIME_PREV_PATH_LIST);
            if (mPrevPathList != null && !mPrevPathList.isEmpty() && mPrevPath.size() != mPrevPathList.size()) {
                for (String s : mPrevPathList) {
                    mPrevPath.push(s);
                }
            }
            if (mAdapter != null) {
                //Selection
                mAdapter.onRestoreInstanceState(savedInstanceState);
                if (mAdapter.getSelectedItemCount() > 0) {
                    mActionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(new RecordOptions((AppCompatActivity) getActivity()));
                }
                //Previously serialized activated item position
                if (savedInstanceState.containsKey(STATE_ACTIVATED_POSITION))
                    setSelection(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
            }
        }

        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        getLoaderManager().initLoader(0, null, this);
    }

    public static class MySearchView extends SearchView {
        public MySearchView(Context context) {
            super(context);
        }

        // The normal SearchView doesn't clear its search text when
        // collapsed, so we will do this for it.
        @Override
        public void onActionViewCollapsed() {
            setQuery("", false);
            super.onActionViewCollapsed();
        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    }

    private int mItemBaseID = 9700;

    @Override
    public void onPrepareOptionsMenu(Menu menu) {

        menu.clear();
        // Place an action bar item for searching.
        MenuItem item = menu.add(R.string.search);
        if (ThemeManager.getInstance().isLightBackground())
            item.setIcon(R.drawable.ic_search_black_24dp);
        else
            item.setIcon(R.drawable.ic_search_white_24dp);
        MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM
                | MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
        mSearchView = new MySearchView(getActivity());
        mSearchView.setOnQueryTextListener(this);
        mSearchView.setOnCloseListener(this);
        mSearchView.setIconifiedByDefault(true);
        MenuItemCompat.setActionView(item, mSearchView);
        //menu.add(Menu.NONE, mItemBaseID, 0, R.string.open).setIcon(ThemeManager.getInstance().getCurrentTheme() == ThemeType.Light ? R.drawable.open_light : R.drawable.open_dark).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        menu.add(Menu.NONE, mItemBaseID + 1, 1, R.string.sort)
                .setIcon(ThemeManager.getInstance(getActivity()).isLightBackground() ? R.drawable.ic_sort_by_alpha_black_24dp : R.drawable.ic_sort_by_alpha_white_24dp);
        MenuItemCompat.setShowAsAction(menu.getItem(menu.size() - 1), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
        //menu.add(R.string.search_anime).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem pItem) {
        if (pItem.getTitle().equals("Sort")) {
            final CharSequence[] items = {"Title A-Z", "Title Z-A"};

            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            builder.setTitle("Sort By");
            builder.setSingleChoiceItems(items, mSortType, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int item) {
                    dialog.dismiss();
                    switch (item) {
                        case 0:
                            mAdapter.sort(new FileUtils.SortByFileName());
                            break;
                        case 1:
                            mAdapter.sort(Collections.reverseOrder(new FileUtils.SortByFileName()));
                            break;
                    }
                    mSortType = item;
                }
            });
            AlertDialog alert = builder.create();
            alert.show();
            return true;
        } else if (pItem.getTitle().equals("Find Current Anime")) {
            if (mAnime != null) {
                viewAnime(mAnime.getUrl());
            } else {
                Toast.makeText(getActivity(), "No anime data found", Toast.LENGTH_SHORT).show();
                WriteLog.appendLog("No anime data found");
            }
            return true;
        }
        return super.onOptionsItemSelected(pItem);
    }

    public void viewAnime(String animeUrl) {
        AnimeUtils.viewAnime(getActivity(), animeUrl);