com.ovrhere.android.careerstack.ui.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.ovrhere.android.careerstack.ui.MainActivity.java

Source

/*
 * Copyright 2014 Jason J.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.ovrhere.android.careerstack.ui;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;

import com.ovrhere.android.careerstack.R;
import com.ovrhere.android.careerstack.dao.CareerItem;
import com.ovrhere.android.careerstack.prefs.PreferenceUtils;
import com.ovrhere.android.careerstack.ui.fragments.CareerItemFragment;
import com.ovrhere.android.careerstack.ui.fragments.MainFragment;
import com.ovrhere.android.careerstack.ui.fragments.SearchResultsFragment;
import com.ovrhere.android.careerstack.ui.fragments.SettingsFragment;
import com.ovrhere.android.careerstack.ui.fragments.dialogs.ConfirmationDialogFragment;
import com.ovrhere.android.careerstack.ui.fragments.dialogs.SearchBarDialogFragment;
import com.ovrhere.android.careerstack.utils.TabletUtil;

/** The main entry point into the application.
 * @author Jason J.
 * @version 0.9.0-20141124
 */
public class MainActivity extends ActionBarActivity implements OnBackStackChangedListener,
        DialogInterface.OnClickListener, MainFragment.OnFragmentInteractionListener,
        CareerItemFragment.OnFragmentInteractionListener, SearchResultsFragment.OnFragmentInteractionListener,
        SettingsFragment.OnFragmentInteractionListener, SearchBarDialogFragment.OnDialogResultsListener {

    /** Class name for debugging purposes. */
    final static private String CLASS_NAME = MainActivity.class.getSimpleName();
    /** Whether or not to debug. */
    final static private boolean DEBUG = true;

    /** Bundle key. The last fragment to be loaded (and so reloaded). 
     * Array<String> */
    final static private String KEY_FRAG_TAG_TACK = CLASS_NAME + ".KEY_LAST_FRAG_TAG";
    /** Bundle key. The group of saved states to retain.
     *  Hashmap<String,Bundle>/Serializable. */
    final static private String KEY_FRAG_SAVED_STATES = CLASS_NAME + ".KEY_FRAG_SAVED_STATES";
    /** Bundle key. The actionbar title in #actionBarTitle. String. */
    final static private String KEY_ACTIONBAR_TITLE = CLASS_NAME + ".KEY_ACTIONBAR_SUBTITLE";

    /** Bundle key. The current search state for the searchbar. Bundle. */
    final static private String KEY_CURRENT_SEARCH_BAR_STATE = CLASS_NAME + ".KEY_CURRENT_SEARCH";

    /** Extra Key. The theme intent value. Int. */
    final static private String KEY_THEME_INTENT = CLASS_NAME + ".KEY_THEME_INTENT";

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// Frag tags
    ////////////////////////////////////////////////////////////////////////////////////////////////

    /** The main fragment tag. */
    final static private String TAG_MAIN_FRAG = MainFragment.FRAGTAG;

    /** The tag for the search resultsfrag. */
    final static private String TAG_SEARCH_RESULTS_FRAG = SearchResultsFragment.class.getName();

    /** The tag for the career item frag. */
    final static private String TAG_CAREER_ITEM_FRAG = CareerItem.class.getName();

    /** The settings tag. */
    final static private String TAG_SETTINGS_FRAG = SettingsFragment.class.getName();

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// End constants
    ////////////////////////////////////////////////////////////////////////////////////////////////

    /** The list of all fragments in play. */
    final private ArrayListStack<String> fragTagStack = new ArrayListStack<String>();

    /** A map of all back stack fragment states. 
     * Key: fragment tag (String), Value: savedState (Bundle) */
    final private HashMap<String, Bundle> fragSavedStates = new HashMap<String, Bundle>();

    /** The current actionbar subtitle. */
    private String actionBarTitle = "";

    /** The current theme. Default is -1. */
    private int currThemeId = -1;

    /** The current shared preference. */
    private SharedPreferences prefs = null;

    /** The current menu as built by activity.
     *  Initialized in {@link #onCreateOptionsMenu(Menu)} */
    private Menu menu = null;

    /** The tablet container message for tablet mode. */
    private TextView tabletMessage = null;

    /** The current search of the application set according to the keys of
     * {@link SearchBarDialogFragment}.
     * Set in {@link #onSearchRequest(Bundle)} & {@link #onSearch(DialogFragment, Bundle)} */
    private Bundle currSearchBarState = new Bundle();

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putStringArrayList(KEY_FRAG_TAG_TACK, fragTagStack.getArrayList());
        outState.putSerializable(KEY_FRAG_SAVED_STATES, fragSavedStates);
        outState.putString(KEY_ACTIONBAR_TITLE, actionBarTitle);

        outState.putBundle(KEY_CURRENT_SEARCH_BAR_STATE, currSearchBarState);
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setThemeByIntent();
        super.onCreate(savedInstanceState);
        getSupportFragmentManager().addOnBackStackChangedListener(this);

        if (PreferenceUtils.isFirstRun(this)) {
            PreferenceUtils.setToDefault(this);
        }
        prefs = PreferenceUtils.getPreferences(this);
        //checks and, if necessary, restarts activity for theme
        checkThemePref();

        setContentView(R.layout.activity_main);

        tabletMessage = (TextView) findViewById(R.id.careerstack_main_text_job_description_message);

        if (savedInstanceState == null) {
            loadFragment(new MainFragment(), TAG_MAIN_FRAG, false);
            actionBarTitle = getString(R.string.app_name);

        } else {
            try {
                currSearchBarState = savedInstanceState.getBundle(KEY_CURRENT_SEARCH_BAR_STATE);

                if (savedInstanceState.getStringArrayList(KEY_FRAG_TAG_TACK) != null) {
                    fragTagStack.addAll(savedInstanceState.getStringArrayList(KEY_FRAG_TAG_TACK));
                }

                if (savedInstanceState.getString(KEY_ACTIONBAR_TITLE) != null) {
                    actionBarTitle = savedInstanceState.getString(KEY_ACTIONBAR_TITLE);
                }
                reattachLastFragment();

                if (savedInstanceState.getSerializable(KEY_FRAG_SAVED_STATES) != null) {
                    try {
                        fragSavedStates.putAll((Map<? extends String, ? extends Bundle>) savedInstanceState
                                .getSerializable(KEY_FRAG_SAVED_STATES));
                    } catch (ClassCastException e) {
                    }
                }

            } catch (Exception e) {
                if (DEBUG) {
                    Log.e(CLASS_NAME, "Should not be having an error here: " + e);
                    e.printStackTrace();
                }
            }
        }

        getSupportActionBar().setTitle(actionBarTitle);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        this.menu = menu;
        checkMenus();
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        switch (item.getItemId()) {
        case R.id.action_settings:
            if (fragTagStack.peek().equals(TAG_SETTINGS_FRAG) == false) {
                loadFragment(new SettingsFragment(), TAG_SETTINGS_FRAG, true);
                setActionBarTitle(getString(R.string.action_settings));
            }
            //hide settings when viewing settings
            checkMenus();
            return true;

        case R.id.action_toggleTheme:
            if (quickSwitchTheme()) {
                toggleDayNightMode();
            } else {
                showChangeThemeDialog();
            }
            return true;

        case R.id.action_refresh:
            refreshSearchRequest();
            return true;

        case R.id.action_search:
            showSearchBarDialog();
            return true;

        default:
            break;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        if (canBack()) {
            //do what ever would be done for back actionbar
            onSupportNavigateUp();
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onSupportNavigateUp() {
        //This method is called when the up button is pressed. Just the pop back stack.
        setActionBarTitle(getString(R.string.app_name));
        getSupportFragmentManager().popBackStack();
        fragTagStack.pop();

        if (TAG_CAREER_ITEM_FRAG.equals(fragTagStack.peek()) && inTabletMode()) {
            //if we popped back to have the career item & tablet mode
            reattachLastFragment();
            return true;
        }

        //show settings when not viewing settings
        checkMenus();
        checkTabletFrag();
        return true;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// Misc. Helpers
    ////////////////////////////////////////////////////////////////////////////////////////////////

    /** Sets actionbar title in {@link #actionBarTitle} & sets title to it. */
    private void setActionBarTitle(String title) {
        actionBarTitle = title;
        getSupportActionBar().setTitle(actionBarTitle);
    }

    /** Retreives the quick switch theme pref. */
    private boolean quickSwitchTheme() {
        return prefs != null
                && prefs.getBoolean(getString(R.string.careerstack_pref_KEY_QUICK_THEME_SWITCH), false);
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// Theme Helper methods
    ////////////////////////////////////////////////////////////////////////////////////////////////

    /* Theme flow explained:
     * Activity starts and them preferences are checked:
     * A) 1. If using quick switch mode, set theme. Done.
     * 
     * B) 1 If NOT using quick switch, set theme intent and restart
     *   2 If theme intent is set, set theme. Done.
     * 
     * ---
     * 
     * Changing themes:
     * A)1. If using quick switch mode: toggle theme
     *   2. Call recreate
     *   3. See A above. Done
     * 
     * B)1. If not using using quick switch: launch dialog
     *   2. Accept-> toggle theme
     *   3. Restart
     *   4. See B above. Done
     * 
     */

    /* Created to compensate for a bug. See: 
     * -https://code.google.com/p/android/issues/detail?id=3793#makechanges
     * -https://code.google.com/p/android/issues/detail?id=4394
     * -https://groups.google.com/forum/?fromgroups=#!topic/android-developers/vSZHsVWUCqk
     */
    /** Sets theme before super.onCreate() via intent. */
    private void setThemeByIntent() {
        currThemeId = getIntent().getIntExtra(KEY_THEME_INTENT, -1);
        if (currThemeId > 0) {
            setTheme(currThemeId);
        }
    }

    /** If #currThemeId is unset (-1) 
     * it checks theme preference, and restarts activity for application.
     * If #currThemeId  is set, it returns early.
     * <p>
     * Must be called before {@link #setContentView(int)} but 
     * after super.onCreate(). </p>*/
    private void checkThemePref() {
        if (currThemeId != -1) { //if it does not equal -1
            return; //we must have our theme already set.
        }

        final String dark = getString(R.string.careerstack_pref_VALUE_THEME_DARK);
        //final String light = getString(R.string.careerstack_pref_VALUE_THEME_LIGHT);
        final String currTheme = prefs.getString(getString(R.string.careerstack_pref_KEY_THEME_PREF), dark);

        if (currTheme.equals(dark)) {
            currThemeId = R.style.AppBaseTheme_Dark;
        } else {
            //light
            currThemeId = R.style.AppBaseTheme_Light;
        }

        final boolean quickSwitch = prefs.getBoolean(getString(R.string.careerstack_pref_KEY_QUICK_THEME_SWITCH),
                false);
        if (quickSwitch) { //if we are not in 
            setTheme(currThemeId);
        } else {
            resetActivityForTheme(); //reset
        }

    }

    /** Toggles day and night mode pref and restarts.
     * Assumes {@link #checkThemePref()} is called in {@link #onCreate(Bundle)}   */
    @SuppressLint("NewApi")
    private void toggleDayNightMode() {
        final String key = getString(R.string.careerstack_pref_KEY_THEME_PREF);
        final String dark = getString(R.string.careerstack_pref_VALUE_THEME_DARK);
        final String light = getString(R.string.careerstack_pref_VALUE_THEME_LIGHT);

        if (currThemeId == R.style.AppBaseTheme_Dark) {
            currThemeId = R.style.AppBaseTheme_Light;
            prefs.edit().putString(key, light).commit(); //toggle values
        } else {
            currThemeId = R.style.AppBaseTheme_Dark;
            prefs.edit().putString(key, dark).commit();
        }

        resetActivityForTheme();
    }

    /** Restarts/Recreates the activity with the theme set. 
     * Based upon the value of quick switch preference.  */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void resetActivityForTheme() {
        final boolean quickSwitch = quickSwitchTheme();

        //if quick switching and supported
        if (quickSwitch && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            //quick switch
            super.recreate();

        } else {
            //otherwise, we'll restart!
            try {
                Intent intent = getIntent();
                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                intent.putExtra(KEY_THEME_INTENT, currThemeId);
                finish();
                startActivity(intent); //restart same intent
            } catch (Exception e) {
                Log.e(CLASS_NAME, "Error restarting activity: " + e);
            }
        }

    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// Functionality helpers
    ////////////////////////////////////////////////////////////////////////////////////////////////

    /** Checks that the device is currently in tablet mode based on:
     * <ol><li>Screen size</li><li>Ads enabled</li><li>Tablet mode enabled</li></ol>
     * @return <code>true</code> if currently in tablet mode, <code>false</code>
     * otherwise.    */
    private boolean inTabletMode() {
        return TabletUtil.inTabletMode(getResources(), prefs);
    }

    /** Sends the search request off the SearchResultsFragment;
     * if the fragment is at the front it sends it directly, otherwise, it 
     * clears the backstack and re-attaches.
     * @param args The argments to forward onto {@link SearchResultsFragment}
     */
    private void sendSearchRequest(Bundle args) {
        boolean requestSent = false;

        resetTabletContainerMessage();

        try {
            //get fragment then send results.
            Fragment frag = getSupportFragmentManager().findFragmentByTag(TAG_SEARCH_RESULTS_FRAG);

            //check to see if we are already in the search frag
            if (frag != null) {
                ((SearchResultsFragment) frag).sendRequest(args);
                requestSent = true;

                //if not at front, clear stack and re-add
                if (!TAG_SEARCH_RESULTS_FRAG.equals(fragTagStack.peek())) {
                    loadFragment(frag, TAG_SEARCH_RESULTS_FRAG, false);
                }
            }

        } catch (ClassCastException e) {
            Log.e(CLASS_NAME, "Something abnormal has occurred! : " + e);
            if (DEBUG) {
                throw e;
            }
        }

        /* If the request has not been sent yet, either due to an error or
         * the frag the fragment is not in the stack yet. */
        if (!requestSent) {
            //launch fragment with no backstack
            loadFragment(SearchResultsFragment.newInstance(args), TAG_SEARCH_RESULTS_FRAG, false);
        }
    }

    /** Refreshes the search results page if available. If not
     * available nothing happens.     */
    private void refreshSearchRequest() {
        //if we are already in the search frag
        if (TAG_SEARCH_RESULTS_FRAG.equals(fragTagStack.peek())) {
            try {
                //get fragment then send results.
                SearchResultsFragment frag = (SearchResultsFragment) getSupportFragmentManager()
                        .findFragmentByTag(TAG_SEARCH_RESULTS_FRAG);

                frag.retryRequest();

            } catch (ClassCastException e) {
                Log.e(CLASS_NAME, "Something abnormal has occurred! : " + e);
                if (DEBUG) {
                    throw e;
                }
            }
        } else if (DEBUG) {
            Log.d(CLASS_NAME, "Why are we refreshing without search results?");
        }
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// Dialog helpers
    ////////////////////////////////////////////////////////////////////////////////////////////////

    /** Builds dialog for activity and shows it. */
    private void showChangeThemeDialog() {
        new ConfirmationDialogFragment.Builder().setTitle(R.string.action_toggleTheme)
                .setMessage(R.string.careerstack_theme_dialogMsg)
                .setPositive(R.string.careerstack_restart_dialogMsg_confirm).setNegative(android.R.string.no)
                .create()
                .show(getSupportFragmentManager(), ConfirmationDialogFragment.class.getName() + CLASS_NAME);
    }

    /** Initializes and shows the search bar dialog using the current
     * search state.     */
    private void showSearchBarDialog() {
        SearchBarDialogFragment.newInstance(currSearchBarState).show(getSupportFragmentManager(),
                SearchBarDialogFragment.class.getName());
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// Fragment methods
    ////////////////////////////////////////////////////////////////////////////////////////////////

    /*
     * Tablet mode explanation:
     * 
     * 1. Whenever NOT in tablet mode OR in tablet mode viewing Settings
     * or Main Frags, set R.id.tablet_container as view GONE.
     * 
     * 2. Whenever shifting TO tablet mode, CareerItemFragment is re-attached
     * to tablet_container and popped, so that R.id.container is SearchResultsFragment.  
     * 
     * 3. Whenever shifting FROM tablet mode, CareerItemFragment is re-attached
     * to R.id.container and tablet_container is HIDDEN. 
     * 
     * 4. Whenever attaching a new CareerItemFragment, tabletmode is checked.
     * In tablet mode, it is attached to tablet_container, in regular R.id.container.
     * 
     * 5. Whenever searching, if in tablet mode, the message is reset in the tablet_container.
     * 
     */

    /** Re-attaches the last fragment. And resets back button. */
    private void reattachLastFragment() {
        //first, always check tablet fragment.
        checkTabletFrag();

        String currentTag = fragTagStack.peek();
        FragmentManager fm = getSupportFragmentManager();
        Log.d("Main", "currentTag: " + currentTag);

        if (currentTag != null) {
            if (inTabletMode()) {
                //TODO re-arrange fragments accordingly

                Fragment careerItem = fm.findFragmentByTag(TAG_CAREER_ITEM_FRAG);
                //if the regular mode item is a career item
                if (TAG_CAREER_ITEM_FRAG.equals(currentTag)) {

                    fm.beginTransaction().remove(careerItem).commit();

                    //pop to search result fragment
                    onSupportNavigateUp();
                    fm.executePendingTransactions();
                    loadTabletFragment(careerItem, TAG_CAREER_ITEM_FRAG);

                } else if (careerItem == null) {
                    resetTabletContainerMessage();
                }

            } else if (TAG_SEARCH_RESULTS_FRAG.equals(currentTag)) {
                //if we are in search mode, the we may have a lingering careeritem

                //TODO re-arrange fragments accordingly
                Fragment careerItem = fm.findFragmentByTag(TAG_CAREER_ITEM_FRAG);
                //re-attach careeritem
                if (careerItem != null) {
                    fm.beginTransaction().remove(careerItem).commit();
                    fm.executePendingTransactions();
                    loadFragment(careerItem, TAG_CAREER_ITEM_FRAG, true);
                }
            } else {
                Fragment frag = fm.findFragmentByTag(currentTag);
                fm.beginTransaction().attach(frag).commit();
            }
        }

        checkHomeButtonBack();
    }

    /** Checks to see if the current fragment is the settings fragment.
     * If so deactivate menu, if not, re-enable it. Must be called after
     * {@link #onCreateOptionsMenu(Menu)} 
     */
    private void checkMenus() {
        if (menu == null) {
            return;
        }
        final String currTag = fragTagStack.peek();
        //if in settings frag
        if (TAG_SETTINGS_FRAG.equals(currTag)) {
            menu.setGroupVisible(0, false);
        } else {
            menu.setGroupVisible(0, true);
        }

        boolean showSearch = false;
        boolean showRefresh = false;

        if (TAG_MAIN_FRAG.equals(currTag)) {
            showSearch = false;
            showRefresh = false;
        } else if (TAG_SEARCH_RESULTS_FRAG.equals(currTag)) {
            showSearch = true;
            showRefresh = true;
        } else if (TAG_CAREER_ITEM_FRAG.equals(currTag)) {
            //FIXME the search results frag needs to "back search" before search can be enabled from items
            showSearch = false;
            showRefresh = false;
        }

        menu.findItem(R.id.action_search).setVisible(showSearch).setEnabled(showSearch);
        menu.findItem(R.id.action_refresh).setVisible(showRefresh).setEnabled(showRefresh);

    }

    /** Returns whether or not there is a backstack. */
    private boolean canBack() {
        return getSupportFragmentManager().getBackStackEntryCount() > 0;
    }

    /** Checks to see whether to enable the action bar back. */
    private void checkHomeButtonBack() {
        boolean canback = canBack();
        //boolean canback = fragTagStack.size() > 0;
        getSupportActionBar().setDisplayHomeAsUpEnabled(canback);
    }

    /** Shows and hides visibility based. */
    private void checkTabletFrag() {
        boolean show = false;
        if (inTabletMode()) {
            final String tag = fragTagStack.peek();
            if (!(TAG_MAIN_FRAG.equals(tag) || TAG_SETTINGS_FRAG.equals(tag))) {
                //if neither in main nor settings, do not show tablet container.
                show = true;
                getSupportFragmentManager().executePendingTransactions();
            }
        }
        findViewById(R.id.tablet_container).setVisibility(show ? View.VISIBLE : View.GONE);
    }

    /** Loads a fragment either by adding or replacing and then adds it to
     * the fragTagList.
     * @param fragment The fragment to add
     * @param tag The tag to give the fragment
     * @param backStack <code>true</code> to add to backstack, 
     * <code>false</code> to not.
     */
    private void loadFragment(Fragment fragment, String tag, boolean backStack) {

        FragmentManager fragManager = getSupportFragmentManager();

        if (backStack) {
            String prevTag = fragTagStack.peek();
            fragManager.beginTransaction().addToBackStack(prevTag).replace(R.id.container, fragment, tag).commit();
        } else {
            //clear the entire backstack
            fragManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
            fragManager.beginTransaction().replace(R.id.container, fragment, tag).commit();
            fragTagStack.clear();
            fragSavedStates.clear();
        }

        fragTagStack.push(tag);

        checkTabletFrag();
        checkMenus();
        checkHomeButtonBack();
    }

    /** Loads fragment into tablet container. 
     * @param fragment The fragment to add
     * @param tag The tag to give the fragment */
    private void loadTabletFragment(Fragment fragment, String tag) {
        FragmentManager fragManager = getSupportFragmentManager();
        checkTabletFrag();

        tabletMessage.setVisibility(View.GONE);
        fragManager.beginTransaction().replace(R.id.tablet_container, fragment, tag).commit();
    }

    /** Resets the tablet container message by clearing it and inflating view stub. */
    private void resetTabletContainerMessage() {
        tabletMessage.setVisibility(View.VISIBLE);
        Fragment frag = getSupportFragmentManager().findFragmentByTag(TAG_CAREER_ITEM_FRAG);
        if (frag != null) {
            getSupportFragmentManager().beginTransaction().remove(frag).commit();
        }
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// Internal classes
    ////////////////////////////////////////////////////////////////////////////////////////////////

    /** Simple wrapper to manage array list as a stack. */
    @SuppressWarnings("unused")
    private static class ArrayListStack<Obj> {
        final private ArrayList<Obj> listStack = new ArrayList<Obj>();

        /** Returns stack size. */
        public int size() {
            return listStack.size();
        }

        /** Clears all elements */
        public void clear() {
            listStack.clear();
        }

        /** Adds all elements to stack. */
        public void addAll(ArrayList<Obj> arrayList) {
            listStack.addAll(arrayList);
        }

        /** Returns all elements to as {@link ArrayList}. */
        public ArrayList<Obj> getArrayList() {
            return this.listStack;
        }

        /** Pushes object onto "stack" */
        public boolean push(Obj object) {
            return listStack.add(object);
        }

        /** Pops the last element and returns it or <code>null</code>. */
        public Obj pop() {
            if (listStack.size() > 0) {
                return listStack.remove(listStack.size() - 1);
            }
            return null;
        }

        /** Displays last element without removing it. (or null) */
        public Obj peek() {
            if (listStack.size() > 0) {
                return listStack.get(listStack.size() - 1);
            }
            return null;
        }

    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// Implemented listeners
    ////////////////////////////////////////////////////////////////////////////////////////////////

    /* MainFragments listener
     * (non-Javadoc)
     * @see com.ovrhere.android.careerstack.ui.fragments.MainFragment.OnFragmentInteractionListener#onSearchRequest(android.os.Bundle)
     */
    @Override
    public boolean onSearchRequest(Bundle bundle) {
        Bundle args = new Bundle();
        currSearchBarState = new Bundle();

        args.putString(SearchResultsFragment.KEY_KEYWORD_TEXT, bundle.getString(MainFragment.KEY_KEYWORD_TEXT));
        currSearchBarState.putString(SearchBarDialogFragment.KEY_KEYWORD_TEXT,
                bundle.getString(MainFragment.KEY_KEYWORD_TEXT));

        args.putString(SearchResultsFragment.KEY_LOCATION_TEXT, bundle.getString(MainFragment.KEY_LOCATION_TEXT));
        currSearchBarState.putString(SearchBarDialogFragment.KEY_LOCATION_TEXT,
                bundle.getString(MainFragment.KEY_LOCATION_TEXT));

        args.putBoolean(SearchResultsFragment.KEY_RELOCATE_OFFER,
                bundle.getBoolean(MainFragment.KEY_RELOCATE_OFFER));
        currSearchBarState.putBoolean(SearchBarDialogFragment.KEY_RELOCATE_OFFER,
                bundle.getBoolean(MainFragment.KEY_RELOCATE_OFFER));

        args.putBoolean(SearchResultsFragment.KEY_REMOTE_ALLOWED,
                bundle.getBoolean(MainFragment.KEY_REMOTE_ALLOWED));
        currSearchBarState.putBoolean(SearchBarDialogFragment.KEY_REMOTE_ALLOWED,
                bundle.getBoolean(MainFragment.KEY_REMOTE_ALLOWED));

        int distance = bundle.getInt(MainFragment.KEY_DISTANCE, -1);
        if (distance > 0) {
            args.putInt(SearchResultsFragment.KEY_DISTANCE, distance);
            currSearchBarState.putInt(SearchBarDialogFragment.KEY_DISTANCE, distance);
        }

        sendSearchRequest(args);
        return true;
    }

    //end MainFragments listeners

    //start SearchResultsFragment listeners

    /* (non-Javadoc)
     * @see com.ovrhere.android.careerstack.ui.fragments.SearchResultsFragment.OnFragmentInteractionListener#onCareerItemRequest(com.ovrhere.android.careerstack.dao.CareerItem)
     */
    @Override
    public boolean onCareerItemRequest(CareerItem item) {
        if (item != null) {
            if (inTabletMode()) {
                loadTabletFragment(CareerItemFragment.newInstance(item), TAG_CAREER_ITEM_FRAG);
            } else {
                loadFragment(CareerItemFragment.newInstance(item), TAG_CAREER_ITEM_FRAG, true);
            }
            return true;
        }
        return false;
    }

    /* (non-Javadoc)
     * @see com.ovrhere.android.careerstack.ui.fragments.SearchResultsFragment.OnFragmentInteractionListener#onHoldSavedStateRequest(android.os.Bundle)
     */
    @Override
    public boolean onHoldSavedStateRequest(Bundle savedState) {
        fragSavedStates.put(TAG_SEARCH_RESULTS_FRAG, savedState);
        return true;
    }

    /* (non-Javadoc)
     * @see com.ovrhere.android.careerstack.ui.fragments.SearchResultsFragment.OnFragmentInteractionListener#onPopSavedStateRequest()
     */
    @Override
    public Bundle onPopSavedStateRequest() {
        return fragSavedStates.remove(TAG_SEARCH_RESULTS_FRAG);
    }

    //end SearchResultsFragment listeners

    /* (non-Javadoc)
     * @see com.ovrhere.android.careerstack.ui.fragments.SettingsFragment.OnFragmentInteractionListener#onRestartRequest()
     */
    @Override
    public boolean onRestartRequest() {
        //pure restart
        Intent intent = new Intent(this, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        finish();
        startActivity(intent);
        return true;
    }

    //end SettingsFragment

    //start CareerItemFragment listeners
    @Override
    public boolean onTagClick(String tag) {
        Bundle searchArgs = new Bundle(); //prepare search args for tag
        searchArgs.putString(SearchResultsFragment.KEY_TAG_TEXT, tag);

        currSearchBarState = new Bundle(); //replace search with tag only
        currSearchBarState.putString(SearchBarDialogFragment.KEY_KEYWORD_TEXT, tag);

        //remove any previous states
        fragSavedStates.remove(TAG_SEARCH_RESULTS_FRAG);
        //launch request
        sendSearchRequest(searchArgs);
        return true;
    }
    //end CareerItemFragment listeners

    //start SearchBarDialogFragment
    /* (non-Javadoc)
     * @see com.ovrhere.android.careerstack.ui.fragments.dialogs.SearchBarDialogFragment.OnDialogResultsListener#onSearch(android.support.v4.app.DialogFragment, android.os.Bundle)
     */
    @Override
    public void onSearch(DialogFragment dialog, Bundle searchParams) {
        currSearchBarState = null;
        currSearchBarState = searchParams; //replace search
        //unpack request from dialog
        Bundle args = new Bundle();
        args.putString(SearchResultsFragment.KEY_KEYWORD_TEXT,
                searchParams.getString(SearchBarDialogFragment.KEY_KEYWORD_TEXT));
        args.putString(SearchResultsFragment.KEY_LOCATION_TEXT,
                searchParams.getString(SearchBarDialogFragment.KEY_LOCATION_TEXT));

        args.putBoolean(SearchResultsFragment.KEY_RELOCATE_OFFER,
                searchParams.getBoolean(SearchBarDialogFragment.KEY_RELOCATE_OFFER));
        args.putBoolean(SearchResultsFragment.KEY_REMOTE_ALLOWED,
                searchParams.getBoolean(SearchBarDialogFragment.KEY_REMOTE_ALLOWED));

        int distance = searchParams.getInt(SearchBarDialogFragment.KEY_DISTANCE, -1);
        if (distance > 0) {
            args.putInt(SearchResultsFragment.KEY_DISTANCE, distance);
        }

        //remove any previous states
        fragSavedStates.remove(TAG_SEARCH_RESULTS_FRAG);
        //launch request
        sendSearchRequest(args);
    }

    /* (non-Javadoc)
     * @see com.ovrhere.android.careerstack.ui.fragments.dialogs.SearchBarDialogFragment.OnDialogResultsListener#onCancel(android.support.v4.app.DialogFragment)
     */
    @Override
    public void onCancel(DialogFragment dialog) {
        //do nothing      
    }

    //end SearchBarDialogFragment

    @Override
    public void onBackStackChanged() {
        checkHomeButtonBack();
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        switch (which) {
        case DialogInterface.BUTTON_POSITIVE:
            toggleDayNightMode();
            break;

        default:
            break;
        }

    }

}