Browse Source

Allow to reorder and rename categories

inorichi 10 năm trước cách đây
mục cha
commit
3f1f9ea9f2
20 tập tin đã thay đổi với 280 bổ sung và 20 xóa
  1. 7 0
      app/src/main/java/eu/kanade/mangafeed/data/database/DatabaseHelper.java
  2. 6 0
      app/src/main/java/eu/kanade/mangafeed/data/database/models/Category.java
  3. 9 1
      app/src/main/java/eu/kanade/mangafeed/data/database/tables/CategoryTable.java
  4. 57 0
      app/src/main/java/eu/kanade/mangafeed/ui/base/adapter/ItemTouchHelperAdapter.java
  5. 13 0
      app/src/main/java/eu/kanade/mangafeed/ui/base/adapter/OnStartDragListener.java
  6. 43 0
      app/src/main/java/eu/kanade/mangafeed/ui/base/adapter/SimpleItemTouchHelperCallback.java
  7. 29 9
      app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryAdapter.java
  8. 35 4
      app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryFragment.java
  9. 14 1
      app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryHolder.java
  10. 16 0
      app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryItemTouchHelper.java
  11. 30 1
      app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryPresenter.java
  12. BIN
      app/src/main/res/drawable-hdpi/ic_reorder_grey_600_24dp.png
  13. BIN
      app/src/main/res/drawable-ldpi/ic_reorder_grey_600_24dp.png
  14. BIN
      app/src/main/res/drawable-mdpi/ic_reorder_grey_600_24dp.png
  15. BIN
      app/src/main/res/drawable-xhdpi/ic_reorder_grey_600_24dp.png
  16. BIN
      app/src/main/res/drawable-xxhdpi/ic_reorder_grey_600_24dp.png
  17. BIN
      app/src/main/res/drawable-xxxhdpi/ic_reorder_grey_600_24dp.png
  18. 14 2
      app/src/main/res/layout/item_edit_categories.xml
  19. 6 2
      app/src/main/res/menu/category_selection.xml
  20. 1 0
      app/src/main/res/values/strings.xml

+ 7 - 0
app/src/main/java/eu/kanade/mangafeed/data/database/DatabaseHelper.java

@@ -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)

+ 6 - 0
app/src/main/java/eu/kanade/mangafeed/data/database/models/Category.java

@@ -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) {

+ 9 - 1
app/src/main/java/eu/kanade/mangafeed/data/database/tables/CategoryTable.java

@@ -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"
                 + ");";
 
     }

+ 57 - 0
app/src/main/java/eu/kanade/mangafeed/ui/base/adapter/ItemTouchHelperAdapter.java

@@ -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);
+}

+ 13 - 0
app/src/main/java/eu/kanade/mangafeed/ui/base/adapter/OnStartDragListener.java

@@ -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);
+}

+ 43 - 0
app/src/main/java/eu/kanade/mangafeed/ui/base/adapter/SimpleItemTouchHelperCallback.java

@@ -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());
+    }
+
+}

+ 29 - 9
app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryAdapter.java

@@ -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) {
+
     }
 }

+ 35 - 4
app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryFragment.java

@@ -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);
+    }
+
 }

+ 14 - 1
app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryHolder.java

@@ -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) {

+ 16 - 0
app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryItemTouchHelper.java

@@ -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;
+    }
+}

+ 30 - 1
app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryPresenter.java

@@ -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();
+    }
 }

BIN
app/src/main/res/drawable-hdpi/ic_reorder_grey_600_24dp.png


BIN
app/src/main/res/drawable-ldpi/ic_reorder_grey_600_24dp.png


BIN
app/src/main/res/drawable-mdpi/ic_reorder_grey_600_24dp.png


BIN
app/src/main/res/drawable-xhdpi/ic_reorder_grey_600_24dp.png


BIN
app/src/main/res/drawable-xxhdpi/ic_reorder_grey_600_24dp.png


BIN
app/src/main/res/drawable-xxxhdpi/ic_reorder_grey_600_24dp.png


+ 14 - 2
app/src/main/res/layout/item_edit_categories.xml

@@ -21,14 +21,26 @@
         android:layout_marginRight="@dimen/margin_right"
         android:layout_marginEnd="@dimen/margin_right"/>
 
+    <ImageView
+        android:id="@+id/reorder"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:layout_marginLeft="@dimen/margin_left"
+        android:layout_marginStart="@dimen/margin_left"
+        android:layout_marginRight="@dimen/margin_right"
+        android:layout_marginEnd="@dimen/margin_right"
+        android:scaleType="center"
+        android:layout_alignParentRight="true"
+        android:src="@drawable/ic_reorder_grey_600_24dp"/>
+
     <TextView
         android:id="@+id/title"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginRight="@dimen/margin_right"
-        android:layout_marginEnd="@dimen/margin_right"
         android:layout_toRightOf="@id/image"
         android:layout_toEndOf="@id/image"
+        android:layout_toLeftOf="@id/reorder"
+        android:layout_toStartOf="@id/reorder"
         android:layout_centerInParent="true"
         android:ellipsize="end"
         android:maxLines="1"

+ 6 - 2
app/src/main/res/menu/category_selection.xml

@@ -3,10 +3,14 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:app="http://schemas.android.com/apk/res-auto">
 
+    <item android:id="@+id/action_edit"
+          android:title="@string/action_edit"
+          android:icon="@drawable/ic_create"
+          app:showAsAction="ifRoom"/>
+
     <item android:id="@+id/action_delete"
           android:title="@string/action_delete"
           android:icon="@drawable/ic_action_delete"
-          android:orderInCategory="1"
-          app:showAsAction="always"/>
+          app:showAsAction="ifRoom"/>
 
 </menu>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -24,6 +24,7 @@
     <string name="action_edit">Edit</string>
     <string name="action_add_category">Add category</string>
     <string name="action_edit_categories">Edit categories</string>
+    <string name="action_rename_category">Rename category</string>
     <string name="action_sort_up">Sort up</string>
     <string name="action_sort_down">Sort down</string>
     <string name="action_show_unread">Unread</string>