public class AboutFragment extends TitledFragment implements AboutAdapter.OnClickListener {

    public static final String TAG = AboutFragment.class.getSimpleName();

    @BindView(R.id.recyclerView)
    public RecyclerView mRecyclerView;

    private Unbinder mUnbinder;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_about, container, false);
        mUnbinder = ButterKnife.bind(this, v);
        return v;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);


        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity());
        mRecyclerView.setLayoutManager(layoutManager);

        String[] aboutStringArray = getResources().getStringArray(R.array.about_libraries);
        String byString = getResources().getString(R.string.word_by);
        for(int i = 0; i < aboutStringArray.length; i++){
            aboutStringArray[i] = String.format(aboutStringArray[i], byString);
        }

        AboutAdapter adapter = new AboutAdapter(this, aboutStringArray);

        mRecyclerView.setAdapter(adapter);
    }

    @Override public void onDestroyView() {
        super.onDestroyView();
        mUnbinder.unbind();
    }

    @Override
    public void onResume() {
        super.onResume();
        EventBus.getDefault().post(new ChangeTitleEvent(getTitle()));

        EventTracker.trackFragmentView(this, null, EventTracker.CONTENT_TYPE_APP);
    }

    @Override
    public void onItemClick(Intent intent) {
        Toast.makeText(getActivity(), R.string.open_intent_browser, Toast.LENGTH_SHORT).show();
        startActivity(intent);
    }

    @Override
    public String getTitle() {
        return getString(R.string.about_title);
    }
} public class ToolbarSpinnerAdapter extends BaseAdapter {

    private final Context      mContext;
    private       Website      mWebsite;
    private       String       mTitle;
    private       List<String> mItems;

    public ToolbarSpinnerAdapter(Context context, String title, List<String> items) {
        //noinspection unchecked
        mContext = context;
        mItems = items;
        mTitle = title;
    }

    @Override
    public int getCount() {
        return mItems.size();
    }

    @Override
    public Object getItem(int position) {
        return mItems.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        String item = (String) getItem(position);

        if (convertView == null) {
            convertView = LayoutInflater.from(mContext).inflate(R.layout.toolbar_spinner_item, parent, false);
        }

        ((TextView) convertView.findViewById(R.id.title)).setText(mTitle);
        ((TextView) convertView.findViewById(R.id.subtitle)).setText(item);

        return convertView;
    }

    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        String item = (String) getItem(position);

        if (convertView == null) {
            convertView = LayoutInflater.from(mContext).inflate(R.layout.toolbar_spinner_dropdown_item, parent, false);
        }

        ((TextView) convertView.findViewById(R.id.title)).setText(item);

        return convertView;
    }

    /**
     * Populate the spinner with the given website. It will browse it add add the websitep ages name.
     *
     * @param website                 the website to get pages from
     * @param websitePageSlugSelected the slug of the selected item, if any
     * @return the selected item index
     */
    public int populate(Website website, @Nullable String websitePageSlugSelected) {
        mItems.clear();

        mWebsite = website;

        mTitle = website.name;

        int selected = 0;

        int i = 0;
        for (WebsitePage websitePage : website.pages) {
            mItems.add(websitePage.name);
            if (websitePageSlugSelected != null && websitePageSlugSelected.equals(websitePage.slug)) {
                selected = i;
            }
            i++;
        }

        return selected;
    }

    /**
     * Get the current displayed website, if any
     * @return the current website
     */
    @Nullable
    public Website getWebsite() {
        return mWebsite;
    }
} public class UpdateAnecdoteFragmentEvent implements Event {
} public class WebsiteDialogFragment extends AppCompatDialogFragment {

    public static final String ARGS_WEBSITE = "args_website";

    @BindView(R.id.nameContainer)
    public TextInputLayout mNameTextInputLayout;
    @BindView(R.id.nameEditText)
    public EditText        mNameEditText;
    @BindView(R.id.pageNameContainer)
    public TextInputLayout mPageNameTextInputLayout;
    @BindView(R.id.pageNameEditText)
    public EditText        mPageNameEditText;
    @BindView(R.id.urlContainer)
    public TextInputLayout mUrlTextInputLayout;
    @BindView(R.id.urlEditText)
    public EditText        mUrlEditText;
    @BindView(R.id.urlSuffixEditText)
    public EditText        mUrlSuffixEditText;
    @BindView(R.id.selectorContainer)
    public TextInputLayout mSelectorTextInputLayout;
    @BindView(R.id.selectorEditText)
    public EditText        mSelectorEditText;
    @BindView(R.id.firstPageZeroSwitchCompat)
    public SwitchCompat    mFirstPageZeroSwitchCompat;
    @BindView(R.id.saveButton)
    public Button          mSaveButton;

    protected Website     mWebsite;
    protected WebsitePage mWebsitePage;
    protected boolean     mEditMode;
    private   Unbinder    mUnbinder;

    public static WebsiteDialogFragment newInstance(@Nullable Website website) {
        WebsiteDialogFragment frag = new WebsiteDialogFragment();
        if (website != null) {
            Bundle args = new Bundle();
            args.putString(ARGS_WEBSITE, new Gson().toJson(website));
            frag.setArguments(args);
        }
        return frag;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_dialog_website, container);
        mUnbinder = ButterKnife.bind(this, view);
        return view;
    }

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

        if (getDialog() != null) {
            getDialog().getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        }
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        if (getArguments() != null && !TextUtils.isEmpty(getArguments().getString(ARGS_WEBSITE))) {
            mWebsite = new Gson().fromJson(
                    getArguments().getString(ARGS_WEBSITE),
                    new TypeToken<Website>() {
                    }.getType());
            mWebsitePage = mWebsite.pages.get(0);
            initEdit();
        } else {
            mWebsite = new Website();
            mWebsitePage = new WebsitePage();
            initAdd();
        }

        mSaveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!isDataCorrect()) {
                    return;
                }
                mWebsite.name = mNameEditText.getText().toString();

                mWebsitePage.name = mPageNameEditText.getText().toString();
                mWebsitePage.url = mUrlEditText.getText().toString();
                mWebsitePage.urlSuffix = mUrlSuffixEditText.getText().toString();
                mWebsitePage.isFirstPageZero = mFirstPageZeroSwitchCompat.isChecked();
                mWebsitePage.selector = mSelectorEditText.getText().toString();
                if (mWebsitePage.content == null) {
                    mWebsitePage.content = new Content();
                    mWebsitePage.content.items.add(new ContentItem(MediaType.TEXT, 1));
                }

                if (mWebsite.pages.size() > 0) {
                    mWebsite.pages.set(0, mWebsitePage);
                } else {
                    mWebsite.pages.add(mWebsitePage);
                }

                SpStorage.saveWebsite(getContext(), mWebsite);

                if (mEditMode) {
                    EventTracker.trackWebsiteEdit(mWebsite.name, true);
                } else {
                    EventTracker.trackCustomWebsiteAdded();
                }

                EventBus.getDefault().post(new WebsitesChangeEvent());
                WebsiteDialogFragment.this.getDialog().dismiss();
            }
        });
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mUnbinder.unbind();
    }

    protected void initAdd() {
        getDialog().setTitle(R.string.dialog_website_new_title);
        mSaveButton.setText(R.string.dialog_website_add);
    }

    protected void initEdit() {
        mEditMode = true;
        getDialog().setTitle(R.string.dialog_website_edit_title);
        mNameEditText.setText(mWebsite.name);
        mPageNameEditText.setText(mWebsitePage.name);
        mUrlEditText.setText(mWebsitePage.url);
        mUrlSuffixEditText.setText(mWebsitePage.urlSuffix);
        mSelectorEditText.setText(mWebsitePage.selector);
        mFirstPageZeroSwitchCompat.setChecked(mWebsitePage.isFirstPageZero);
    }

    protected boolean isDataCorrect() {

        if (TextUtils.isEmpty(mNameEditText.getText().toString())) {
            mNameTextInputLayout.setErrorEnabled(true);
            mNameTextInputLayout.setError(getContext().getString(R.string.dialog_website_error_name));
            mNameEditText.requestLayout();
            return false;
        } else {
            mNameTextInputLayout.setErrorEnabled(false);
        }

        if (TextUtils.isEmpty(mPageNameEditText.getText().toString())) {
            mPageNameTextInputLayout.setErrorEnabled(true);
            mPageNameTextInputLayout.setError(getContext().getString(R.string.dialog_website_error_pagename));
            mPageNameEditText.requestLayout();
            return false;
        } else {
            mPageNameTextInputLayout.setErrorEnabled(false);
        }

        if (TextUtils.isEmpty(mUrlEditText.getText().toString())) {
            mUrlTextInputLayout.setErrorEnabled(true);
            mUrlTextInputLayout.setError(getContext().getString(R.string.dialog_website_error_url));
            mUrlEditText.requestLayout();
            return false;
        } else {
            mUrlTextInputLayout.setErrorEnabled(false);
        }

        if (TextUtils.isEmpty(mSelectorEditText.getText().toString())) {
            mSelectorTextInputLayout.setErrorEnabled(true);
            mSelectorTextInputLayout.setError(getContext().getString(R.string.dialog_website_error_selector));
            mSelectorEditText.requestLayout();
            return false;
        } else {
            mSelectorTextInputLayout.setErrorEnabled(false);
        }

        return true;
    }
} public class ChangeFullscreenEvent implements Event {

    public boolean toFullscreen;

    public ChangeFullscreenEvent(boolean toFullscreen) {
        this.toFullscreen = toFullscreen;
    }
} @Singleton
public final class FavoritesRepository {

