/*
* Copyright (c) 2015 Jonas Kalderstam.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nononsenseapps.notepad.ui.common;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.database.Cursor;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import com.nononsenseapps.notepad.R;
import com.nononsenseapps.notepad.data.model.sql.Task;
import com.nononsenseapps.notepad.data.model.sql.TaskList;
import com.nononsenseapps.notepad.ui.list.TaskListFragment;
import com.nononsenseapps.notepad.util.SharedPreferencesHelper;
import com.nononsenseapps.notepad.util.SyncGtaskHelper;
import java.util.ArrayList;
import static com.nononsenseapps.notepad.util.ArrayHelper.toArray;
/**
* This fragment is the view which is displayed in the left drawer.
* Any activity containing it must implement NavigationDrawerCallbacks.
*/
public class NavigationDrawerFragment extends Fragment implements LoaderManager
.LoaderCallbacks<Cursor> {
static final int VIEWTYPE_ITEM = 0;
static final int VIEWTYPE_EXTRA_HEADER_ITEM = 1;
static final int VIEWTYPE_EXTRA_FOOTER_ITEM = 2;
static final int VIEWTYPE_SEPARATOR_HEADER = 3;
static final int VIEWTYPE_SEPARATOR_FOOTER = 4;
static final int VIEWTYPE_TOPLEVEL = 5;
static final String[] COUNTROWS = new String[]{"COUNT(1)"};
static final String NOTCOMPLETED = Task.Columns.COMPLETED + " IS NULL ";
private static final long EXTRA_ID_SETTINGS = -100;
private static final long EXTRA_ID_ABOUT = -101;
private static final long EXTRA_ID_CREATE_LIST = -102;
private static final long EXTRA_ID_CHANGELOG = -103;
private static final long EXTRA_ID_SEPARATOR_1 = -1001;
private static final long EXTRA_ID_SEPARATOR_2 = -1002;
private static final int LOADER_NAVDRAWER_LISTS = 0;
/**
* Per the design guidelines, you should show the drawer on launch until the user manually
* expands it. This shared preference tracks this.
*/
private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned";
ArrayList<ArrayList<Object>> mExtraData;
/**
* Helper component that ties the action bar to the navigation drawer.
*/
private ActionBarDrawerToggle mDrawerToggle;
private DrawerLayout mDrawerLayout;
private boolean mFromSavedInstanceState;
private boolean mUserLearnedDrawer;
private Adapter mAdapter;
private NavigationDrawerCallbacks mCallbacks;
private boolean mIsThemeLight;
public NavigationDrawerFragment() {
}
private void updateExtra(final int pos, final int count) {
while (mExtraData.get(pos).size() < 2) {
// To avoid crashes
mExtraData.get(pos).add("0");
}
mExtraData.get(pos).set(1, Integer.toString(count));
mAdapter.notifyDataSetChanged();
}
/**
* Users of this fragment must call this method to set up the navigation drawer interactions.
*
* @param drawerLayout The DrawerLayout containing this fragment's UI.
*/
public void setUp(DrawerLayout drawerLayout, Toolbar toolbar) {
mDrawerLayout = drawerLayout;
// set a custom shadow that overlays the main content when the drawer opens
//mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
// Fix the color of the status bar
final ActionBar ab = getActionBar();
if (ab != null) {
ab.setHomeAsUpIndicator(R.drawable.ic_menu_24dp_white);
ab.setDisplayHomeAsUpEnabled(true);
}
// ActionBarDrawerToggle ties together the the proper interactions
// between the navigation drawer and the action bar app icon.
mDrawerToggle = new ActionBarDrawerToggle(getActivity(), mDrawerLayout, toolbar, R.string
.navigation_drawer_open, R.string.navigation_drawer_close) {
@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
if (!isAdded()) {
return;
}
if (!mUserLearnedDrawer) {
// The user manually opened the drawer; store this flag to prevent auto-showing
// the navigation drawer automatically in the future.
mUserLearnedDrawer = true;
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences
(getActivity());
sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply();
}
}
};
// If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer,
// per the navigation drawer design guidelines.
if (!mUserLearnedDrawer && !mFromSavedInstanceState) {
mDrawerLayout.openDrawer(GravityCompat.START);
//mDrawerLayout.openDrawer(fragmentContainerView);
}
// Defer code dependent on restoration of previous instance state.
mDrawerLayout.post(new Runnable() {
@Override
public void run() {
mDrawerToggle.syncState();
}
});
mDrawerLayout.setDrawerListener(mDrawerToggle);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mCallbacks = (NavigationDrawerCallbacks) getActivity();
} catch (ClassCastException e) {
throw new ClassCastException("Activity must implement NavigationDrawerCallbacks.");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIsThemeLight = SharedPreferencesHelper.isCurrentThemeLight(getActivity());
// Read in the flag indicating whether or not the user has demonstrated awareness of the
// drawer. See PREF_USER_LEARNED_DRAWER for details.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
if (savedInstanceState != null) {
//mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
mFromSavedInstanceState = true;
}
// To get call to handle home button
hasOptionsMenu();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_navdrawer, container, false);
RecyclerView list = (RecyclerView) rootView.findViewById(R.id.left_drawer);
// TODO add edit lists item?
mAdapter = new Adapter(toArray(new TopLevelItem(), new ExtraHeaderItem(TaskListFragment
.LIST_ID_ALL, R.string.show_from_all_lists)), toArray(new SeparatorFooter
(EXTRA_ID_SEPARATOR_1), new CreateListFooter(), new SeparatorFooter
(EXTRA_ID_SEPARATOR_2), new SettingsFooterItem(), new AboutFooterItem(),
new ChangelogFooterItem()));
list.setAdapter(mAdapter);
list.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
list.setLayoutManager(layoutManager);
return rootView;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Start loading
getLoaderManager().restartLoader(LOADER_NAVDRAWER_LISTS, null, this);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Forward the new configuration the drawer toggle component.
mDrawerToggle.onConfigurationChanged(newConfig);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (android.R.id.home == item.getItemId()) {
mDrawerLayout.openDrawer(GravityCompat.START);
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
private ActionBar getActionBar() {
return ((AppCompatActivity) getActivity()).getSupportActionBar();
}
/**
* Instantiate and return a new Loader for the given ID.
*
* @param id The ID whose loader is to be created.
* @param args Any arguments supplied by the caller.
* @return Return a new Loader instance that is ready to start loading.
*/
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// Normal lists
switch (id) {
case LOADER_NAVDRAWER_LISTS:
return new CursorLoader(getContext(), TaskList.URI_WITH_COUNT, new
String[]{TaskList.Columns._ID, TaskList.Columns.TITLE, TaskList.Columns
.VIEW_COUNT}, null, null, getResources().getString(R.string
.const_as_alphabetic, TaskList.Columns.TITLE));
default:
return null;
}
}
/**
* @param loader The Loader that has finished.
* @param c The data generated by the Loader.
*/
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
switch (loader.getId()) {
case LOADER_NAVDRAWER_LISTS:
mAdapter.setData(c);
break;
}
}
/**
* Called when a previously created loader is being reset, and thus
* making its data unavailable. The application should at this point
* remove any references it has to the Loader's data.
*
* @param loader The Loader that is being reset.
*/
@Override
public void onLoaderReset(Loader<Cursor> loader) {
switch (loader.getId()) {
case LOADER_NAVDRAWER_LISTS:
mAdapter.setData(null);
break;
}
}
public interface NavigationDrawerCallbacks {
void openList(long id);
void createList();
void editList(long id);
void openSettings();
void openAbout();
void openChangelog();
}
/**
* The interface of the extra items in this adapter.
*/
interface ExtraItem {
long getItemId();
int getViewType();
}
private class Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final ExtraItem[] headers;
private final ExtraItem[] footers;
Cursor mCursor = null;
public Adapter(@NonNull ExtraItem[] headers, @NonNull ExtraItem[] footers) {
setHasStableIds(true);
this.headers = headers;
this.footers = footers;
}
public void setData(Cursor cursor) {
mCursor = cursor;
notifyDataSetChanged();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder vh;
LayoutInflater inflater = LayoutInflater.from(getContext());
switch (viewType) {
case VIEWTYPE_TOPLEVEL:
vh = new TopLevelItemViewHolder(inflater.inflate(R.layout
.navigation_drawer_header, parent, false));
break;
case VIEWTYPE_EXTRA_HEADER_ITEM:
vh = new ExtraHeaderItemViewHolder(inflater.inflate(R.layout
.navigation_drawer_list_item, parent, false));
break;
case VIEWTYPE_EXTRA_FOOTER_ITEM:
vh = new ExtraFooterItemViewHolder(inflater.inflate(R.layout
.navigation_drawer_list_item, parent, false));
break;
case VIEWTYPE_SEPARATOR_FOOTER:
vh = new SeparatorFooterViewHolder(inflater.inflate(R.layout
.navigation_drawer_list_separator, parent, false));
break;
case VIEWTYPE_ITEM:
default:
vh = new CursorViewHolder(inflater.inflate(R.layout
.navigation_drawer_list_item, parent, false));
break;
}
return vh;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case VIEWTYPE_TOPLEVEL:
((TopLevelItemViewHolder) holder).bind((TopLevelItem) headers[position]);
break;
case VIEWTYPE_EXTRA_HEADER_ITEM:
((ExtraHeaderItemViewHolder) holder).bind((ExtraHeaderItem) headers[position]);
break;
case VIEWTYPE_EXTRA_FOOTER_ITEM:
((ExtraFooterItemViewHolder) holder).bind((ExtraFooterItem)
footers[actualPosition(position)]);
break;
case VIEWTYPE_ITEM:
mCursor.moveToPosition(actualPosition(position));
((CursorViewHolder) holder).bind(mCursor);
break;
}
}
@Override
public int getItemViewType(int position) {
if (isHeader(position)) {
return headers[position].getViewType();
} else if (isFooter(position)) {
return footers[actualPosition(position)].getViewType();
} else {
return VIEWTYPE_ITEM;
}
}
@Override
public long getItemId(int position) {
if (isHeader(position)) {
return headers[position].getItemId();
} else if (isFooter(position)) {
return footers[actualPosition(position)].getItemId();
} else {
mCursor.moveToPosition(actualPosition(position));
return mCursor.getLong(0);
}
}
@Override
public int getItemCount() {
int result = headers.length + footers.length;
if (mCursor != null) {
result += mCursor.getCount();
}
return result;
}
/**
* @param position as seen from the outside
* @return position as seen by internal data (header position if header, wrapped position
* if..)
*/
public int actualPosition(int position) {
if (isHeader(position)) {
return position;
} else if (isFooter(position)) {
int cursorCount = mCursor == null ? 0 : mCursor.getCount();
return position - headers.length - cursorCount;
} else {
return position - headers.length;
}
}
/**
* @param position as seen from the outside
* @return true if position is on a footer, false otherwise
*/
public boolean isFooter(int position) {
return (getItemCount() - position) <= footers.length;
}
/**
* @param position as seen from the outside
* @return true if position is on a header, false otherwise
*/
public boolean isHeader(int position) {
return position < headers.length;
}
}
class SeparatorFooter implements ExtraItem {
private final long mId;
public SeparatorFooter(long id) {
this.mId = id;
}
@Override
public long getItemId() {
return mId;
}
@Override
public int getViewType() {
return VIEWTYPE_SEPARATOR_FOOTER;
}
}
class TopLevelItem implements ExtraItem {
public String getAvatarName() {
// Try google account first
String result = "";
if (SyncGtaskHelper.isGTasksConfigured(getActivity())) {
result = SharedPreferencesHelper.getGoogleAccount(getActivity());
}
if (result.isEmpty() && SharedPreferencesHelper.isDropboxSyncEnabled(getActivity())) {
// Then try dropbox
result = SharedPreferencesHelper.getDropboxAccount(getActivity());
}
if (result.isEmpty() && SharedPreferencesHelper.isSdSyncEnabled(getActivity())) {
// Then try folder
result = SharedPreferencesHelper.getSdDir(getActivity());
}
return result;
}
@Override
public long getItemId() {
return 0;
}
@Override
public int getViewType() {
return VIEWTYPE_TOPLEVEL;
}
}
class ExtraHeaderItem implements ExtraItem {
public final long mId;
public final int mTitleRes;
public ExtraHeaderItem(long id, @StringRes int title) {
this.mId = id;
this.mTitleRes = title;
}
public String getTitle() {
return getString(mTitleRes);
}
@Override
public long getItemId() {
return mId;
}
@Override
public int getViewType() {
return VIEWTYPE_EXTRA_HEADER_ITEM;
}
}
class ExtraFooterItem extends ExtraHeaderItem {
final int mIconRes;
public ExtraFooterItem(long id, @StringRes int title) {
super(id, title);
mIconRes = -1;
}
public ExtraFooterItem(long id, @StringRes int title, @DrawableRes int icon) {
super(id, title);
this.mIconRes = icon;
}
@Override
public int getViewType() {
return VIEWTYPE_EXTRA_FOOTER_ITEM;
}
public int getIconRes() {
return mIconRes;
}
public void onClick() {
}
}
class SettingsFooterItem extends ExtraFooterItem {
public SettingsFooterItem() {
super(EXTRA_ID_SETTINGS, R.string.menu_preferences, mIsThemeLight ? R.drawable
.ic_settings_24dp_black_active : R.drawable.ic_settings_24dp_white);
}
@Override
public void onClick() {
mCallbacks.openSettings();
mDrawerLayout.closeDrawers();
}
}
class AboutFooterItem extends ExtraFooterItem {
public AboutFooterItem() {
super(EXTRA_ID_ABOUT, R.string.about, mIsThemeLight ? R.drawable
.ic_help_24dp_black_active : R.drawable.ic_help_24dp_white);
}
@Override
public void onClick() {
mCallbacks.openAbout();
mDrawerLayout.closeDrawers();
}
}
class ChangelogFooterItem extends ExtraFooterItem {
public ChangelogFooterItem() {
super(EXTRA_ID_CHANGELOG, R.string.changelog, mIsThemeLight ? R.drawable
.ic_info_24dp_black_active : R.drawable.ic_info_24dp_white);
}
@Override
public void onClick() {
mCallbacks.openChangelog();
mDrawerLayout.closeDrawers();
}
}
class CreateListFooter extends ExtraFooterItem {
public CreateListFooter() {
super(EXTRA_ID_CREATE_LIST, R.string.menu_createnew, mIsThemeLight ? R.drawable
.ic_add_24dp_black_active : R.drawable.ic_add_24dp_white);
}
@Override
public void onClick() {
mCallbacks.createList();
mDrawerLayout.closeDrawers();
}
}
private class SeparatorFooterViewHolder extends RecyclerView.ViewHolder {
public SeparatorFooterViewHolder(View itemView) {
super(itemView);
}
public void bind(SeparatorFooter separatorFooter) {
}
}
private class TopLevelItemViewHolder extends RecyclerView.ViewHolder {
private final ImageView mAvatar;
private final TextView mText1;
public TopLevelItemViewHolder(View itemView) {
super(itemView);
mAvatar = (ImageView) itemView.findViewById(R.id.main_avatar);
mText1 = (TextView) itemView.findViewById(android.R.id.text1);
}
public void bind(TopLevelItem topLevelItem) {
final String name = topLevelItem.getAvatarName();
final String imageName = (name.isEmpty() || name.startsWith("/")) ? "N" : name;
TextDrawable drawable = TextDrawable.builder()
.buildRound(imageName.toUpperCase().substring(0, 1), ColorGenerator.MATERIAL
.getColor(imageName));
mAvatar.setImageDrawable(drawable);
mText1.setText(name);
}
}
private class ExtraHeaderItemViewHolder extends RecyclerView.ViewHolder implements View
.OnClickListener {
final TextView mTitle;
final TextView mCount;
final ImageView mAvatar;
private ExtraHeaderItem mItem;
public ExtraHeaderItemViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
mTitle = (TextView) itemView.findViewById(android.R.id.text1);
mCount = (TextView) itemView.findViewById(android.R.id.text2);
mAvatar = (ImageView) itemView.findViewById(R.id.item_avatar);
}
public void bind(ExtraHeaderItem headerItem) {
mItem = headerItem;
mTitle.setText(headerItem.mTitleRes);
mCount.setVisibility(View.GONE);
TextDrawable drawable = TextDrawable.builder()
.buildRound(mItem.getTitle().toUpperCase().substring(0, 1), ColorGenerator
.MATERIAL.getColor(mItem.getTitle()));
mAvatar.setImageDrawable(drawable);
}
@Override
public void onClick(View v) {
PreferenceManager.getDefaultSharedPreferences(getContext()).edit().putLong
(TaskListFragment.LIST_ALL_ID_PREF_KEY, mItem.mId).commit();
mCallbacks.openList(mItem.mId);
mDrawerLayout.closeDrawers();
}
}
private class ExtraFooterItemViewHolder extends ExtraHeaderItemViewHolder {
private ExtraFooterItem mItem;
public ExtraFooterItemViewHolder(View itemView) {
super(itemView);
}
public void bind(ExtraFooterItem headerItem) {
mItem = headerItem;
mTitle.setText(headerItem.mTitleRes);
mCount.setVisibility(View.GONE);
if (headerItem.getIconRes() < 1) {
TextDrawable drawable = TextDrawable.builder().buildRound(mItem.getTitle()
.toUpperCase().substring(0, 1),
ColorGenerator.MATERIAL.getColor(mItem.getTitle()));
mAvatar.setImageDrawable(drawable);
} else {
mAvatar.setImageResource(headerItem.getIconRes());
}
}
@Override
public void onClick(View v) {
mItem.onClick();
}
}
private class CursorViewHolder extends RecyclerView.ViewHolder implements View
.OnClickListener, View.OnLongClickListener {
private final TextView mTitle;
private final TextView mCount;
private final ImageView mAvatar;
private long id = -1;
public CursorViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
mTitle = (TextView) itemView.findViewById(android.R.id.text1);
mCount = (TextView) itemView.findViewById(android.R.id.text2);
mAvatar = (ImageView) itemView.findViewById(R.id.item_avatar);
}
public void bind(Cursor cursor) {
final String title = cursor.getString(cursor.getColumnIndex(TaskList.Columns.TITLE));
id = cursor.getLong(cursor.getColumnIndex(TaskList.Columns._ID));
mTitle.setText(title);
mCount.setText(cursor.getString(cursor.getColumnIndex(TaskList.Columns.VIEW_COUNT)));
TextDrawable drawable = TextDrawable.builder()
.buildRound(title.toUpperCase().substring(0, 1), ColorGenerator.MATERIAL
.getColor(title));
mAvatar.setImageDrawable(drawable);
}
@Override
public boolean onLongClick(View v) {
mCallbacks.editList(id);
return true;
}
@Override
public void onClick(View v) {
mCallbacks.openList(id);
mDrawerLayout.closeDrawers();
}
}
}