Java tutorial
/******************************************************************************* * Mirakel is an Android App for managing your ToDo-Lists * * Copyright (c) 2013-2015 Anatolij Zelenin, Georg Semmler. * * 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 * 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 de.azapps.mirakel.new_ui.fragments; import android.app.Activity; import android.app.Dialog; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.database.Cursor; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.support.v7.internal.view.ContextThemeWrapper; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import com.afollestad.materialdialogs.AlertDialogWrapper; import com.emtronics.dragsortrecycler.DragSortRecycler; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.collect.Collections2; import com.nispok.snackbar.Snackbar; import com.nispok.snackbar.SnackbarManager; import com.nispok.snackbar.listeners.ActionClickListener; import com.nispok.snackbar.listeners.EventListener; import java.util.ArrayList; import java.util.List; import butterknife.ButterKnife; import butterknife.InjectView; import de.azapps.material_elements.utils.SnackBarEventListener; import de.azapps.material_elements.utils.ThemeManager; import de.azapps.mirakel.adapter.MultiSelectCursorAdapter; import de.azapps.mirakel.adapter.OnItemClickedListener; import de.azapps.mirakel.helper.MirakelModelPreferences; import de.azapps.mirakel.model.provider.MirakelInternalContentProvider; import de.azapps.mirakel.model.account.AccountMirakel; import de.azapps.mirakel.model.list.ListMirakel; import de.azapps.mirakel.model.list.SpecialList; import de.azapps.mirakel.model.query_builder.CursorGetter; import de.azapps.mirakel.model.query_builder.MirakelQueryBuilder; import de.azapps.mirakel.new_ui.activities.LockableDrawer; import de.azapps.mirakel.new_ui.activities.MirakelActivity; import de.azapps.mirakel.new_ui.adapter.ListAdapter; import de.azapps.mirakel.new_ui.views.ListEditView; import de.azapps.mirakelandroid.R; public class ListsFragment extends Fragment implements LoaderManager.LoaderCallbacks, MultiSelectCursorAdapter.MultiSelectCallbacks<ListMirakel>, ActionMode.Callback, DragSortRecycler.OnItemMovedListener { private static final String ARGUMENT_ACCOUNT = "ARGUMENT_ACCOUNT"; public static final String LISTS_TO_DELETE = "LISTS_TO_DELETE"; private ListAdapter mAdapter; private OnItemClickedListener<ListMirakel> mListener; @NonNull private Optional<AccountMirakel> accountMirakelOptional = Optional.absent(); @InjectView(R.id.list_lists) RecyclerView mListView; @Nullable private ActionMode mActionMode; private int numberOfSelectedEditables = 0; private int numberOfSelectedDeletables = 0; public ListsFragment() { // Required empty public constructor } public Optional<AccountMirakel> getAccount() { return accountMirakelOptional; } public void setAccount(final Optional<AccountMirakel> accountMirakelOptional) { this.accountMirakelOptional = accountMirakelOptional; final Bundle args = new Bundle(); args.putParcelable(ARGUMENT_ACCOUNT, accountMirakelOptional.orNull()); getLoaderManager().restartLoader(0, args, this); } @Override public void onActivityCreated(final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mAdapter = new ListAdapter(getActivity(), null, mListener, this); getLoaderManager().initLoader(0, null, this); mListView.setAdapter(mAdapter); mListView.setLayoutManager(new LinearLayoutManager(getActivity())); mListView.setItemAnimator(null); final DragSortRecycler dragSortRecycler = new DragSortRecycler(); dragSortRecycler.setViewHandleId(R.id.row_list_drag); dragSortRecycler.setOnDragStateChangedListener(new DragSortRecycler.OnDragStateChangedListener() { @Override public void onDragStart() { ((LockableDrawer) getActivity()).lockDrawer(); } @Override public void onDragStop() { ((LockableDrawer) getActivity()).unlockDrawer(); } }); dragSortRecycler.setOnItemMovedListener(this); mListView.addItemDecoration(dragSortRecycler); mListView.addOnItemTouchListener(dragSortRecycler); mListView.setOnScrollListener(dragSortRecycler.getScrollListener()); } @Override public void onAttach(final Activity activity) { super.onAttach(activity); try { mListener = (OnItemClickedListener<ListMirakel>) activity; if (!((activity instanceof EventListener) || (activity instanceof LockableDrawer))) { throw new ClassCastException(); } } catch (final ClassCastException ignored) { throw new ClassCastException(activity.toString() + " must implement OnListSelectedListener, EventListener and LockableDrawer"); } } @Override public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { // Inflate the layout for this fragment final View layout = inflater.inflate(R.layout.fragment_lists, container, false); ButterKnife.inject(this, layout); return layout; } @Override public Loader onCreateLoader(final int id, final Bundle args) { if (args != null) { accountMirakelOptional = Optional.fromNullable((AccountMirakel) args.getParcelable(ARGUMENT_ACCOUNT)); if (accountMirakelOptional.isPresent() && (accountMirakelOptional.get().getType() == AccountMirakel.ACCOUNT_TYPES.ALL)) { accountMirakelOptional = Optional.absent(); // Remove the hack as soon as possible } final ArrayList<ListMirakel> listsToDelete = args.getParcelableArrayList(LISTS_TO_DELETE); if (listsToDelete != null) { final List<Long> ids = new ArrayList<>( Collections2.transform(listsToDelete, new Function<ListMirakel, Long>() { @Override public Long apply(@Nullable final ListMirakel input) { if (input != null) { return input.getId(); } else { return 0L; } } })); return ListMirakel.allWithSpecialMQB(accountMirakelOptional) .and(ListMirakel.ID, MirakelQueryBuilder.Operation.NOT_IN, ids) .toSupportCursorLoader(MirakelInternalContentProvider.LIST_WITH_COUNT_URI); } } return ListMirakel.allWithSpecialSupportCursorLoader(accountMirakelOptional); } @Override public void onLoadFinished(final Loader loader, final Object data) { mAdapter.swapCursor((Cursor) data); } @Override public void onLoaderReset(final Loader loader) { mAdapter.swapCursor(null); } public void onCloseNavDrawer() { if (mActionMode != null) { mActionMode.finish(); } } @Override public void onSelectModeChanged(final boolean selectMode) { if (selectMode) { mActionMode = getActivity().startActionMode(this); } else if (mActionMode != null) { mActionMode.finish(); } } @Override public boolean canAddItem(@NonNull final ListMirakel listMirakel) { if (!listMirakel.isDeletable() && !listMirakel.isEditable()) { SnackbarManager.show(Snackbar.with(getActivity()).text(R.string.can_not_edit_list) .eventListener(new SnackBarEventListener()), getActivity()); return false; } else { return true; } } @Override public void onAddSelectedItem(@NonNull final ListMirakel listMirakel) { onSelectedItemCountChanged(mAdapter.getSelectedItemCount()); if ((mAdapter.getSelectedItemCount() > 0) && (mActionMode == null)) { onSelectModeChanged(true); } if (listMirakel.isEditable()) { numberOfSelectedEditables++; if (numberOfSelectedEditables == 1) { mActionMode.getMenu().findItem(R.id.menu_edit).setVisible(true); } else { mActionMode.getMenu().findItem(R.id.menu_edit).setVisible(false); } } if (listMirakel.isDeletable()) { numberOfSelectedDeletables++; // Show remove icon if (numberOfSelectedDeletables == 1) { mActionMode.getMenu().findItem(R.id.menu_delete).setVisible(true); } } } @Override public void onRemoveSelectedItem(@NonNull final ListMirakel listMirakel) { final int count = mAdapter.getSelectedItemCount(); if ((count > 0) && (mActionMode == null)) { onSelectModeChanged(true); } onSelectedItemCountChanged(count); if (count > 0) { if ((count == 1) && listMirakel.isEditable()) { mActionMode.getMenu().findItem(R.id.menu_edit).setVisible(true); } if (listMirakel.isDeletable()) { numberOfSelectedDeletables--; if (numberOfSelectedDeletables == 0) { mActionMode.getMenu().findItem(R.id.menu_delete).setVisible(false); } } } } public void onSelectedItemCountChanged(final int itemCount) { if (mActionMode == null) { onSelectModeChanged(true); } if (itemCount == 0) { mActionMode.finish(); } else { mActionMode.setTitle( getResources().getQuantityString(R.plurals.list_multiselect_title, itemCount, itemCount)); } } @Override public boolean onCreateActionMode(final ActionMode mode, final Menu menu) { final int count = mAdapter.getSelectedItemCount(); final MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.multiselect_lists, menu); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getActivity().getWindow().setStatusBarColor(ThemeManager.getColor(R.attr.colorCABStatus)); } mode.setTitle(getResources().getQuantityString(R.plurals.list_multiselect_title, count, count)); return true; } @Override public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) { return true; } @Override public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { final ArrayList<ListMirakel> selected = new ArrayList<>( Collections2.transform(mAdapter.getSelectedItems(), new Function<ListMirakel, ListMirakel>() { @Override public ListMirakel apply(@Nullable ListMirakel input) { if ((input != null) && input.isSpecial()) { return SpecialList.get(input.getId()).get(); } return input; } })); switch (item.getItemId()) { case R.id.menu_delete: handleDelete(selected); break; case R.id.menu_edit: editList(selected.get(0)); break; default: return false; } mode.finish(); return true; } private void handleDelete(final ArrayList<ListMirakel> listsToDelete) { final Bundle args = new Bundle(); args.putParcelable(ARGUMENT_ACCOUNT, accountMirakelOptional.orNull()); args.putParcelableArrayList(LISTS_TO_DELETE, listsToDelete); getLoaderManager().restartLoader(0, args, this); final int count = listsToDelete.size(); SnackbarManager.show(Snackbar.with(getActivity()) .text(getResources().getQuantityString(R.plurals.list_multiselect_deleted, count, count)) .actionLabel(R.string.undo).actionListener(new ActionClickListener() { @Override public void onActionClicked(Snackbar snackbar) { listsToDelete.clear(); } }).eventListener(new SnackBarEventListener() { @Override public void onDismiss(final Snackbar snackbar) { super.onDismiss(snackbar); ((MirakelActivity) getActivity()).moveFabDown(snackbar.getHeight()); new Thread(new Runnable() { @Override public void run() { for (final ListMirakel listMirakel : listsToDelete) { listMirakel.destroy(); } mListView.post(new Runnable() { @Override public void run() { setAccount(accountMirakelOptional); } }); } }).start(); } @Override public void onShow(final Snackbar snackbar) { ((MirakelActivity) getActivity()).moveFABUp(snackbar.getHeight()); } }), getActivity()); } public void editList(final ListMirakel listMirakel) { Context ctx = new ContextThemeWrapper(getActivity(), R.style.MirakelBaseTheme); final ListEditView listEditView = new ListEditView(ctx); listEditView.setListMirakel(listMirakel); final Dialog dialog = new AlertDialogWrapper.Builder(ctx).setView(listEditView) .setTitle(R.string.list_edit_title) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { listEditView.saveList(); listEditView.closeKeyBoard(); } }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { listEditView.closeKeyBoard(); } }).create(); dialog.setOnShowListener(new DialogInterface.OnShowListener() { @Override public void onShow(final DialogInterface dialog) { listEditView.openKeyBoard(); } }); dialog.show(); } @Override public void onDestroyActionMode(final ActionMode mode) { mAdapter.clearSelections(); mActionMode = null; numberOfSelectedDeletables = 0; numberOfSelectedEditables = 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getActivity().getWindow().setStatusBarColor(ThemeManager.getColor(R.attr.colorPrimaryDark)); } } /** * We want to move a list from position A to position B. * * We have a column lft. The lists are ordered by this column. When we are moving lists down we have to do the following: * * lft | list * 1 | A * 3 | B * 5 | C * 7 | D * 9 | E * * ## Moving Down * We want to move list B below D. We do the following: * 1. Decrement the lft-values by 2 of the lists with lft > lft(B) && lft <= lft(D) * 2. Set the lft of B to the old lft(D) * * ## Moving Up * We want to move list D above list B. We do the following: * 1. Increment the lft-values by 2 of the lists with lft>=lft(B) && lft < lft(D) * 2. Set the lft of D to the old lft(B) * * @param tmpFrom List to move * @param tmpTo Target list to move the `from` list to. If the user is moving from down, than it should be moved below `to` otherwise above */ @Override public void onItemMoved(final int tmpFrom, final int tmpTo) { mAdapter.onItemMoved(tmpFrom, tmpTo); // update divider position final int dividerPosition = MirakelModelPreferences.getDividerPosition(); final int from; final int to; final boolean changedPosition; if ((tmpFrom < dividerPosition) && (tmpTo >= dividerPosition)) { MirakelModelPreferences.setDividerPosition(dividerPosition - 1); from = tmpFrom; to = tmpTo - 1; changedPosition = true; } else if ((tmpFrom > dividerPosition) && (tmpTo <= dividerPosition)) { MirakelModelPreferences.setDividerPosition(dividerPosition + 1); from = tmpFrom - 1; to = tmpTo; changedPosition = true; } else if ((tmpFrom > dividerPosition) && (tmpTo > dividerPosition)) { from = tmpFrom - 1; to = tmpTo - 1; changedPosition = false; } else { from = tmpFrom; to = tmpTo; changedPosition = false; } final Cursor cursor = mAdapter.getCursor(); cursor.moveToPosition(from); final ListMirakel fromList = new ListMirakel(CursorGetter.unsafeGetter(cursor)); cursor.moveToPosition(to); final ListMirakel toList = new ListMirakel(CursorGetter.unsafeGetter(cursor)); MirakelInternalContentProvider.withTransaction(new MirakelInternalContentProvider.DBTransaction() { @Override public void exec() { for (final String table : new String[] { SpecialList.TABLE, ListMirakel.TABLE }) { final ContentValues cv = new ContentValues(); cv.put("TABLE", table); if (to < from) { // move list up getActivity().getContentResolver().update( MirakelInternalContentProvider.UPDATE_LIST_MOVE_UP_URI, cv, ListMirakel.LFT + " >= " + toList.getLft() + " and " + ListMirakel.LFT + " < " + fromList.getLft(), null); } else if (to > from) { // move list down getActivity().getContentResolver().update( MirakelInternalContentProvider.UPDATE_LIST_MOVE_DOWN_URI, cv, ListMirakel.LFT + " > " + fromList.getLft() + " and " + ListMirakel.LFT + " <= " + toList.getLft(), null); } else if (changedPosition) { setAccount(accountMirakelOptional); return; } else { return; } getActivity().getContentResolver() .update(MirakelInternalContentProvider.UPDATE_LIST_FIX_RGT_URI, cv, null, null); } fromList.setLft(toList.getLft()); fromList.setRgt(toList.getLft() + 2); fromList.save(); } }); } }