    public static final  int    OFFSET_QUERY = 50;

    private static Website sWebsite;

    @Inject
    protected Application mApplication;
    @Inject
    protected BoxStore    mBoxStore;
    private Box<Anecdote>   mAnecdoteBox;
    private Query<Anecdote> mAnecdoteManyQuery;
    private Query<Anecdote> mAnecdoteSingleQuery;

    /**
     * Setup the singleton. This is not done on Singleton creation because the DI is made afterward.
     */
    public void setup() {
        if (mAnecdoteBox == null) {
            mAnecdoteBox = mBoxStore.boxFor(Anecdote.class);
            mAnecdoteManyQuery = mAnecdoteBox
                    .query()
                    .orderDesc(Anecdote_.favoritesTimestamp)
                    .build();

            mAnecdoteSingleQuery = mAnecdoteBox
                    .query()
                    .equal(Anecdote_.text, "")
                    .equal(Anecdote_.websitePageSlug, "")
                    .build();
        }
    }


    /**
     * Return the favorited Anecdote for the given page, starting at page 0 with {@link #OFFSET_QUERY} items per page
     * max.
     * @param page the page to get favorited anecdote on
     * @return the lsit of favorited anecdote, may be empty
     */
    public List<Anecdote> getFavorites(int page) {
        return mAnecdoteManyQuery.find(OFFSET_QUERY * page, OFFSET_QUERY);
    }

    /**
     * Check if the given Anecdote is in favorites or not.
     */
    @Nullable
    public Anecdote isFavorite(Anecdote anecdote) {
        mAnecdoteSingleQuery.setParameter(Anecdote_.websitePageSlug, anecdote.websitePageSlug);

        if(!TextUtils.isEmpty(anecdote.text)){
            mAnecdoteSingleQuery.setParameter(Anecdote_.text, anecdote.text);
        }
        
        return mAnecdoteSingleQuery.findFirst();
    }

    /**
     * Save a given Anecdote to the local storage
     *
     * @param websiteNamePageSlug the name of the website and of the page
     * @param anecdote            the anecdote to save
     */
    public Anecdote addFavorite(String websiteNamePageSlug, Anecdote anecdote) {
        // We clone the given anecdote so we do'nt mutate it and the adapter can make the diff properly
        Anecdote modifiedAnecdote;
        try {
            modifiedAnecdote = anecdote.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            modifiedAnecdote = anecdote;
        }
        modifiedAnecdote.favoritesTimestamp = System.currentTimeMillis() / 1000;
        mAnecdoteBox.put(modifiedAnecdote);
        EventTracker.trackFavorite(websiteNamePageSlug);
        return modifiedAnecdote;
    }

    /**
     * Remove a given Anecdote to the local storage
     *
     * @param anecdote the anecdote to save
     */
    public Anecdote removeFavorite(Anecdote anecdote) {
        // Stored Anecdote don't have the same id after app restart.
        Anecdote toRemoveAnecdote = isFavorite(anecdote);
        if(toRemoveAnecdote != null){
            mAnecdoteBox.remove(toRemoveAnecdote);
        }
        // Clone the anecdote and change the timestamp to remove the favorite on it
        Anecdote modifiedAnecdote;
        try {
            modifiedAnecdote = anecdote.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            modifiedAnecdote = anecdote;
        }
        modifiedAnecdote.favoritesTimestamp = 0L;
        return modifiedAnecdote;
    }

