mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Allow to reorder and rename categories
This commit is contained in:
		| @@ -351,6 +351,7 @@ public class DatabaseHelper { | ||||
|                 .listOfObjects(Category.class) | ||||
|                 .withQuery(Query.builder() | ||||
|                         .table(CategoryTable.TABLE) | ||||
|                         .orderBy(CategoryTable.COLUMN_ORDER) | ||||
|                         .build()) | ||||
|                 .prepare(); | ||||
|     } | ||||
| @@ -361,6 +362,12 @@ public class DatabaseHelper { | ||||
|                 .prepare(); | ||||
|     } | ||||
|  | ||||
|     public PreparedPutCollectionOfObjects<Category> insertCategories(List<Category> categories) { | ||||
|         return db.put() | ||||
|                 .objects(categories) | ||||
|                 .prepare(); | ||||
|     } | ||||
|  | ||||
|     public PreparedDeleteObject<Category> deleteCategory(Category category) { | ||||
|         return db.delete() | ||||
|                 .object(category) | ||||
|   | ||||
| @@ -16,6 +16,12 @@ public class Category implements Serializable { | ||||
|     @StorIOSQLiteColumn(name = CategoryTable.COLUMN_NAME) | ||||
|     public String name; | ||||
|  | ||||
|     @StorIOSQLiteColumn(name = CategoryTable.COLUMN_ORDER) | ||||
|     public int order; | ||||
|  | ||||
|     @StorIOSQLiteColumn(name = CategoryTable.COLUMN_FLAGS) | ||||
|     public int flags; | ||||
|  | ||||
|     public Category() {} | ||||
|  | ||||
|     public static Category create(String name) { | ||||
|   | ||||
| @@ -13,6 +13,12 @@ public class CategoryTable { | ||||
|     @NonNull | ||||
|     public static final String COLUMN_NAME = "name"; | ||||
|  | ||||
|     @NonNull | ||||
|     public static final String COLUMN_ORDER = "sort"; | ||||
|  | ||||
|     @NonNull | ||||
|     public static final String COLUMN_FLAGS = "flags"; | ||||
|  | ||||
|     // This is just class with Meta Data, we don't need instances | ||||
|     private CategoryTable() { | ||||
|         throw new IllegalStateException("No instances please"); | ||||
| @@ -24,7 +30,9 @@ public class CategoryTable { | ||||
|     public static String getCreateTableQuery() { | ||||
|         return "CREATE TABLE " + TABLE + "(" | ||||
|                 + COLUMN_ID + " INTEGER NOT NULL PRIMARY KEY, " | ||||
|                 + COLUMN_NAME + " TEXT NOT NULL" | ||||
|                 + COLUMN_NAME + " TEXT NOT NULL, " | ||||
|                 + COLUMN_ORDER + " INTEGER NOT NULL, " | ||||
|                 + COLUMN_FLAGS + " INTEGER NOT NULL" | ||||
|                 + ");"; | ||||
|  | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,57 @@ | ||||
| /* | ||||
|  * Copyright (C) 2015 Paul Burke | ||||
|  * | ||||
|  * 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 eu.kanade.mangafeed.ui.base.adapter; | ||||
|  | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.support.v7.widget.helper.ItemTouchHelper; | ||||
|  | ||||
| /** | ||||
|  * Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}. | ||||
|  * | ||||
|  * @author Paul Burke (ipaulpro) | ||||
|  */ | ||||
| public interface ItemTouchHelperAdapter { | ||||
|  | ||||
|     /** | ||||
|      * Called when an item has been dragged far enough to trigger a move. This is called every time | ||||
|      * an item is shifted, and <strong>not</strong> at the end of a "drop" event.<br/> | ||||
|      * <br/> | ||||
|      * Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after | ||||
|      * adjusting the underlying data to reflect this move. | ||||
|      * | ||||
|      * @param fromPosition The start position of the moved item. | ||||
|      * @param toPosition   Then resolved position of the moved item. | ||||
|      * | ||||
|      * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder) | ||||
|      * @see RecyclerView.ViewHolder#getAdapterPosition() | ||||
|      */ | ||||
|     void onItemMove(int fromPosition, int toPosition); | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Called when an item has been dismissed by a swipe.<br/> | ||||
|      * <br/> | ||||
|      * Implementations should call {@link RecyclerView.Adapter#notifyItemRemoved(int)} after | ||||
|      * adjusting the underlying data to reflect this removal. | ||||
|      * | ||||
|      * @param position The position of the item dismissed. | ||||
|      * | ||||
|      * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder) | ||||
|      * @see RecyclerView.ViewHolder#getAdapterPosition() | ||||
|      */ | ||||
|     void onItemDismiss(int position); | ||||
| } | ||||
| @@ -0,0 +1,13 @@ | ||||
| package eu.kanade.mangafeed.ui.base.adapter; | ||||
|  | ||||
| import android.support.v7.widget.RecyclerView; | ||||
|  | ||||
| public interface OnStartDragListener { | ||||
|  | ||||
|     /** | ||||
|      * Called when a view is requesting a start of a drag. | ||||
|      * | ||||
|      * @param viewHolder The holder of the view to drag. | ||||
|      */ | ||||
|     void onStartDrag(RecyclerView.ViewHolder viewHolder); | ||||
| } | ||||
| @@ -0,0 +1,43 @@ | ||||
| package eu.kanade.mangafeed.ui.base.adapter; | ||||
|  | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.support.v7.widget.helper.ItemTouchHelper; | ||||
|  | ||||
| public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback { | ||||
|  | ||||
|     private final ItemTouchHelperAdapter adapter; | ||||
|  | ||||
|     public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) { | ||||
|         this.adapter = adapter; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isLongPressDragEnabled() { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isItemViewSwipeEnabled() { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { | ||||
|         int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; | ||||
|         int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; | ||||
|         return makeMovementFlags(dragFlags, swipeFlags); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, | ||||
|                           RecyclerView.ViewHolder target) { | ||||
|         adapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition()); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { | ||||
|         adapter.onItemDismiss(viewHolder.getAdapterPosition()); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -6,25 +6,29 @@ import android.view.ViewGroup; | ||||
|  | ||||
| import com.amulyakhare.textdrawable.util.ColorGenerator; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter; | ||||
| import eu.kanade.mangafeed.R; | ||||
| import eu.kanade.mangafeed.data.database.models.Category; | ||||
| import eu.kanade.mangafeed.ui.base.adapter.ItemTouchHelperAdapter; | ||||
|  | ||||
| public class CategoryAdapter extends FlexibleAdapter<CategoryHolder, Category> { | ||||
|      | ||||
|     private CategoryFragment fragment; | ||||
|     private ColorGenerator generator; | ||||
| public class CategoryAdapter extends FlexibleAdapter<CategoryHolder, Category> implements | ||||
|         ItemTouchHelperAdapter { | ||||
|  | ||||
|     private final CategoryFragment fragment; | ||||
|     private final ColorGenerator generator; | ||||
|  | ||||
|     public CategoryAdapter(CategoryFragment fragment) { | ||||
|         this.fragment = fragment; | ||||
|         setHasStableIds(true); | ||||
|         generator = ColorGenerator.DEFAULT; | ||||
|         setHasStableIds(true); | ||||
|     } | ||||
|  | ||||
|     public void setItems(List<Category> items) { | ||||
|         mItems = items; | ||||
|         mItems = new ArrayList<>(items); | ||||
|         notifyDataSetChanged(); | ||||
|     } | ||||
|  | ||||
| @@ -42,7 +46,7 @@ public class CategoryAdapter extends FlexibleAdapter<CategoryHolder, Category> { | ||||
|     public CategoryHolder onCreateViewHolder(ViewGroup parent, int viewType) { | ||||
|         LayoutInflater inflater = LayoutInflater.from(fragment.getActivity()); | ||||
|         View v = inflater.inflate(R.layout.item_edit_categories, parent, false); | ||||
|         return new CategoryHolder(v, this, fragment); | ||||
|         return new CategoryHolder(v, this, fragment, fragment); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -54,7 +58,23 @@ public class CategoryAdapter extends FlexibleAdapter<CategoryHolder, Category> { | ||||
|         holder.itemView.setActivated(isSelected(position)); | ||||
|     } | ||||
|  | ||||
|     public ColorGenerator getColorGenerator() { | ||||
|         return generator; | ||||
|     @Override | ||||
|     public void onItemMove(int fromPosition, int toPosition) { | ||||
|         if (fromPosition < toPosition) { | ||||
|             for (int i = fromPosition; i < toPosition; i++) { | ||||
|                 Collections.swap(mItems, i, i + 1); | ||||
|             } | ||||
|         } else { | ||||
|             for (int i = fromPosition; i > toPosition; i--) { | ||||
|                 Collections.swap(mItems, i, i - 1); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         fragment.getPresenter().reorderCategories(mItems); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onItemDismiss(int position) { | ||||
|  | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import android.support.v4.content.res.ResourcesCompat; | ||||
| import android.support.v7.view.ActionMode; | ||||
| import android.support.v7.widget.LinearLayoutManager; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.support.v7.widget.helper.ItemTouchHelper; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| @@ -22,6 +23,7 @@ import eu.kanade.mangafeed.R; | ||||
| import eu.kanade.mangafeed.data.database.models.Category; | ||||
| import eu.kanade.mangafeed.ui.base.activity.BaseActivity; | ||||
| import eu.kanade.mangafeed.ui.base.adapter.FlexibleViewHolder; | ||||
| import eu.kanade.mangafeed.ui.base.adapter.OnStartDragListener; | ||||
| import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment; | ||||
| import eu.kanade.mangafeed.ui.decoration.DividerItemDecoration; | ||||
| import eu.kanade.mangafeed.ui.library.LibraryCategoryAdapter; | ||||
| @@ -29,14 +31,15 @@ import nucleus.factory.RequiresPresenter; | ||||
| import rx.Observable; | ||||
|  | ||||
| @RequiresPresenter(CategoryPresenter.class) | ||||
| public class CategoryFragment extends BaseRxFragment<CategoryPresenter> | ||||
|         implements ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener { | ||||
| public class CategoryFragment extends BaseRxFragment<CategoryPresenter> implements | ||||
|         ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener, OnStartDragListener { | ||||
|  | ||||
|     @Bind(R.id.categories_list) RecyclerView recycler; | ||||
|     @Bind(R.id.fab) FloatingActionButton fab; | ||||
|  | ||||
|     private CategoryAdapter adapter; | ||||
|     private ActionMode actionMode; | ||||
|     private ItemTouchHelper touchHelper; | ||||
|  | ||||
|     public static CategoryFragment newInstance() { | ||||
|         return new CategoryFragment(); | ||||
| @@ -56,12 +59,17 @@ public class CategoryFragment extends BaseRxFragment<CategoryPresenter> | ||||
|         recycler.addItemDecoration(new DividerItemDecoration( | ||||
|                 ResourcesCompat.getDrawable(getResources(), R.drawable.line_divider, null))); | ||||
|  | ||||
|         // Touch helper to drag and reorder categories | ||||
|         touchHelper = new ItemTouchHelper(new CategoryItemTouchHelper(adapter)); | ||||
|         touchHelper.attachToRecyclerView(recycler); | ||||
|  | ||||
|         fab.setOnClickListener(v -> { | ||||
|             new MaterialDialog.Builder(getActivity()) | ||||
|                     .title(R.string.action_add_category) | ||||
|                     .input(R.string.name, 0, false, (dialog, input) -> { | ||||
|                         getPresenter().createCategory(input.toString()); | ||||
|                     }).show(); | ||||
|                     }) | ||||
|                     .show(); | ||||
|         }); | ||||
|  | ||||
|         return view; | ||||
| @@ -105,6 +113,8 @@ public class CategoryFragment extends BaseRxFragment<CategoryPresenter> | ||||
|         } else { | ||||
|             setContextTitle(count); | ||||
|             actionMode.invalidate(); | ||||
|             MenuItem editItem = actionMode.getMenu().findItem(R.id.action_edit); | ||||
|             editItem.setVisible(count == 1); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -128,7 +138,10 @@ public class CategoryFragment extends BaseRxFragment<CategoryPresenter> | ||||
|     public boolean onActionItemClicked(ActionMode mode, MenuItem item) { | ||||
|         switch (item.getItemId()) { | ||||
|             case R.id.action_delete: | ||||
|                 getPresenter().deleteCategories(getSelectedCategories()); | ||||
|                 deleteCategories(getSelectedCategories()); | ||||
|                 return true; | ||||
|             case R.id.action_edit: | ||||
|                 editCategory(getSelectedCategories().get(0)); | ||||
|                 return true; | ||||
|         } | ||||
|         return false; | ||||
| @@ -147,4 +160,22 @@ public class CategoryFragment extends BaseRxFragment<CategoryPresenter> | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void deleteCategories(List<Category> categories) { | ||||
|         getPresenter().deleteCategories(categories); | ||||
|     } | ||||
|  | ||||
|     private void editCategory(Category category) { | ||||
|         new MaterialDialog.Builder(getActivity()) | ||||
|                 .title(R.string.action_rename_category) | ||||
|                 .input(getString(R.string.name), category.name, false, (dialog, input) -> { | ||||
|                     getPresenter().renameCategory(category, input.toString()); | ||||
|                 }) | ||||
|                 .show(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onStartDrag(RecyclerView.ViewHolder viewHolder) { | ||||
|         touchHelper.startDrag(viewHolder); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package eu.kanade.mangafeed.ui.library.category; | ||||
|  | ||||
| import android.support.v4.view.MotionEventCompat; | ||||
| import android.view.MotionEvent; | ||||
| import android.view.View; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.TextView; | ||||
| @@ -13,6 +15,7 @@ import butterknife.OnClick; | ||||
| import eu.kanade.mangafeed.R; | ||||
| import eu.kanade.mangafeed.data.database.models.Category; | ||||
| import eu.kanade.mangafeed.ui.base.adapter.FlexibleViewHolder; | ||||
| import eu.kanade.mangafeed.ui.base.adapter.OnStartDragListener; | ||||
|  | ||||
| public class CategoryHolder extends FlexibleViewHolder { | ||||
|  | ||||
| @@ -20,11 +23,21 @@ public class CategoryHolder extends FlexibleViewHolder { | ||||
|  | ||||
|     @Bind(R.id.image) ImageView image; | ||||
|     @Bind(R.id.title) TextView title; | ||||
|     @Bind(R.id.reorder) ImageView reorder; | ||||
|  | ||||
|     public CategoryHolder(View view, CategoryAdapter adapter, OnListItemClickListener listener) { | ||||
|     public CategoryHolder(View view, CategoryAdapter adapter, | ||||
|                           OnListItemClickListener listener, OnStartDragListener dragListener) { | ||||
|         super(view, adapter, listener); | ||||
|         ButterKnife.bind(this, view); | ||||
|         this.view = view; | ||||
|  | ||||
|         reorder.setOnTouchListener((v, event) -> { | ||||
|             if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { | ||||
|                 dragListener.onStartDrag(this); | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     public void onSetValues(Category category, ColorGenerator generator) { | ||||
|   | ||||
| @@ -0,0 +1,16 @@ | ||||
| package eu.kanade.mangafeed.ui.library.category; | ||||
|  | ||||
| import eu.kanade.mangafeed.ui.base.adapter.ItemTouchHelperAdapter; | ||||
| import eu.kanade.mangafeed.ui.base.adapter.SimpleItemTouchHelperCallback; | ||||
|  | ||||
| public class CategoryItemTouchHelper extends SimpleItemTouchHelperCallback { | ||||
|  | ||||
|     public CategoryItemTouchHelper(ItemTouchHelperAdapter adapter) { | ||||
|         super(adapter); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isItemViewSwipeEnabled() { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| @@ -15,6 +15,8 @@ public class CategoryPresenter extends BasePresenter<CategoryFragment> { | ||||
|  | ||||
|     @Inject DatabaseHelper db; | ||||
|  | ||||
|     private List<Category> categories; | ||||
|  | ||||
|     private static final int GET_CATEGORIES = 1; | ||||
|  | ||||
|     @Override | ||||
| @@ -23,6 +25,7 @@ public class CategoryPresenter extends BasePresenter<CategoryFragment> { | ||||
|  | ||||
|         restartableLatestCache(GET_CATEGORIES, | ||||
|                 () -> db.getCategories().createObservable() | ||||
|                         .doOnNext(categories -> this.categories = categories) | ||||
|                         .observeOn(AndroidSchedulers.mainThread()), | ||||
|                 CategoryFragment::setCategories); | ||||
|  | ||||
| @@ -30,10 +33,36 @@ public class CategoryPresenter extends BasePresenter<CategoryFragment> { | ||||
|     } | ||||
|  | ||||
|     public void createCategory(String name) { | ||||
|         db.insertCategory(Category.create(name)).createObservable().subscribe(); | ||||
|         Category cat = Category.create(name); | ||||
|  | ||||
|         // Set the new item in the last position | ||||
|         int max = 0; | ||||
|         if (categories != null) { | ||||
|             for (Category cat2 : categories) { | ||||
|                 if (cat2.order > max) { | ||||
|                     max = cat2.order + 1; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         cat.order = max; | ||||
|  | ||||
|         db.insertCategory(cat).createObservable().subscribe(); | ||||
|     } | ||||
|  | ||||
|     public void deleteCategories(List<Category> categories) { | ||||
|         db.deleteCategories(categories).createObservable().subscribe(); | ||||
|     } | ||||
|  | ||||
|     public void reorderCategories(List<Category> categories) { | ||||
|         for (int i = 0; i < categories.size(); i++) { | ||||
|             categories.get(i).order = i; | ||||
|         } | ||||
|  | ||||
|         db.insertCategories(categories).createObservable().subscribe(); | ||||
|     } | ||||
|  | ||||
|     public void renameCategory(Category category, String name) { | ||||
|         category.name = name; | ||||
|         db.insertCategory(category).createObservable().subscribe(); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user