    /**
     * Get the static Website for Favorites. This allow the app to use the same architecture for Favorites while having
     * this website as an "offline" website.
     *
     * @param context app context
     * @return the website for Favorites
     */
    public static Website getFavoritesWebsite(Context context) {
        if (sWebsite == null) {
            String name = context.getString(R.string.action_favoris);
            sWebsite = new Website(context.getString(R.string.action_favoris), Website.SOURCE_CALCULATED);

            WebsitePage websitePage = new WebsitePage();
            websitePage.name = name;
            websitePage.slug = Website.SOURCE_CALCULATED + "-" + "favoris";

            sWebsite.pages.add(websitePage);

            sWebsite.validateData();
        }
        return sWebsite;
    }
} public class AnecdoteFragment extends TitledFragment implements
        SwipeRefreshLayout.OnRefreshListener,
        AdapterListener {

    private static final String TAG                      = AnecdoteFragment.class.getSimpleName();
    private static final String ARGS_WEBSITE_PARENT_NAME = "key_website_parent_name";
    private static final String ARGS_WEBSITE_PARENT_SLUG = "key_website_parent_slug";
    public static final  String ARGS_WEBSITE_PAGE_SLUG   = "key_website_page_slug";
    private static final String ARGS_WEBSITE_NAME        = "key_website_name";

    /**
     * Define the threshold to load new items. It's the number of items not visible after the current last visible.
     */
    public static final int PREFETECH_THRESHOLD = 4;

    @BindView(R.id.swipeRefreshLayout)
    public SwipeRefreshLayout  mSwipeRefreshLayout;
    @BindView(R.id.recyclerViewContainer)
    public EnhancedFrameLayout mRecyclerViewContainer;
    @BindView(R.id.recyclerView)
    public RecyclerView        mRecyclerView;

    private   String              mWebsiteName;
    protected String              mWebsiteSlug;
    protected String              mPageSlug;
    protected String              mWebsiteAndPageName;
    protected AnecdoteAdapter     mAdapter;
    @Nullable
    protected AnecdoteService     mAnecdoteService;
    @Inject
    protected FavoritesRepository mFavoritesRepository;

    private LinearLayoutManager mLayoutManager;
    private boolean             mIsLoadingNewItems;
    private boolean             mNoData;
    private boolean             mEverythingLoaded;
    private int                 mNextPageNumber;
    private Unbinder            mUnbinder;

    public static AnecdoteFragment newInstance(Website website, WebsitePage page) {
        AnecdoteFragment fragment = new AnecdoteFragment();
        Bundle bundle = new Bundle();
        bundle.putString(AnecdoteFragment.ARGS_WEBSITE_PARENT_NAME, website.name);
        bundle.putString(AnecdoteFragment.ARGS_WEBSITE_PARENT_SLUG, website.slug);
        bundle.putString(AnecdoteFragment.ARGS_WEBSITE_PAGE_SLUG, page.slug);
        bundle.putString(AnecdoteFragment.ARGS_WEBSITE_NAME, website.name + " " + page.name);
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

    // Inflate the view for the fragment based on layout XML
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment_anecdote, container, false);
        mUnbinder = ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mUnbinder.unbind();
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        Scope scope = Toothpick.openScopes(getActivity().getApplicationContext(), this);
        Toothpick.inject(this, scope);
        mFavoritesRepository.setup();

        mLayoutManager = new LinearLayoutManager(getActivity());

        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.addOnScrollListener(mOnScrollListener);

        mSwipeRefreshLayout.setOnRefreshListener(this);

        init();
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.main, menu);
    }

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

        EventBus.getDefault().register(this);
        EventBus.getDefault().post(new ChangeTitleEvent(this, mPageSlug, true));

        EventTracker.trackFragmentView(this, mWebsiteAndPageName, EventTracker.CONTENT_TYPE_ANECDOTE);
    }

    @Override
    public void onPause() {
        super.onPause();
        EventBus.getDefault().unregister(this);
    }


    @Override
    public String getTitle() {
        return mWebsiteName;
    }

    public String getWebsitePageSlug() {
        return mPageSlug;
    }

    protected void init() {
        if (getArguments() != null) {
            mWebsiteName = getArguments().getString(ARGS_WEBSITE_PARENT_NAME);
            mWebsiteSlug = getArguments().getString(ARGS_WEBSITE_PARENT_SLUG);
            mPageSlug = getArguments().getString(ARGS_WEBSITE_PAGE_SLUG);
            mWebsiteAndPageName = getArguments().getString(ARGS_WEBSITE_NAME);
        }

        // Get text pref
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
        int textSize = Integer.parseInt(preferences.getString(getString(R.string.pref_textsize_key), String.valueOf(getResources().getInteger(R.integer.anecdote_textsize_default))));
        boolean rowStripping = preferences.getBoolean(getString(R.string.pref_rowstriping_key), getResources().getBoolean(R.bool.pref_rowstripping_default));

        mAnecdoteService = ((MainActivity) getActivity()).getAnecdoteService(mPageSlug);

        if (mAnecdoteService == null) {
            Log.e(TAG, "Unable to get an AnecdoteService");
            return;
        }

        mAdapter = new MixedContentAdapter(this, mWebsiteAndPageName);
        mAdapter.setLoaderDisplay(!mAnecdoteService.getWebsitePage().isSinglePage);

        mAdapter.setData(mAnecdoteService.getAnecdotes());
        mRecyclerView.setAdapter((RecyclerView.Adapter) mAdapter);

        // Set default values
        mIsLoadingNewItems = false;

        int colorBackground;
        int colorBackgroundStripping;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            colorBackground = getResources().getColor(R.color.rowColorBackground, null);
            colorBackgroundStripping = getResources().getColor(R.color.rowColorBackgroundStripping, null);
        } else {
            // noinspection deprecation
            colorBackground = getResources().getColor(R.color.rowColorBackground);
            // noinspection deprecation
            colorBackgroundStripping = getResources().getColor(R.color.rowColorBackgroundStripping);
        }
        mAdapter.setTextStyle(textSize, rowStripping, colorBackground, colorBackgroundStripping);
    }

    /**
     * To be called by child fragment when a request if finished, could be an error or success.
     *
     * @param dataChanged true if the dataSet has changed, like new Anecdote is here!
     */
    protected void afterRequestFinished(boolean dataChanged) {
        mIsLoadingNewItems = false;

        if (dataChanged && mAnecdoteService != null) {
            if (mAnecdoteService.getAnecdotes().size() == 0) {
                // We don't have any data to display, we prevent any more request and display a nice friendly message.
                mNoData = true;
                mRecyclerViewContainer.displayOverlay(R.layout.overlay_nodata);
                return;
            } else {
                if (mAdapter.getContentItemCount() > 0 &&
                        mAdapter.getContentItemCount() >= mAnecdoteService.getAnecdotes().size()) {
                    mAdapter.setLoaderDisplay(false);
                    mEverythingLoaded = true;
                } else {
                    mAdapter.setLoaderDisplay(true);
                }
                mNextPageNumber++;
                mAdapter.setData(mAnecdoteService.getAnecdotes());
            }
        }

        mRecyclerViewContainer.hideOverlay();

        if (shouldLoadNewAnecdote()) {
            Log.d(TAG, "afterRequestFinished, load new anecdotes");
            loadNewAnecdotes(mNextPageNumber);
        }

    }

    /**
     * Post a new event to load a new event page
     *
     * @param page the page to load
     */
    protected void loadNewAnecdotes(int page) {
        mIsLoadingNewItems = true;
        EventBus.getDefault().post(new LoadNewAnecdoteEvent(mPageSlug, page));
    }

    /**
     * Open the given anecdote content in fullscreen if it's an image or video, else in the browser.
     *
     * @param anecdote anecdote to open
     * @param view     view to have nice transition if possible
     */
    private void fullscreenAnecdote(Anecdote anecdote, View view) {
        if (anecdote.media == null) {
            EventBus.getDefault().post(new OpenAnecdoteEvent(mWebsiteAndPageName, anecdote, false));
            return;
        }

        if (anecdote.type == null || mAnecdoteService == null) {
            Log.w(TAG, "Fullscreen not possible");
            return;
        }

        Intent intent = FullscreenActivity.createIntent(
                getContext(),
                mAnecdoteService.getAnecdotes().indexOf(anecdote),
                anecdote,
                mPageSlug,
                view);


        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            startActivity(
                    intent,
                    ActivityOptions.makeSceneTransitionAnimation(
                            getActivity(),
                            view,
                            view.getTransitionName()).toBundle()
            );
        } else {
            startActivity(intent);
        }
    }


    private RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            // Scrolled to bottom. Do something here.
            if (shouldLoadNewAnecdote()) {
                Log.d(TAG, "Scrolled to end, load new anecdotes");
                loadNewAnecdotes(mNextPageNumber);
            }
        }
    };

    /**
     * Define the condition to preload enw anecdote before arriving at the end of the list
     *
     * @return true if should preload, false otherweise
     */
    private boolean shouldLoadNewAnecdote() {
        int totalItemCount = mAdapter.getContentItemCount();
        int lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
        boolean willPrefetch = (lastVisibleItem >= (totalItemCount - PREFETECH_THRESHOLD));

        // Scrolled to bottom. Do something here.
        if (!mIsLoadingNewItems && willPrefetch && !mNoData && !mEverythingLoaded) {
            //noinspection RedundantIfStatement
            if (mAnecdoteService != null && mAnecdoteService.getWebsitePage().isSinglePage) {
                return false;
            }
            return true;
        }
        return false;
    }

    /***************************
     * Implement SwipeRefreshLayout.OnRefreshListener
     ***************************/

    @Override
    public void onRefresh() {
        if (mAnecdoteService == null) {
            return;
        }
        mSwipeRefreshLayout.setRefreshing(false);

        // Reset the no data flag
        mNoData = mEverythingLoaded = false;
        mAnecdoteService.cleanAnecdotes();
        mAdapter.setData(new ArrayList<Anecdote>());
        loadNewAnecdotes(mNextPageNumber = 0);
    }


    /***************************
     * Implement ViewHolderListener
     **************************/

    @Override
    public void onClick(Anecdote anecdote, View view, int action) {
        switch (action) {
            default:
            case AdapterListener.ACTION_COPY:
                EventBus.getDefault().post(new CopyAnecdoteEvent(mWebsiteAndPageName, anecdote, CopyAnecdoteEvent.TYPE_ANECDOTE, anecdote.getShareString(getContext())));
                break;
            case AdapterListener.ACTION_SHARE:
                EventBus.getDefault().post(new ShareAnecdoteEvent(mWebsiteAndPageName, anecdote, anecdote.getShareString(getContext())));
                break;
            case AdapterListener.ACTION_OPEN_IN_BROWSER_PRELOAD:
                EventBus.getDefault().post(new OpenAnecdoteEvent(mWebsiteAndPageName, anecdote, true));
                break;
            case AdapterListener.ACTION_OPEN_IN_BROWSER:
                EventBus.getDefault().post(new OpenAnecdoteEvent(mWebsiteAndPageName, anecdote, false));
                break;
            case AdapterListener.ACTION_FULLSCREEN:
                fullscreenAnecdote(anecdote, view);
                break;
            case AdapterListener.ACTION_FAVORIS:
                EventBus.getDefault().post(new FavoritesEvent(mPageSlug, anecdote, !anecdote.isFavorite()));
                break;
        }
    }

    @Override
    public void onLongClick(final Object object) {
        // Nothing here
    }


    /***************************
     * Event
     ***************************/

    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
    public void onRequestFailedEvent(RequestFailedEvent event) {
        if (event.originalEvent instanceof LoadNewAnecdoteEvent &&
                !((LoadNewAnecdoteEvent) event.originalEvent).websitePageSlug.equals(mPageSlug)) return;

        EventBus.getDefault().removeStickyEvent(event.getClass());
        afterRequestFinished(false);
    }

    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
    public void onAnecdoteReceived(OnAnecdoteLoadedEvent event) {
        EventBus.getDefault().removeStickyEvent(event.getClass());
        afterRequestFinished(true);
    }

    @Subscribe
    public void onUpdateAnecdoteFragment(UpdateAnecdoteFragmentEvent event) {
        init();
        EventBus.getDefault().post(new ChangeTitleEvent(this, mPageSlug, true));
    }

    @Subscribe
    public void onUpdateAnecdote(UpdateAnecdoteEvent event) {
        if (mAnecdoteService != null) {
            afterRequestFinished(true);
        }
    }
} public abstract class AnecdoteService {

    protected String               mServiceName;
    protected Website              mWebsite;
    protected WebsitePage          mWebsitePage;
    protected List<Anecdote>       mAnecdotes;

    @Inject
    protected FavoritesRepository mFavoritesRepository;

    public AnecdoteService(Website website, WebsitePage websitePage) {
        mWebsite = website;
        mWebsitePage = websitePage;
        mServiceName = mWebsite.name.replaceAll("\\s", "") + websitePage.name + this.getClass().getSimpleName();

        mAnecdotes = new CopyOnWriteArrayList<>();
    }

    /**
     * Get the Website object
     *
     * @return website object
     */
    public Website getWebsite() {
        return mWebsite;
    }
    /**
     * Get the Website page object
     *
     * @return website page object
     */
    public WebsitePage getWebsitePage() {
        return mWebsitePage;
    }

    /**
     * @return the full website page name which looks like eg. “9GAG Latest"
     */
    public String getPageFullName(){
        return mWebsite.name + " " + mWebsitePage.name;
    }

    /**
     * Return the list of anecdotes already loaded by the service
     *
     * @return list of anecdote
     */
    public List<Anecdote> getAnecdotes() {
        return mAnecdotes;
    }

    /**
     * Remvoe all anecdotes
     */
    public void cleanAnecdotes() {
        mAnecdotes.clear();
    }
    /**
     * Download the given page and parse it
     *
     * @param event      original event
     * @param pageNumber the page to download
     */
    protected abstract void downloadLatest(@NonNull final Event event, final int pageNumber);


    protected abstract void onFavoriteChange(boolean settedAsFavorite, Anecdote anecdote);

    /**
     * Post an Event ot UI Thread
     *
     * @param event the event to post on Bus
     */
    protected void postOnUiThread(final Event event) {
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                if (event instanceof OnAnecdoteLoadedEvent || event instanceof RequestFailedEvent) {
                    EventBus.getDefault().postSticky(event);
                } else {
                    EventBus.getDefault().post(event);
                }
            }
        });
    }

    @Subscribe
    public void loadNextAnecdoteEvent(LoadNewAnecdoteEvent event) {
        if (!event.websitePageSlug.equals(mWebsitePage.slug)) return;
        int pageNumber = event.page;
        downloadLatest(event, pageNumber);
    }

    @Subscribe
    public void setFavoriteAnecdote(FavoritesEvent event){
        int i;
        Anecdote anecdote;
        for (i = 0; i < mAnecdotes.size(); i++) {
            anecdote = mAnecdotes.get(i);
            if(anecdote.equals(event.anecdote)){
                if(event.setAsFavorite){
                    onFavoriteChange(true, mFavoritesRepository.addFavorite(mWebsite.name + " " + mWebsitePage.name, anecdote));
                } else {
                    onFavoriteChange(false, mFavoritesRepository.removeFavorite(anecdote));
                }
                break;
            }
        }

        if (!event.websitePageSlug.equals(mWebsitePage.slug)) return;

        postOnUiThread(new UpdateAnecdoteEvent(mWebsitePage.slug, i));
    }

} public class WebsiteApiService {

    private static final String TAG = WebsiteApiService.class.getSimpleName();
    @Nullable
    protected LoadRemoteWebsiteEvent mFailedEvent;
    private   OkHttpClient           mOkHttpClient;
    private   List<Website>          mWebsites;
    private   Request                mCurrentRequest;

    public WebsiteApiService() {

        mOkHttpClient = new OkHttpClient();
    }

    /**
     * Download remote website configuration
     *
     * @param event the original event
     */
    private void getRemoteSetting(final LoadRemoteWebsiteEvent event) {
        mCurrentRequest = new Request.Builder()
                .url(Configuration.API_URL)
                .build();

        Log.d(TAG, "newCall");

        mOkHttpClient.newCall(mCurrentRequest).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, final IOException e) {
                Log.e(TAG, "onFailure", e);
                mCurrentRequest = null;
                mFailedEvent = event;
                postEventToMainThread(new RequestFailedEvent(
                        event,
                        RequestFailedEvent.ERROR_NOINTERNET,
                        "",
                        e));
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                mCurrentRequest = null;
                if (!response.isSuccessful()) {
                    mFailedEvent = event;
                    postEventToMainThread(new RequestFailedEvent(
                            event,
                            RequestFailedEvent.ERROR_SERVER,
                            "",
                            null));
                    return;
                }
                Log.d(TAG, "onResponse");
                // We are not on main thread
                String websitesString = response.body().string();

                websitesString = java.net.URLDecoder.decode(websitesString, "UTF-8");

                Type type = new TypeToken<HashMap<String, Website>>() {
                }.getType();

                Map<String, Website> websites = new Gson().fromJson(websitesString, type);

                if (websites == null || websites.isEmpty()) {
                    return;
                }

                mWebsites = new ArrayList<>();

                for (Map.Entry<String, Website> entry : websites.entrySet()) {
                    mWebsites.add(entry.getValue());
                }

                postEventToMainThread(new OnRemoteWebsiteResponseEvent(true, mWebsites));
            }
        });
    }

    /**
     * Post given event to the main applciation thread (ui threa)d
     *
     * @param event the event to send via an event bus
     */
    private void postEventToMainThread(final Event event) {
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                EventBus.getDefault().post(event);
            }
        });
    }

    /**
     * Check if the service has downloaded the websites
     */
    public boolean isWebsitesDownloaded() {
        return mWebsites != null;
    }

    @Nullable
    public List<Website> getWebsites() {
        return mWebsites;
    }

    /***************************
     * Event
     ***************************/

    @Subscribe
    public void loadWebsite(LoadRemoteWebsiteEvent event) {
        if (mWebsites != null && !mWebsites.isEmpty()) {
            EventBus.getDefault().post(new OnRemoteWebsiteResponseEvent(true, mWebsites));
            return;
        }

        if (mCurrentRequest != null) {
            return;
        }

        if (mFailedEvent == null) {
            getRemoteSetting(event);
        } else {
            getRemoteSetting(mFailedEvent);
        }

    }

    /**
     * Called by child service
     *
     * @param connectivityEvent an event fired when the network connectivity change
     */
    @Subscribe
    public void onConnectivityChangeListener(NetworkConnectivityChangeEvent connectivityEvent) {
        if (connectivityEvent.state != NetworkConnectivityListener.State.CONNECTED) {
            return;
        }
        if (mFailedEvent != null) {
            getRemoteSetting(mFailedEvent);
        }
    }

} public class WebsiteChooserFragment extends Fragment implements WebsiteViewHolderListener {

    @SuppressWarnings("unused")
    public static final String TAG = WebsiteChooserFragment.class.getSimpleName();

    public static final String BUNDLE_MODE_KEY     = "modeKey";
    public static final String BUNDLE_MODE_RESTORE = "restore";
    public static final String BUNDLE_MODE_ADD     = "add";

    @BindView(R.id.recyclerView)
    public RecyclerView mRecyclerView;

    @BindView(R.id.saveButton)
    public Button mSaveButton;

    @Nullable
    protected String                mMode;
    private   WebsiteChooserAdapter mAdapter;
    private   List<Website>         mSelectedWebsites;
    private   Unbinder              mUnbinder;

    public static WebsiteChooserFragment newInstance(String mode) {
        WebsiteChooserFragment fragment = new WebsiteChooserFragment();
        Bundle bundle = new Bundle();
        bundle.putString(BUNDLE_MODE_KEY, mode);
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_websitechooser, container, false);
        mUnbinder = ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        if (getArguments() != null) {
            mMode = getArguments().getString(WebsiteChooserFragment.BUNDLE_MODE_KEY, null);
        }

        mSelectedWebsites = new ArrayList<>();
        mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        mAdapter = new WebsiteChooserAdapter(this);
        mRecyclerView.setAdapter(mAdapter);

        if (!TextUtils.isEmpty(mMode) && mMode.equals(BUNDLE_MODE_ADD)) {
            mSaveButton.setText(R.string.dialog_website_add);
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mUnbinder.unbind();
    }

    @Override
    public void onResume() {
        super.onResume();
        EventBus.getDefault().register(this);
        EventBus.getDefault().post(new ChangeTitleEvent(getString(R.string.dialog_websitechooser_title)));

        MainActivity mainActivity = ((MainActivity) getActivity());
        if (mainActivity.getWebsiteApiService().isWebsitesDownloaded()) {
            setAdapterData(mainActivity.getWebsiteApiService().getWebsites());
        } else {
            EventBus.getDefault().post(new LoadRemoteWebsiteEvent());
        }

        EventTracker.trackFragmentView(this, null, EventTracker.CONTENT_TYPE_APP);
    }

    @Override
    public void onPause() {
        super.onPause();
        EventBus.getDefault().unregister(this);
    }

    @OnClick(R.id.saveButton)
    public void onSaveButtonClicked() {
        // If we are on the first launch, we assume the user didn't have any Websites, so we override it.
        // Else, we add the selected one
        if (TextUtils.isEmpty(mMode)) {
            SpStorage.saveWebsites(getActivity(), mSelectedWebsites);
            SpStorage.setFirstLaunch(getActivity(), false);
            EventBus.getDefault().post(new WebsitesChangeEvent(true));
        } else if (mMode.equals(BUNDLE_MODE_RESTORE)) {
            SpStorage.saveWebsites(getActivity(), mSelectedWebsites);
            EventTracker.trackWebsitesRestored();
            EventBus.getDefault().post(new WebsitesChangeEvent(true));
        } else if (mMode.equals(BUNDLE_MODE_ADD)) {
            for (Website website : mSelectedWebsites) {
                SpStorage.saveWebsite(getActivity(), website);
            }
            EventBus.getDefault().post(new WebsitesChangeEvent(false));
        }
    }

    /**
     * Set the adapter data by copying the given data to a new object and removing already added website.
     * It will also sort the list by the most liked website first
     *
     * @param websites data to display
     */
    private void setAdapterData(List<Website> websites) {
        List<Website> websites1 = new ArrayList<>();
        websites1.addAll(websites);
        if (!websites1.isEmpty()) {
            if (!TextUtils.isEmpty(mMode) && !mMode.equals(BUNDLE_MODE_RESTORE)) {
                // We want to add some websites : remove duplicates or already added ones
                List<Website> savedWebsites = SpStorage.getWebsites(getActivity());

                // We cannot iterate on a list and remove item at the same time, need an array
                for (Website website : websites1.toArray(new Website[websites1.size()])) {
                    if (savedWebsites.contains(website)) {
                        websites1.remove(website);
                    }
                }
            }

            Collections.sort(websites1, new Comparator<Website>() {
                @Override
                public int compare(Website website, Website website2) {
                    if (website.like > website2.like) {
                        return -1;
                    } else if (website.like < website2.like) {
                        return 1;
                    }
                    return 0;
                }
            });

            mAdapter.setData(websites1);
        }
    }

    /***************************
     * Event
     ***************************/

    @Subscribe
    public void onRemoteWebsiteLoaded(OnRemoteWebsiteResponseEvent event) {
        Log.d(TAG, "onRemoteWebsiteLoaded, count? " + event.websiteList.size());
        if (event.isSuccessful) {
            setAdapterData(event.websiteList);
        } else {
            Toast
                    .makeText(
                            getActivity(),
                            getActivity().getString(R.string.error_server_unknown),
                            Toast.LENGTH_SHORT)
                    .show();
        }
    }

    /***************************
     * ViewHolderListener
     ***************************/

    @Override
    public void onClick(Object object) {
        if (object instanceof Integer) {
            FragmentManager fm = getActivity().getSupportFragmentManager();
            DialogFragment dialogFragment = WebsiteDialogFragment.newInstance(null);
            dialogFragment.show(fm, dialogFragment.getClass().getSimpleName());
        } else if (object instanceof Website) {

            Website website = (Website) object;
            if (mSelectedWebsites.contains(website)) {
                mSelectedWebsites.remove(website);
            } else {
                mSelectedWebsites.add(website);
            }
        }
    }
} public class LoadRemoteWebsiteEvent implements Event {
} public class OnRemoteWebsiteResponseEvent implements Event {

    public boolean       isSuccessful;
    public List<Website> websiteList;

    public OnRemoteWebsiteResponseEvent(boolean isSuccessful, List<Website> websiteList) {
        this.isSuccessful = isSuccessful;
        this.websiteList = websiteList;
    }
} public class Website {

    public static final String SOURCE_LOCAL  = "local";
    public static final String SOURCE_REMOTE = "remote";
    public static final String SOURCE_CALCULATED = "calc";

    public int    version;
    public String slug;
    public String name;
    public int    color;
    public int    like;
    public String source;
    public String userAgent;

    public List<WebsitePage> pages;

    public Website() {
        this(null, SOURCE_LOCAL);
    }

    public Website(String name, String source) {
        this.pages = new ArrayList<>();
        this.source = source;
        this.name = name;
    }

    /**
     * Validate this object by preventing any crash when using it
     */
    public void validateData() {
        if (TextUtils.isEmpty(name)) {
            name = Long.toHexString(Double.doubleToLongBits(Math.random()));
        }

        for(WebsitePage websitePage : pages){
            websitePage.validate();
        }

        if (TextUtils.isEmpty(slug)) {
            if (source.equals(SOURCE_REMOTE)) {
                slug = "api-" + name;
            } else if (source.equals(SOURCE_CALCULATED)) {
                slug = "calc-" + name;
            } else {
                slug = "local-" + name;
            }
        }
    }

    /**
     * Check if the version missmatch between the two object
     *
     * @param website the new website
     * @return true if up to date, false otherweise
     */
    public boolean isUpToDate(Website website) {
        return website.version <= version;
    }

    /**
     * Check if the user can edit this website manually or not
     *
     * @return true if editable
     */
    public boolean isEditable() {
        return source.equals(SOURCE_LOCAL);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Website website = (Website) o;

        if (!slug.equals(website.slug)) return false;
        return source.equals(website.source);

    }

    @Override
    public int hashCode() {
        int result = slug.hashCode();
        result = 31 * result + source.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return "Website{" +
                "version=" + version +
                ", slug='" + slug + '\'' +
                ", name='" + name + '\'' +
                ", color=" + color +
                ", like=" + like +
                ", source='" + source + '\'' +
                ", userAgent='" + userAgent + '\'' +
                ", pages=" + pages +
                '}';
    }
} public class WebsitePage {

    public String     name;
    public String     slug;
    public String     url;
    public String     selector;
    public String     urlSuffix;
    public boolean    isFirstPageZero;
    public boolean    isSinglePage;
    public Content    content;
    /**
     * If a pagination is not null, so the first page to get if the root website (like http://9gag.com) and the
     * other page url are getted from the last page launch using this field.
     */
    @Nullable
    public Pagination pagination;

    /**
     * Get the page url from the given page number.
     *
     * @param pageNumber    the page to get the url from
     * @param paginationMap the paginationMap is any, to try to get the url from
     * @return the url that represent the page
     */
    public String getPageUrl(int pageNumber, @Nullable Map<Integer, String> paginationMap) {
        if (pagination == null) {
            return url +
                    ((isFirstPageZero) ? pageNumber : pageNumber + 1) +
                    ((urlSuffix == null)? "" :  urlSuffix);
        } else if (pageNumber != 0 && paginationMap != null && paginationMap.containsKey(pageNumber)) {
            return paginationMap.get(pageNumber);
        } else {
            return url;
        }
    }

    /**
     * Validate inner data to prevent usage when using it
     */
    public void validate() {
        if(TextUtils.isEmpty(slug)){
            slug = "local-" + name;
        }
        if(content == null){
            content = new Content();
        }
    }
} public class ChangeTitleEvent implements Event {

    public String  title;
    @Nullable
    public String  additionalTitle;
    public boolean spinnerEnable;

    public ChangeTitleEvent(String title) {
        this.title = title;
        this.spinnerEnable = false;
    }

    public ChangeTitleEvent(TitledFragment fragment, String additionalSlug, boolean spinnerEnable) {
        this.title = fragment.getTitle();
        this.additionalTitle = additionalSlug;
        this.spinnerEnable = spinnerEnable;
    }
} public class DisplaySnackbarEvent {

    public String toastMessage;
    public String actionString;
    public Intent intentToRun;
    public int duration;

    public DisplaySnackbarEvent(String toastMessage, String actionString, Intent intentToRun, int duration) {
        this.toastMessage = toastMessage;
        this.actionString = actionString;
        this.intentToRun = intentToRun;
        this.duration = duration;
    }
} public interface Event {
} public class NetworkConnectivityChangeEvent implements Event {

    public NetworkConnectivityListener.State state;

    public NetworkConnectivityChangeEvent(NetworkConnectivityListener.State state) {
        this.state = state;
    }
} public class RequestFailedEvent implements Event {

    public static final int ERROR_GENERAL      = 0;
    public static final int ERROR_LOADFAIL     = 1;
    public static final int ERROR_PROCESSING   = 2;
    public static final int ERROR_RESPONSEFAIL = 3;
    public static final int ERROR_PARSING      = 4;
    public static final int ERROR_NOINTERNET   = 5;
    public static final int ERROR_SERVER       = 6;
    public static final int ERROR_WRONGCONFIG  = 7;

    public  Event     originalEvent;
    public  int       error;
    private String    websiteName;
    @Nullable
    public  Exception exception;
    public  String    additionalError;

    public RequestFailedEvent(Event originalEvent,
                              int error,
                              String websiteName,
                              @Nullable Exception exception) {
        this(originalEvent, error, websiteName, exception, null);

    }

    public RequestFailedEvent(Event originalEvent,
                              int error,
                              String websiteName,
                              @Nullable Exception exception,
                              @Nullable String additionalError) {
        this.originalEvent = originalEvent;
        this.error = error;
        this.websiteName = websiteName;
        this.additionalError = additionalError;
        this.exception = exception;
    }

    /**
     * Format the current fail event to return an human readable message
     * @param context app context to get resources from
     * @return the human readable error message
     */
    public String formatErrorMessage(Context context){
        switch (error){
            default:
            case ERROR_GENERAL:
                return context.getResources().getString(R.string.error_general);
            case ERROR_NOINTERNET:
                return context.getResources().getString(R.string.error_nointernet);
            case ERROR_SERVER:
                return context.getResources().getString(R.string.error_server);
            case ERROR_WRONGCONFIG:
                return context.getResources().getString(R.string.error_wrongconfig);
            case ERROR_LOADFAIL:
                return context.getResources().getString(R.string.error_loadfail, websiteName);
            case ERROR_PROCESSING:
                return context.getResources().getString(R.string.error_processing);
            case ERROR_RESPONSEFAIL:
                return context.getResources().getString(R.string.error_responsefail, additionalError);
            case ERROR_PARSING:
                return context.getResources().getString(R.string.error_parsing, websiteName);
        }
    }

    @Override
    public String toString() {
        return "RequestFailedEvent{" +
                "error='" + error + '\'' +
                ", websiteName=" + websiteName + " " +
                ", exception=" + exception +
                '}';
    }
} public class RequestPermissionEvent implements Event {

    public String permission;
    public Event callbackEvent;

    public RequestPermissionEvent(String permission, Event callbackEvent) {
        this.permission = permission;
        this.callbackEvent = callbackEvent;
    }
} public class ResetAppEvent implements Event {
} public class WebsitesChangeEvent implements Event {

    public boolean fromWebsiteChooserOverride;

    public WebsitesChangeEvent() {
    }

    public WebsitesChangeEvent(boolean fromWebsiteChooserOverride) {
        this();
        this.fromWebsiteChooserOverride = fromWebsiteChooserOverride;
    }
} public class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {

    @Override
    public void onCreatePreferences(Bundle bundle, String s) {
        addPreferencesFromResource(R.xml.pref_general);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        Preference myPref = findPreference(getString(R.string.pref_reset_key));
        myPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
            public boolean onPreferenceClick(Preference preference) {
                EventBus.getDefault().post(new ResetAppEvent());
                return true;
            }
        });

    }

    @Override
    public void onResume() {
        super.onResume();
        EventBus.getDefault().post(new ChangeTitleEvent(getString(R.string.action_settings)));
        EventTracker.trackFragmentView(this, null, EventTracker.CONTENT_TYPE_APP);
        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
    }

    @Override
    public void onPause() {
        super.onPause();
        EventBus.getDefault().post(new UpdateAnecdoteFragmentEvent());
        getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
    }

    /***************************
     * implements SharedPreferences.OnSharedPreferenceChangeListener
     ***************************/

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        String value = "";
        if (key.equals(getString(R.string.pref_rowstriping_key))) {
            value = String.valueOf(sharedPreferences.getBoolean(key, false));
        } else if (key.equals(getString(R.string.pref_textsize_key))) {
            value = String.valueOf(sharedPreferences.getString(key, null));
        }

        EventTracker.trackSettingChange(key, value);
    }

} public class SpStorage {

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

    public static final String SP_KEY                   = "io.gresse.hugo.anecdote.1";
    public static final String SP_KEY_VERSION           = "version";
    public static final String SP_KEY_FIRSTLAUNCH       = "firstLaunch";
    public static final String SP_KEY_WEBSITES          = "websites";
    public static final String SP_KEY_WEBSITE_REMOTE_NB = "websitesRemoteNumber";

    /**
     * Return the version code number
     *
     * @param context        app context
     * @param defaultVersion the default value if not any already saved
     * @return the current version
     */
    public static int getVersion(Context context, int defaultVersion) {
        SharedPreferences sharedPreferences = context.getSharedPreferences(SP_KEY, Context.MODE_PRIVATE);
        return sharedPreferences.getInt(SP_KEY_VERSION, defaultVersion);
    }

    /**
     * Set the current sharedpref versions, matching app versionCode
     *
     * @param context app context
     * @param version the version to switch to
     */
    public static void setVersion(Context context, int version) {
        SharedPreferences.Editor sharedPreferencesEditor = context.getSharedPreferences(SP_KEY, Context.MODE_PRIVATE).edit();
        sharedPreferencesEditor.putInt(SP_KEY_VERSION, version);
        sharedPreferencesEditor.apply();
    }

    /**
     * Check if it's the first application launch or not
     *
     * @param context app context
     * @return true if first launch, false otherwise
     */
    public static boolean isFirstLaunch(Context context) {
        SharedPreferences sharedPreferences = context.getSharedPreferences(SP_KEY, Context.MODE_PRIVATE);
        return sharedPreferences.getBoolean(SP_KEY_FIRSTLAUNCH, true);
    }

    /**
     * Set first launch pref or the app
     *
     * @param context       app context
     * @param isFirstLaunch true if the app is in first launch, false otherweise
     */
    public static void setFirstLaunch(Context context, boolean isFirstLaunch) {
        SharedPreferences.Editor sharedPreferencesEditor = context.getSharedPreferences(SP_KEY, Context.MODE_PRIVATE).edit();
        sharedPreferencesEditor.putBoolean(SP_KEY_FIRSTLAUNCH, isFirstLaunch);
        sharedPreferencesEditor.apply();
    }

    /**
     * Get the raw json string for the websites key
     *
     * @param context app context
     * @return websites json
     */
    public static String getWebsitesString(Context context) {
        SharedPreferences sharedPreferences = context.getSharedPreferences(SP_KEY, Context.MODE_PRIVATE);
        return sharedPreferences.getString(SP_KEY_WEBSITES, "");
    }

    /**
     * Get the list of user Website to be displayed in the app
     *
     * @param context app context
     * @return the list of website to be displayed
     */
    public static List<Website> getWebsites(Context context) {
        SharedPreferences sharedPreferences = context.getSharedPreferences(SP_KEY, Context.MODE_PRIVATE);

        String settingString = sharedPreferences.getString(SP_KEY_WEBSITES, "");

        if (TextUtils.isEmpty(settingString)) {
            return new ArrayList<>();
        }

        Type listType = new TypeToken<ArrayList<Website>>() {
        }.getType();

        return new Gson().fromJson(settingString, listType);
    }

    /**
     * Update all saved websites config, override alreayd saved ones
     *
     * @param context  app context
     * @param websites list of websites to save
     */
    public static void saveWebsites(Context context, List<Website> websites) {
        SharedPreferences.Editor sharedPreferencesEditor = context.getSharedPreferences(SP_KEY, Context.MODE_PRIVATE).edit();
        for (Website website : websites) {
            website.validateData();
            for (WebsitePage websitePage : website.pages) {
                websitePage.content.reorderItems();
            }
        }
        sharedPreferencesEditor.putString(SP_KEY_WEBSITES, new Gson().toJson(websites));
        sharedPreferencesEditor.apply();
    }

    /**
     * Save or add a unique website.
     *
     * @param context app context
     * @param website website to save
     */
    public static void saveWebsite(Context context, Website website) {
        List<Website> websites = getWebsites(context);

        website.validateData();
        for (WebsitePage websitePage : website.pages) {
            websitePage.content.reorderItems();
        }

        Website currentWebsite;
        for (int i = 0; i < websites.size(); i++) {
            currentWebsite = websites.get(i);
            if (currentWebsite.equals(website)) {
                websites.set(i, website);
                saveWebsites(context, websites);
                return;
            }
        }

        websites.add(website);
        saveWebsites(context, websites);
    }

    /**
     * Delete/remove given website for preferences
     *
     * @param context app context
     * @param website website to remove
     */
    public static void deleteWebsite(Context context, Website website) {
        List<Website> websites = getWebsites(context);

        for (Website websiteTemp : websites) {
            if (websiteTemp.equals(website)) {
                websites.remove(website);
                break;
            }
        }
        saveWebsites(context, websites);
    }

    /**
     * Set given website as default/first website on the list
     *
     * @param context app context
     * @param website website to set default on
     */
    public static void setDefaultWebsite(Context context, Website website) {
        List<Website> websites = getWebsites(context);

        int websiteToSwap = -1;
        for (int i = 0; i < websites.size(); i++) {
            if (websites.get(i).equals(website)) {
                websiteToSwap = i;
                break;
            }
        }
        Collections.swap(websites, 0, websiteToSwap);
        saveWebsites(context, websites);
    }

    /**
     * Get the number of websites last time checked
     *
     * @param context app context
     * @return number of websites
     */
    public static int getSavedRemoteWebsiteNumber(Context context) {
        SharedPreferences sharedPreferences = context.getSharedPreferences(SP_KEY, Context.MODE_PRIVATE);
        return sharedPreferences.getInt(SP_KEY_WEBSITE_REMOTE_NB, 0);
    }

    /**
     * Set saved remite website number
     *
     * @param context app context
     * @param number  the number of remote website last time checked
     */
    public static void setSavedRemoteWebsiteNumber(Context context, int number) {
        SharedPreferences.Editor sharedPreferencesEditor = context.getSharedPreferences(SP_KEY, Context.MODE_PRIVATE).edit();
        sharedPreferencesEditor.putInt(SP_KEY_WEBSITE_REMOTE_NB, number);
        sharedPreferencesEditor.apply();
    }

    /**
     * Migrate the current saved data to new formats
     *
     * @param context app context
     */
    public static boolean migrate(Context context) {
        boolean openWebsiteChooserReturn = false;
        int latestVersion = BuildConfig.VERSION_CODE;
        switch (getVersion(context, latestVersion)){
            case 0:
                List<Website> websites = getWebsites(context);
                /**
                 * v0.4.0 migration (type in WebsiteItem)
                 *
                 * the first/previous version is considered as 0 as version was implemented starting app v0.4.0
                 */
                if (websites != null) {
                    for (Website website : websites) {
                        SpStorage.deleteWebsite(context, website);
                    }
                }
                setVersion(context, 5); // 5 = 0.4.0
                Log.i(TAG, "Migrating from 0 > 5 done");
                break;
            case 5:
                /**
                 * v1.0.0 migration : major model change
                 */
                List<Website> migratedLocalWebsites = new ArrayList<>();

                // 1. Load all stored websites
                String websitesString = SpStorage.getWebsitesString(context);

                // 2. Clear all stored websites
                SharedPreferences.Editor sharedPreferencesEditor = context.getSharedPreferences(SP_KEY, Context.MODE_PRIVATE).edit();
                sharedPreferencesEditor.putString(SP_KEY_WEBSITES, "[]");
                sharedPreferencesEditor.apply();

                // 3. Migrate local websites ONLY
                if (!TextUtils.isEmpty(websitesString)) {
                    try {
                        JSONArray jsonObj = new JSONArray(websitesString);

                        for (int i = 0; i < jsonObj.length(); i++) {
                            JSONObject object = jsonObj.getJSONObject(i);

                            String source = object.getString("source");
                            if (TextUtils.isEmpty(source) || "remote".equals(source)) {
                                // Skip remote websites. The user will need to reselect it
                                continue;
                            }

                            Website website = new Website();
                            WebsitePage websitePage = new WebsitePage();
                            websitePage.content = new Content();
                            websitePage.content.items.add(new ContentItem(MediaType.TEXT, 1));

                            website.color = object.getInt("color");
                            websitePage.isFirstPageZero = object.getBoolean("isFirstPageZero");
                            websitePage.isSinglePage = object.getBoolean("isSinglePage");
                            website.like = object.getInt("like");
                            website.name = object.getString("name");
                            websitePage.selector = object.getString("selector");
                            websitePage.name = website.name;
                            website.source = "local";
                            websitePage.url = object.getString("url");
                            websitePage.urlSuffix = object.getString("urlSuffix");

                            website.pages.add(websitePage);
                            website.validateData();

                            migratedLocalWebsites.add(website);
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }

                // 4. Save back local websites
                SpStorage.saveWebsites(context, migratedLocalWebsites);

                // 5. Change return value to help user reselect websites
                openWebsiteChooserReturn = true;

                // 6. Done, set new version
                setVersion(context, 12);
                // As the user will need to reselect websites, say to him why
                Toast.makeText(context, context.getString(R.string.app_migration_1_0_0), Toast.LENGTH_SHORT).show();
                Log.i(TAG, "Migrating from 5 > 12 done");
                break;
            default:
                setVersion(context, latestVersion);
                // Nothing
                break;
        }
        return openWebsiteChooserReturn;
    }

} public class EventTracker {

    public static final String CONTENT_TYPE_ANECDOTE = "Anecdote";
    public static final String CONTENT_TYPE_APP = "App";
    public static final String TRACKING_WEBSITE_NAME = "Website name";

    private static EventSenderInterface sEvent;

    public EventTracker(Context context) {
        if (!isEventEnable()) return;

        sEvent = new EventSender(context);
    }

    /**
     * Return true if event reporting is enable, checking the BuildConfig
     *
     * @return true if enable, false otherweise
     */
    public static boolean isEventEnable() {
        return !Configuration.DEBUG;
    }

    /**
     * Called by activities onStart
     */
    public static void onStart(Activity activity){
        if (!isEventEnable()) return;

        sEvent.onStart(activity);
    }

    /**
     * Called by activties onStopre
     */
    public static void onStop(){
        if (!isEventEnable()) return;

        sEvent.onStop();
    }
    /**
     * Track fragment view, should be called in onResume
     *
     * @param fragment   the fragment name to track
     * @param screenName the additional name if any
     * @param subName    sub view name
     */
    public static void trackFragmentView(Fragment fragment, @Nullable String screenName, @Nullable String subName) {
        if (!isEventEnable()) return;

        String name;

        if (TextUtils.isEmpty(screenName)) {
            name = fragment.getClass().getSimpleName();
        } else {
            name = screenName;
        }

        if (TextUtils.isEmpty(name)) {
            name = "ERROR";
        }

        String detail = subName;
        if (TextUtils.isEmpty(detail)) {
            detail = "Fragment";
        }

        sEvent.sendView(name, detail);
    }

    /**
     * Track an undetermined error
     *
     * @param key   the key error
     * @param value the error detail
     */
    public static void trackError(String key, String value) {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Error", key, value);
    }

    /**
     * Track when a website configuration is edited
     *
     * @param websiteName website name
     * @param isSave      if the edit is saved or not
     */
    public static void trackWebsiteEdit(String websiteName, boolean isSave) {
        if (!isEventEnable()) return;

        String mode;
        if (isSave) {
            mode = "save";
        } else {
            mode = "open";
        }

        sEvent.sendEvent("Website edit",
                "mode", mode,
                TRACKING_WEBSITE_NAME, websiteName);
    }

    /**
     * Track when a website is deleted/removed from the nagivationView
     *
     * @param websiteName website name
     */
    public static void trackWebsiteDelete(String websiteName) {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Website delete", TRACKING_WEBSITE_NAME, websiteName);
    }

    /**
     * Track when a website is set as the default/first website
     *
     * @param websiteName website name
     */
    public static void trackWebsiteDefault(String websiteName) {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Website delete", "Website set default", websiteName);
    }

    /**
     * Track when all websites are restored for new ones
     */
    public static void trackWebsitesRestored() {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Website restored");
    }

    /**
     * Track when a custom website is added
     */
    public static void trackCustomWebsiteAdded() {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Websites custom added");
    }

    /**
     * Track the copy action on an anecdote
     *
     * @param event the copy event
     */
    public static void trackAnecdoteCopy(CopyAnecdoteEvent event) {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Anecdote copied",
                TRACKING_WEBSITE_NAME, event.websiteName,
                "Type", event.type);
    }

    /**
     * Track when the anecdote is shared
     *
     * @param websiteName the website name
     */
    public static void trackAnecdoteShare(String websiteName) {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Anecdote shared",
                TRACKING_WEBSITE_NAME, websiteName);
    }

    /**
     * Track when the anecdote details is open
     *
     * @param websiteName the website name
     */
    public static void trackAnecdoteDetails(String websiteName) {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Anecdote details",
                TRACKING_WEBSITE_NAME, websiteName);
    }

    /**
     * Track when the anecdote is opened with the "Read more" or open in browser button
     *
     * @param websiteName the website name
     */
    public static void trackAnecdoteReadMore(String websiteName) {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Anecdote read more",
                TRACKING_WEBSITE_NAME, websiteName);
    }

    /**
     * Track the click on third parties library
     *
     * @param thirdPartiesName the name of the third parties
     */
    public static void trackThirdPartiesClick(String thirdPartiesName) {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Third-parties click",
                "Third-parties", thirdPartiesName);
    }

    /**
     * Track the new value assigned to a setting
     *
     * @param name  the setting key
     * @param value the setting new value
     */
    public static void trackSettingChange(String name, String value) {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Setting " + name + " changed",
                "value", value);
    }

    /**
     * Track when to data has been retrieved from this website, indicating possibly an issue with the selectors
     *
     * @param websiteName the website name
     */
    public static void trackWebsiteWrongConfiguration(String websiteName) {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Website wrong configuration",
                TRACKING_WEBSITE_NAME, websiteName);
    }

    /**
     * Track when to data has been retrieved from this website, indicating possibly an issue with the selectors
     *
     * @param websiteName the website name
     */
    public static void trackFavorite(String websiteName) {
        if (!isEventEnable()) return;

        sEvent.sendEvent("Anecdote favorite",
                TRACKING_WEBSITE_NAME, websiteName);
    }
} public class FragmentStackManager {

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

    @Nullable
    private Snackbar mSnackbar;

    public FragmentStackManager(Snackbar snackbar) {
        mSnackbar = snackbar;
    }

    /**
     * Change tu current displayed fragment by a new one.
     *
     * @param frag            the new fragment to display
     * @param saveInBackstack if we want the fragment to be in backstack
     * @param animate         if we want a nice animation or not
     * @param sharedView      the shared view for the transition
     * @param sharedName      the shared name of the transition
     */
    public void changeFragment(
                                AppCompatActivity activity,
                                Fragment frag,
                                boolean saveInBackstack,
                                boolean animate,
                                @Nullable View sharedView,
                                @Nullable String sharedName) {
        String log = "changeFragment: ";
        String backStateName = ((Object) frag).getClass().getName();

        if (frag instanceof AnecdoteFragment) {
            backStateName += frag.getArguments().getString(AnecdoteFragment.ARGS_WEBSITE_PAGE_SLUG);
        }

        try {
            android.support.v4.app.FragmentManager manager = activity.getSupportFragmentManager();
            boolean fragmentPopped = manager.popBackStackImmediate(backStateName, 0);

            if (!fragmentPopped && manager.findFragmentByTag(backStateName) == null) { //fragment not in back stack, create it.
                FragmentTransaction transaction = manager.beginTransaction();

                if (animate) {
                    log += " animate";
                    transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right);
                }
                if (sharedView != null && !TextUtils.isEmpty(sharedName)) {
                    ViewCompat.setTransitionName(sharedView, sharedName);
                    transaction.addSharedElement(sharedView, sharedName);
                }

                transaction.replace(R.id.fragment_container, frag, backStateName);

                if (saveInBackstack) {
                    log += " addToBackTack(" + backStateName + ")";
                    transaction.addToBackStack(backStateName);
                } else {
                    log += " NO addToBackTack(" + backStateName + ")";
                }

                transaction.commit();

                // If some snackbar is display, hide it
                if (mSnackbar != null && mSnackbar.isShownOrQueued()) {
                    mSnackbar.dismiss();
                }

            } else if (!fragmentPopped && manager.findFragmentByTag(backStateName) != null) {
                log += " fragment not popped but finded: " + backStateName;
            } else {
                log += " nothing to do : " + backStateName + " fragmentPopped: " + fragmentPopped;
                // custom effect if fragment is already instanciated
            }
            Log.d(TAG, log);
        } catch (IllegalStateException exception) {
            Log.w(TAG, "Unable to commit fragment, could be activity as been killed in background. " + exception.toString());
        }
    }

} public class NetworkConnectivityListener {

    public static final String TAG = NetworkConnectivityListener.class.getSimpleName();

    private State                         mState;
    private ConnectivityBroadcastReceiver mReceiver;
    private Context                       mContext;
    @Nullable
    private ConnectivityListener          mListener;
    private Handler                       mMainHandler;
    private Runnable                      mConnectivityRunnable;

    private class ConnectivityBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION) || mListener == null) {
                return;
            }

            ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
            boolean networkAvailable = networkInfo != null && networkInfo.isConnected();

            if (networkAvailable) {
                mState = State.CONNECTED;
            } else {
                mState = State.NOT_CONNECTED;
            }

            mMainHandler.post(mConnectivityRunnable);
        }
    }

    public enum State {
        UNKNOWN,

        /**
         * This state is returned if there is connectivity to any network
         **/
        CONNECTED,
        /**
         * This state is returned if there is no connectivity to any network. This is set
         * to true under two circumstances:
         * <ul>
         * <li>When connectivity is lost to one network, and there is no other available
         * network to attempt to switch to.</li>
         * <li>When connectivity is lost to one network, and the attempt to switch to
         * another network fails.</li>
         */
        NOT_CONNECTED;
    }

    /**
     * Create a new NetworkConnectivityListener.
     */
    public NetworkConnectivityListener() {
        mState = State.UNKNOWN;
        mReceiver = new ConnectivityBroadcastReceiver();
        mConnectivityRunnable = new Runnable() {
            @Override
            public void run() {
                if (mListener != null) {
                    mListener.onConnectivityChange(mState);
                }
            }
        };
    }

    /**
     * This method starts listening for network connectivity state changes.
     *
     * @param context app context
     */
    public synchronized void startListening(Context context, ConnectivityListener connectivityListener) {
        if (mListener == null) {
            mContext = context;
            mMainHandler = new Handler(context.getMainLooper());
            mListener = connectivityListener;

            IntentFilter filter = new IntentFilter();
            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
            context.registerReceiver(mReceiver, filter);
        }
    }

    /**
     * This method stops this class from listening for network changes.
     */
    public synchronized void stopListening() {
        if (mListener != null) {
            mContext.unregisterReceiver(mReceiver);
            mMainHandler = null;
            mContext = null;
            mListener = null;
        }
    }

    /**
     * Listener for connectivity change with simple state
     */
    public interface ConnectivityListener {
        void onConnectivityChange(State state);
    }


}
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.NavigationView;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.AppCompatSpinner;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.Spinner;
import android.widget.Toast;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnItemSelected;
import io.gresse.hugo.anecdote.about.AboutFragment;
import io.gresse.hugo.anecdote.anecdote.ToolbarSpinnerAdapter;
import io.gresse.hugo.anecdote.anecdote.UpdateAnecdoteFragmentEvent;
import io.gresse.hugo.anecdote.anecdote.WebsiteDialogFragment;
import io.gresse.hugo.anecdote.anecdote.fullscreen.ChangeFullscreenEvent;
import io.gresse.hugo.anecdote.anecdote.like.FavoritesRepository;
import io.gresse.hugo.anecdote.anecdote.list.AnecdoteFragment;
import io.gresse.hugo.anecdote.anecdote.service.AnecdoteService;
import io.gresse.hugo.anecdote.api.WebsiteApiService;
import io.gresse.hugo.anecdote.api.chooser.WebsiteChooserFragment;
import io.gresse.hugo.anecdote.api.event.LoadRemoteWebsiteEvent;
import io.gresse.hugo.anecdote.api.event.OnRemoteWebsiteResponseEvent;
import io.gresse.hugo.anecdote.api.model.Website;
import io.gresse.hugo.anecdote.api.model.WebsitePage;
import io.gresse.hugo.anecdote.event.ChangeTitleEvent;
import io.gresse.hugo.anecdote.event.DisplaySnackbarEvent;
import io.gresse.hugo.anecdote.event.Event;
import io.gresse.hugo.anecdote.event.NetworkConnectivityChangeEvent;
import io.gresse.hugo.anecdote.event.RequestFailedEvent;
import io.gresse.hugo.anecdote.event.RequestPermissionEvent;
import io.gresse.hugo.anecdote.event.ResetAppEvent;
import io.gresse.hugo.anecdote.event.WebsitesChangeEvent;
import io.gresse.hugo.anecdote.setting.SettingsFragment;
import io.gresse.hugo.anecdote.storage.SpStorage;
import io.gresse.hugo.anecdote.tracking.EventTracker;
import io.gresse.hugo.anecdote.util.FragmentStackManager;
import io.gresse.hugo.anecdote.util.NetworkConnectivityListener;
import toothpick.Scope;
import toothpick.Toothpick;
    }

    @Subscribe
    public void onWebsitesChangeEvent(WebsitesChangeEvent event) {
        if (event.fromWebsiteChooserOverride) {
            Log.d(TAG, "onWebsitesChangeEvent: removing all fragments");
            // if we come after a restoration or at the first start

            // 1. remove all already added fragment
            FragmentManager fm = getSupportFragmentManager();
            for (int i = 0; i < fm.getBackStackEntryCount(); ++i) {
                fm.popBackStack();
            }
            // 2. remove the WebsiteChooserFragment
            Fragment fragment = fm.findFragmentByTag(WebsiteChooserFragment.class.getName());
            if (fragment != null) {
                FragmentTransaction transaction = fm.beginTransaction();
                transaction.remove(fragment).commit();

            }
        }
        resetAnecdoteServices();
        populateNavigationView(false);
        EventBus.getDefault().post(new UpdateAnecdoteFragmentEvent());
    }

    @Subscribe
    public void changeFullscreenVisibilityEvent(ChangeFullscreenEvent event) {
        if (event.toFullscreen && getSupportActionBar() != null) {
            getSupportActionBar().hide();
            // mAppBarLayout.animate().translationY(-mAppBarLayout.getBottom()).setInterpolator(new AccelerateInterpolator()).start();
        } else if (getSupportActionBar() != null) {
            getSupportActionBar().show();
            // mAppBarLayout.animate().translationY(0).setInterpolator(new AccelerateInterpolator()).start();
        }

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                mAppBarLayout.getParent().requestLayout();
            }
        }, 600);

        if (event.toFullscreen) {
            // Hide status bar
            this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

            // Remove the layout behavior on fragment container to prevent issue with the fullscreen
            CoordinatorLayout.LayoutParams fragmentContainerLayoutParams =
                    (CoordinatorLayout.LayoutParams) mFragmentContainer.getLayoutParams();
            mFragmentLayoutBehavior = fragmentContainerLayoutParams.getBehavior();
            fragmentContainerLayoutParams.setBehavior(null);
        } else {
            // Show status bar
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

            // Set back the layout behavior on fragment container
            ((CoordinatorLayout.LayoutParams) mFragmentContainer.getLayoutParams())
                    .setBehavior(new AppBarLayout.ScrollingViewBehavior());
        }
    }

    @Subscribe
    public void onRemoteWebsiteLoaded(OnRemoteWebsiteResponseEvent event) {
        if (!event.isSuccessful) return;
        if (mWebsites == null || mWebsites.isEmpty()) return;

        /*
         * Check remote website and local to update local configuration if needed
         */
        Website tempWebsite;
        List<Website> newWebsiteList = new ArrayList<>();
        boolean dataModified = false;
        for (Website website : mWebsites) {
            int index = event.websiteList.lastIndexOf(website);

            // This remote website has not been found locally, skip it
            if (index == -1) {
                continue;
            }
            tempWebsite = event.websiteList.get(index);
            if (!website.isUpToDate(tempWebsite)) {
                dataModified = true;
                newWebsiteList.add(tempWebsite);
            } else {
                newWebsiteList.add(website);
            }
        }

        boolean newNotification = false;
        int savedWebsiteNumber = SpStorage.getSavedRemoteWebsiteNumber(this);
        if (savedWebsiteNumber < event.websiteList.size()) {
            // Display new website notification
            newNotification = true;
            SpStorage.setSavedRemoteWebsiteNumber(this, event.websiteList.size());
        } else if (event.websiteList.size() < savedWebsiteNumber) {
            SpStorage.setSavedRemoteWebsiteNumber(this, event.websiteList.size());
        }

        if (dataModified) {
            Log.i(TAG, "Updating websites configuration");
            mWebsites = newWebsiteList;
            SpStorage.saveWebsites(this, mWebsites);
            resetAnecdoteServices();
            populateNavigationView(newNotification);
            EventBus.getDefault().post(new UpdateAnecdoteFragmentEvent());
        } else if (newNotification) {
            populateNavigationView(true);
        }
    }

    @Subscribe
    public void onWebsiteReset(ResetAppEvent event) {
        changeFragment(
                WebsiteChooserFragment.newInstance(WebsiteChooserFragment.BUNDLE_MODE_RESTORE),
                true,
                true);
    }

    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
    public void onDisplaySnackbarEvent(final DisplaySnackbarEvent event) {