Browse Source

Allow to create/remove categories. Some refactoring.

inorichi 9 years ago
parent
commit
e548cbf171
31 changed files with 539 additions and 75 deletions
  1. 1 0
      app/build.gradle
  2. 6 0
      app/src/main/java/eu/kanade/mangafeed/data/database/DatabaseHelper.java
  3. 1 1
      app/src/main/java/eu/kanade/mangafeed/data/download/DownloadManager.java
  4. 2 7
      app/src/main/java/eu/kanade/mangafeed/data/preference/PreferencesHelper.java
  5. 2 3
      app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java
  6. 14 5
      app/src/main/java/eu/kanade/mangafeed/ui/base/activity/BaseActivity.java
  7. 2 2
      app/src/main/java/eu/kanade/mangafeed/ui/decoration/DividerItemDecoration.java
  8. 28 27
      app/src/main/java/eu/kanade/mangafeed/ui/library/LibraryCategoryFragment.java
  9. 10 0
      app/src/main/java/eu/kanade/mangafeed/ui/library/LibraryFragment.java
  10. 14 3
      app/src/main/java/eu/kanade/mangafeed/ui/library/LibraryPresenter.java
  11. 60 0
      app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryAdapter.java
  12. 150 0
      app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryFragment.java
  13. 45 0
      app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryHolder.java
  14. 39 0
      app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryPresenter.java
  15. 56 11
      app/src/main/java/eu/kanade/mangafeed/ui/main/MainActivity.java
  16. 4 6
      app/src/main/java/eu/kanade/mangafeed/ui/manga/MangaActivity.java
  17. 5 5
      app/src/main/java/eu/kanade/mangafeed/ui/manga/chapter/ChaptersFragment.java
  18. 3 3
      app/src/main/java/eu/kanade/mangafeed/ui/setting/SettingsActivity.java
  19. BIN
      app/src/main/res/drawable-hdpi/ic_action_add_18dp.png
  20. BIN
      app/src/main/res/drawable-ldpi/ic_action_add_18dp.png
  21. BIN
      app/src/main/res/drawable-mdpi/ic_action_add_18dp.png
  22. BIN
      app/src/main/res/drawable-xhdpi/ic_action_add_18dp.png
  23. BIN
      app/src/main/res/drawable-xxhdpi/ic_action_add_18dp.png
  24. BIN
      app/src/main/res/drawable-xxxhdpi/ic_action_add_18dp.png
  25. 26 0
      app/src/main/res/layout/fragment_edit_categories.xml
  26. 39 0
      app/src/main/res/layout/item_edit_categories.xml
  27. 12 0
      app/src/main/res/menu/category_selection.xml
  28. 9 1
      app/src/main/res/menu/library.xml
  29. 5 0
      app/src/main/res/values/dimens.xml
  30. 5 1
      app/src/main/res/values/strings.xml
  31. 1 0
      build.gradle

+ 1 - 0
app/build.gradle

@@ -113,6 +113,7 @@ dependencies {
     compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
     compile 'eu.davidea:flexible-adapter:4.2.0@aar'
     compile 'com.nononsenseapps:filepicker:2.5.0'
+    compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
 
     compile "com.google.dagger:dagger:$DAGGER_VERSION"
     apt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"

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

@@ -367,6 +367,12 @@ public class DatabaseHelper {
                 .prepare();
     }
 
+    public PreparedDeleteCollectionOfObjects<Category> deleteCategories(List<Category> categories) {
+        return db.delete()
+                .objects(categories)
+                .prepare();
+    }
+
     public PreparedPutObject<MangaCategory> insertMangaCategory(MangaCategory mangaCategory) {
         return db.put()
                 .object(mangaCategory)

+ 1 - 1
app/src/main/java/eu/kanade/mangafeed/data/download/DownloadManager.java

@@ -71,7 +71,7 @@ public class DownloadManager {
         if (threadsNumberSubscription != null && !threadsNumberSubscription.isUnsubscribed())
             threadsNumberSubscription.unsubscribe();
 
-        threadsNumberSubscription = preferences.getDownloadTheadsObservable()
+        threadsNumberSubscription = preferences.downloadThreads().asObservable()
                 .subscribe(threadsNumber::onNext);
 
         downloadsSubscription = downloadsQueueSubject

+ 2 - 7
app/src/main/java/eu/kanade/mangafeed/data/preference/PreferencesHelper.java

@@ -13,7 +13,6 @@ import java.io.File;
 import eu.kanade.mangafeed.R;
 import eu.kanade.mangafeed.data.mangasync.base.MangaSyncService;
 import eu.kanade.mangafeed.data.source.base.Source;
-import rx.Observable;
 
 public class PreferencesHelper {
 
@@ -138,12 +137,8 @@ public class PreferencesHelper {
         prefs.edit().putString(getKey(R.string.pref_download_directory_key), path).apply();
     }
 
-    public int getDownloadThreads() {
-        return prefs.getInt(getKey(R.string.pref_download_slots_key), 1);
-    }
-
-    public Observable<Integer> getDownloadTheadsObservable() {
-        return rxPrefs.getInteger(getKey(R.string.pref_download_slots_key), 1).asObservable();
+    public Preference<Integer> downloadThreads() {
+        return rxPrefs.getInteger(getKey(R.string.pref_download_slots_key), 1);
     }
 
 }

+ 2 - 3
app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java

@@ -14,7 +14,7 @@ import eu.kanade.mangafeed.injection.module.AppModule;
 import eu.kanade.mangafeed.injection.module.DataModule;
 import eu.kanade.mangafeed.ui.catalogue.CataloguePresenter;
 import eu.kanade.mangafeed.ui.download.DownloadPresenter;
-import eu.kanade.mangafeed.ui.library.LibraryCategoryFragment;
+import eu.kanade.mangafeed.ui.library.category.CategoryPresenter;
 import eu.kanade.mangafeed.ui.library.LibraryPresenter;
 import eu.kanade.mangafeed.ui.manga.MangaActivity;
 import eu.kanade.mangafeed.ui.manga.MangaPresenter;
@@ -43,14 +43,13 @@ public interface AppComponent {
     void inject(ReaderPresenter readerPresenter);
     void inject(DownloadPresenter downloadPresenter);
     void inject(MyAnimeListPresenter myAnimeListPresenter);
+    void inject(CategoryPresenter categoryPresenter);
 
     void inject(ReaderActivity readerActivity);
     void inject(MangaActivity mangaActivity);
     void inject(SettingsAccountsFragment settingsAccountsFragment);
     void inject(SettingsActivity settingsActivity);
 
-    void inject(LibraryCategoryFragment libraryCategoryFragment);
-
     void inject(Source source);
 
     void inject(MyAnimeList myAnimeList);

+ 14 - 5
app/src/main/java/eu/kanade/mangafeed/ui/base/activity/BaseActivity.java

@@ -1,14 +1,27 @@
 package eu.kanade.mangafeed.ui.base.activity;
 
-import android.content.Context;
+import android.os.Bundle;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
 import android.view.MenuItem;
 
 import de.greenrobot.event.EventBus;
+import icepick.Icepick;
 
 public class BaseActivity extends AppCompatActivity {
 
+    @Override
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        Icepick.restoreInstanceState(this, savedState);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        Icepick.saveInstanceState(this, outState);
+    }
+
     protected void setupToolbar(Toolbar toolbar) {
         setSupportActionBar(toolbar);
         if (getSupportActionBar() != null)
@@ -35,10 +48,6 @@ public class BaseActivity extends AppCompatActivity {
             getSupportActionBar().setSubtitle(getString(titleResource));
     }
 
-    public Context getActivity() {
-        return this;
-    }
-
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {

+ 2 - 2
app/src/main/java/eu/kanade/mangafeed/ui/decoration/DividerItemDecoration.java

@@ -33,8 +33,8 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration {
     }
 
     @Override
-    public void onDrawOver(Canvas c, RecyclerView parent) {
-        if (mDivider == null) { super.onDrawOver(c, parent); return; }
+    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        if (mDivider == null) { super.onDrawOver(c, parent, state); return; }
 
         if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
             final int left = parent.getPaddingLeft();

+ 28 - 27
app/src/main/java/eu/kanade/mangafeed/ui/library/LibraryCategoryFragment.java

@@ -14,15 +14,11 @@ import com.f2prateek.rx.preferences.Preference;
 
 import java.util.List;
 
-import javax.inject.Inject;
-
 import butterknife.Bind;
 import butterknife.ButterKnife;
-import eu.kanade.mangafeed.App;
 import eu.kanade.mangafeed.R;
 import eu.kanade.mangafeed.data.database.models.Category;
 import eu.kanade.mangafeed.data.database.models.Manga;
-import eu.kanade.mangafeed.data.preference.PreferencesHelper;
 import eu.kanade.mangafeed.event.LibraryMangasEvent;
 import eu.kanade.mangafeed.ui.base.activity.BaseActivity;
 import eu.kanade.mangafeed.ui.base.adapter.FlexibleViewHolder;
@@ -37,8 +33,6 @@ import rx.Subscription;
 public class LibraryCategoryFragment extends BaseFragment implements
         ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
 
-    @Inject PreferencesHelper preferences;
-
     @Bind(R.id.library_mangas) AutofitRecyclerView recycler;
 
     @State Category category;
@@ -47,20 +41,12 @@ public class LibraryCategoryFragment extends BaseFragment implements
 
     private Subscription numColumnsSubscription;
 
-    private static final int INVALID_POSITION = -1;
-
     public static LibraryCategoryFragment newInstance(Category category) {
         LibraryCategoryFragment fragment = new LibraryCategoryFragment();
         fragment.category = category;
         return fragment;
     }
 
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        App.get(getActivity()).getComponent().inject(this);
-    }
-
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
         // Inflate the layout for this fragment
@@ -75,8 +61,8 @@ public class LibraryCategoryFragment extends BaseFragment implements
 
         Preference<Integer> columnsPref = getResources().getConfiguration()
                 .orientation == Configuration.ORIENTATION_PORTRAIT ?
-                preferences.portraitColumns() :
-                preferences.landscapeColumns();
+                getLibraryPresenter().preferences.portraitColumns() :
+                getLibraryPresenter().preferences.landscapeColumns();
 
         numColumnsSubscription = columnsPref.asObservable()
                 .subscribe(recycler::setSpanCount);
@@ -110,6 +96,7 @@ public class LibraryCategoryFragment extends BaseFragment implements
 
     @EventBusHook
     public void onEventMainThread(LibraryMangasEvent event) {
+        destroyActionModeIfNeeded();
         setMangas(event.getMangas().get(category.id));
     }
 
@@ -128,7 +115,7 @@ public class LibraryCategoryFragment extends BaseFragment implements
 
     @Override
     public boolean onListItemClick(int position) {
-        if (actionMode != null && position != INVALID_POSITION) {
+        if (actionMode != null && position != -1) {
             toggleSelection(position);
             return true;
         } else {
@@ -145,6 +132,22 @@ public class LibraryCategoryFragment extends BaseFragment implements
         toggleSelection(position);
     }
 
+    private void toggleSelection(int position) {
+        adapter.toggleSelection(position, false);
+
+        int count = adapter.getSelectedItemCount();
+        if (count == 0) {
+            actionMode.finish();
+        } else {
+            setContextTitle(count);
+            actionMode.invalidate();
+        }
+    }
+
+    private void setContextTitle(int count) {
+        actionMode.setTitle(getString(R.string.label_selected, count));
+    }
+
     @Override
     public boolean onCreateActionMode(ActionMode mode, Menu menu) {
         mode.getMenuInflater().inflate(R.menu.library_selection, menu);
@@ -169,19 +172,17 @@ public class LibraryCategoryFragment extends BaseFragment implements
         actionMode = null;
     }
 
-    private void toggleSelection(int position) {
-        adapter.toggleSelection(position, false);
-
-        int count = adapter.getSelectedItemCount();
-        if (count == 0) {
+    public void destroyActionModeIfNeeded() {
+        if (actionMode != null) {
             actionMode.finish();
-        } else {
-            setContextTitle(count);
-            actionMode.invalidate();
         }
     }
 
-    private void setContextTitle(int count) {
-        actionMode.setTitle(getString(R.string.selected_chapters_title, count));
+    private LibraryFragment getLibraryFragment() {
+        return (LibraryFragment) getParentFragment();
+    }
+
+    private LibraryPresenter getLibraryPresenter() {
+        return getLibraryFragment().getPresenter();
     }
 }

+ 10 - 0
app/src/main/java/eu/kanade/mangafeed/ui/library/LibraryFragment.java

@@ -4,6 +4,7 @@ import android.content.Intent;
 import android.os.Bundle;
 import android.support.design.widget.AppBarLayout;
 import android.support.design.widget.TabLayout;
+import android.support.v4.app.Fragment;
 import android.support.v4.view.ViewPager;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -21,6 +22,7 @@ import eu.kanade.mangafeed.R;
 import eu.kanade.mangafeed.data.database.models.Category;
 import eu.kanade.mangafeed.data.sync.LibraryUpdateService;
 import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment;
+import eu.kanade.mangafeed.ui.library.category.CategoryFragment;
 import eu.kanade.mangafeed.ui.main.MainActivity;
 import nucleus.factory.RequiresPresenter;
 
@@ -82,12 +84,20 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter> {
                     getActivity().startService(intent);
                 }
 
+                return true;
+            case R.id.action_edit_categories:
+                onEditCategories();
                 return true;
         }
 
         return super.onOptionsItemSelected(item);
     }
 
+    private void onEditCategories() {
+        Fragment fragment = CategoryFragment.newInstance();
+        ((MainActivity) getActivity()).pushFragment(fragment);
+    }
+
     public void onNextCategories(List<Category> categories) {
         List<Category> actualCategories = new ArrayList<>();
 

+ 14 - 3
app/src/main/java/eu/kanade/mangafeed/ui/library/LibraryPresenter.java

@@ -11,21 +11,25 @@ import javax.inject.Inject;
 import de.greenrobot.event.EventBus;
 import eu.kanade.mangafeed.data.cache.CoverCache;
 import eu.kanade.mangafeed.data.database.DatabaseHelper;
+import eu.kanade.mangafeed.data.database.models.Category;
 import eu.kanade.mangafeed.data.database.models.Manga;
 import eu.kanade.mangafeed.data.preference.PreferencesHelper;
 import eu.kanade.mangafeed.data.source.SourceManager;
 import eu.kanade.mangafeed.event.LibraryMangasEvent;
 import eu.kanade.mangafeed.ui.base.presenter.BasePresenter;
 import rx.Observable;
+import rx.android.schedulers.AndroidSchedulers;
 import rx.schedulers.Schedulers;
 
 public class LibraryPresenter extends BasePresenter<LibraryFragment> {
 
     @Inject DatabaseHelper db;
-    @Inject PreferencesHelper prefs;
+    @Inject PreferencesHelper preferences;
     @Inject CoverCache coverCache;
     @Inject SourceManager sourceManager;
 
+    protected List<Category> categories;
+
     private static final int GET_CATEGORIES = 1;
 
     @Override
@@ -33,7 +37,7 @@ public class LibraryPresenter extends BasePresenter<LibraryFragment> {
         super.onCreate(savedState);
 
         restartableLatestCache(GET_CATEGORIES,
-                () -> db.getCategories().createObservable(),
+                this::getCategoriesObservable,
                 LibraryFragment::onNextCategories);
 
         start(GET_CATEGORIES);
@@ -41,9 +45,16 @@ public class LibraryPresenter extends BasePresenter<LibraryFragment> {
         add(getLibraryMangasObservable()
                 .subscribe(mangas ->
                         EventBus.getDefault().postSticky(new LibraryMangasEvent(mangas))));
+
+    }
+
+    public Observable<List<Category>> getCategoriesObservable() {
+        return db.getCategories().createObservable()
+                .doOnNext(categories -> this.categories = categories)
+                .observeOn(AndroidSchedulers.mainThread());
     }
 
-    public Observable<Map<Integer, List<Manga>>> getLibraryMangasObservable() {
+    private Observable<Map<Integer, List<Manga>>> getLibraryMangasObservable() {
         return db.getLibraryMangas().createObservable()
                 .flatMap(mangas -> Observable.from(mangas)
                         .groupBy(manga -> manga.category)

+ 60 - 0
app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryAdapter.java

@@ -0,0 +1,60 @@
+package eu.kanade.mangafeed.ui.library.category;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.amulyakhare.textdrawable.util.ColorGenerator;
+
+import java.util.List;
+
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.kanade.mangafeed.R;
+import eu.kanade.mangafeed.data.database.models.Category;
+
+public class CategoryAdapter extends FlexibleAdapter<CategoryHolder, Category> {
+    
+    private CategoryFragment fragment;
+    private ColorGenerator generator;
+
+    public CategoryAdapter(CategoryFragment fragment) {
+        this.fragment = fragment;
+        setHasStableIds(true);
+        generator = ColorGenerator.DEFAULT;
+    }
+
+    public void setItems(List<Category> items) {
+        mItems = items;
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mItems.get(position).id;
+    }
+
+    @Override
+    public void updateDataSet(String param) {
+        
+    }
+
+    @Override
+    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);
+    }
+
+    @Override
+    public void onBindViewHolder(CategoryHolder holder, int position) {
+        final Category category = getItem(position);
+        holder.onSetValues(category, generator);
+
+        //When user scrolls this bind the correct selection status
+        holder.itemView.setActivated(isSelected(position));
+    }
+
+    public ColorGenerator getColorGenerator() {
+        return generator;
+    }
+}

+ 150 - 0
app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryFragment.java

@@ -0,0 +1,150 @@
+package eu.kanade.mangafeed.ui.library.category;
+
+import android.os.Bundle;
+import android.support.design.widget.FloatingActionButton;
+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.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.afollestad.materialdialogs.MaterialDialog;
+
+import java.util.List;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+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.fragment.BaseRxFragment;
+import eu.kanade.mangafeed.ui.decoration.DividerItemDecoration;
+import eu.kanade.mangafeed.ui.library.LibraryCategoryAdapter;
+import nucleus.factory.RequiresPresenter;
+import rx.Observable;
+
+@RequiresPresenter(CategoryPresenter.class)
+public class CategoryFragment extends BaseRxFragment<CategoryPresenter>
+        implements ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
+
+    @Bind(R.id.categories_list) RecyclerView recycler;
+    @Bind(R.id.fab) FloatingActionButton fab;
+
+    private CategoryAdapter adapter;
+    private ActionMode actionMode;
+
+    public static CategoryFragment newInstance() {
+        return new CategoryFragment();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+        View view = inflater.inflate(R.layout.fragment_edit_categories, container, false);
+        ButterKnife.bind(this, view);
+
+        setToolbarTitle(R.string.action_edit_categories);
+
+        adapter = new CategoryAdapter(this);
+        recycler.setLayoutManager(new LinearLayoutManager(getActivity()));
+        recycler.setHasFixedSize(true);
+        recycler.setAdapter(adapter);
+        recycler.addItemDecoration(new DividerItemDecoration(
+                ResourcesCompat.getDrawable(getResources(), R.drawable.line_divider, null)));
+
+        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();
+        });
+
+        return view;
+    }
+
+    public void setCategories(List<Category> categories) {
+        destroyActionModeIfNeeded();
+        adapter.setItems(categories);
+    }
+
+    private List<Category> getSelectedCategories() {
+        // Create a blocking copy of the selected categories
+        return Observable.from(adapter.getSelectedItems())
+                .map(adapter::getItem).toList().toBlocking().single();
+    }
+
+    @Override
+    public boolean onListItemClick(int position) {
+        if (actionMode != null && position != -1) {
+            toggleSelection(position);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void onListItemLongClick(int position) {
+        if (actionMode == null)
+            actionMode = ((BaseActivity) getActivity()).startSupportActionMode(this);
+
+        toggleSelection(position);
+    }
+
+    private void toggleSelection(int position) {
+        adapter.toggleSelection(position, false);
+
+        int count = adapter.getSelectedItemCount();
+        if (count == 0) {
+            actionMode.finish();
+        } else {
+            setContextTitle(count);
+            actionMode.invalidate();
+        }
+    }
+
+    private void setContextTitle(int count) {
+        actionMode.setTitle(getString(R.string.label_selected, count));
+    }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        mode.getMenuInflater().inflate(R.menu.category_selection, menu);
+        adapter.setMode(LibraryCategoryAdapter.MODE_MULTI);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        return false;
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.action_delete:
+                getPresenter().deleteCategories(getSelectedCategories());
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        adapter.setMode(LibraryCategoryAdapter.MODE_SINGLE);
+        adapter.clearSelection();
+        actionMode = null;
+    }
+
+    public void destroyActionModeIfNeeded() {
+        if (actionMode != null) {
+            actionMode.finish();
+        }
+    }
+
+}

+ 45 - 0
app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryHolder.java

@@ -0,0 +1,45 @@
+package eu.kanade.mangafeed.ui.library.category;
+
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.amulyakhare.textdrawable.TextDrawable;
+import com.amulyakhare.textdrawable.util.ColorGenerator;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import eu.kanade.mangafeed.R;
+import eu.kanade.mangafeed.data.database.models.Category;
+import eu.kanade.mangafeed.ui.base.adapter.FlexibleViewHolder;
+
+public class CategoryHolder extends FlexibleViewHolder {
+
+    private View view;
+
+    @Bind(R.id.image) ImageView image;
+    @Bind(R.id.title) TextView title;
+
+    public CategoryHolder(View view, CategoryAdapter adapter, OnListItemClickListener listener) {
+        super(view, adapter, listener);
+        ButterKnife.bind(this, view);
+        this.view = view;
+    }
+
+    public void onSetValues(Category category, ColorGenerator generator) {
+        title.setText(category.name);
+        image.setImageDrawable(getRound(category.name.substring(0, 1), generator));
+    }
+
+    private TextDrawable getRound(String text, ColorGenerator generator) {
+        return TextDrawable.builder().buildRound(text, generator.getColor(text));
+    }
+
+    @OnClick(R.id.image)
+    void onImageClick() {
+        // Simulate long click on this view to enter selection mode
+        onLongClick(view);
+    }
+
+}

+ 39 - 0
app/src/main/java/eu/kanade/mangafeed/ui/library/category/CategoryPresenter.java

@@ -0,0 +1,39 @@
+package eu.kanade.mangafeed.ui.library.category;
+
+import android.os.Bundle;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import eu.kanade.mangafeed.data.database.DatabaseHelper;
+import eu.kanade.mangafeed.data.database.models.Category;
+import eu.kanade.mangafeed.ui.base.presenter.BasePresenter;
+import rx.android.schedulers.AndroidSchedulers;
+
+public class CategoryPresenter extends BasePresenter<CategoryFragment> {
+
+    @Inject DatabaseHelper db;
+
+    private static final int GET_CATEGORIES = 1;
+
+    @Override
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+
+        restartableLatestCache(GET_CATEGORIES,
+                () -> db.getCategories().createObservable()
+                        .observeOn(AndroidSchedulers.mainThread()),
+                CategoryFragment::setCategories);
+
+        start(GET_CATEGORIES);
+    }
+
+    public void createCategory(String name) {
+        db.insertCategory(Category.create(name)).createObservable().subscribe();
+    }
+
+    public void deleteCategories(List<Category> categories) {
+        db.deleteCategories(categories).createObservable().subscribe();
+    }
+}

+ 56 - 11
app/src/main/java/eu/kanade/mangafeed/ui/main/MainActivity.java

@@ -4,6 +4,7 @@ import android.content.Intent;
 import android.os.Bundle;
 import android.support.design.widget.AppBarLayout;
 import android.support.v4.app.Fragment;
+import android.support.v4.widget.DrawerLayout;
 import android.support.v7.widget.Toolbar;
 import android.widget.FrameLayout;
 
@@ -19,23 +20,23 @@ import eu.kanade.mangafeed.ui.catalogue.CatalogueFragment;
 import eu.kanade.mangafeed.ui.download.DownloadFragment;
 import eu.kanade.mangafeed.ui.library.LibraryFragment;
 import eu.kanade.mangafeed.ui.setting.SettingsActivity;
+import icepick.State;
 import nucleus.view.ViewWithPresenter;
 
 public class MainActivity extends BaseActivity {
 
     @Bind(R.id.appbar) AppBarLayout appBar;
     @Bind(R.id.toolbar) Toolbar toolbar;
-
     @Bind(R.id.drawer_container) FrameLayout container;
 
     private Drawer drawer;
     private FragmentStack fragmentStack;
 
-    private final static String SELECTED_ITEM = "selected_item";
+    @State int selectedItem;
 
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
         setContentView(R.layout.activity_main);
         ButterKnife.bind(this);
 
@@ -52,6 +53,13 @@ public class MainActivity extends BaseActivity {
                 .withRootView(container)
                 .withToolbar(toolbar)
                 .withActionBarDrawerToggleAnimated(true)
+                .withOnDrawerNavigationListener(view -> {
+                    if (fragmentStack.size() > 1) {
+                        onBackPressed();
+                        return true;
+                    }
+                    return false;
+                })
                 .addDrawerItems(
                         new PrimaryDrawerItem()
                                 .withName(R.string.label_library)
@@ -70,7 +78,7 @@ public class MainActivity extends BaseActivity {
                                 .withIdentifier(R.id.nav_drawer_settings)
                                 .withSelectable(false)
                 )
-                .withSavedInstance(savedInstanceState)
+                .withSavedInstance(savedState)
                 .withOnDrawerItemClickListener(
                         (view, position, drawerItem) -> {
                             if (drawerItem != null) {
@@ -97,15 +105,23 @@ public class MainActivity extends BaseActivity {
                 )
                 .build();
 
-        if (savedInstanceState == null)
+        if (savedState != null) {
+            // Recover icon state after rotation
+            if (fragmentStack.size() > 1) {
+                showBackArrow();
+            }
+
+            // Set saved selection
+            drawer.setSelection(selectedItem, false);
+        } else {
+            // Set default selection
             drawer.setSelection(R.id.nav_drawer_library);
-        else
-            drawer.setSelection(savedInstanceState.getInt(SELECTED_ITEM), false);
+        }
     }
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
-        outState.putInt(SELECTED_ITEM, drawer.getCurrentSelection());
+        selectedItem = drawer.getCurrentSelection();
         super.onSaveInstanceState(outState);
     }
 
@@ -113,8 +129,37 @@ public class MainActivity extends BaseActivity {
         fragmentStack.replace(fragment);
     }
 
-    public Fragment getActiveFragment() {
-        return fragmentStack.peek();
+    public void pushFragment(Fragment fragment) {
+        fragmentStack.push(fragment);
+        if (fragmentStack.size() > 1) {
+            showBackArrow();
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (!fragmentStack.pop()) {
+            super.onBackPressed();
+        } else if (fragmentStack.size() == 1) {
+            showHamburgerIcon();
+            drawer.getActionBarDrawerToggle().syncState();
+        }
+    }
+
+    private void showHamburgerIcon() {
+        if (getSupportActionBar() != null) {
+            getSupportActionBar().setDisplayHomeAsUpEnabled(false);
+            drawer.getActionBarDrawerToggle().setDrawerIndicatorEnabled(true);
+            drawer.getDrawerLayout().setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
+        }
+    }
+
+    private void showBackArrow() {
+        if (getSupportActionBar() != null) {
+            drawer.getActionBarDrawerToggle().setDrawerIndicatorEnabled(false);
+            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+            drawer.getDrawerLayout().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+        }
     }
 
     public Toolbar getToolbar() {

+ 4 - 6
app/src/main/java/eu/kanade/mangafeed/ui/manga/MangaActivity.java

@@ -49,8 +49,8 @@ public class MangaActivity extends BaseRxActivity<MangaPresenter> {
     }
 
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
         App.get(this).getComponent().inject(this);
         setContentView(R.layout.activity_manga);
         ButterKnife.bind(this);
@@ -64,14 +64,12 @@ public class MangaActivity extends BaseRxActivity<MangaPresenter> {
 
         setupViewPager();
 
-        if (savedInstanceState == null)
+        if (savedState == null)
             getPresenter().queryManga(manga_id);
     }
 
     private void setupViewPager() {
-        adapter = new MangaDetailAdapter(
-                getSupportFragmentManager(),
-                getActivity());
+        adapter = new MangaDetailAdapter(getSupportFragmentManager(), this);
 
         view_pager.setAdapter(adapter);
         tabs.setupWithViewPager(view_pager);

+ 5 - 5
app/src/main/java/eu/kanade/mangafeed/ui/manga/chapter/ChaptersFragment.java

@@ -145,7 +145,7 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
         if (getPresenter().getChapters().isEmpty())
             initialFetchChapters();
 
-        closeActionMode();
+        destroyActionModeIfNeeded();
         adapter.setItems(chapters);
     }
 
@@ -254,7 +254,7 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
         return Observable.from(chapters);
     }
 
-    public void closeActionMode() {
+    public void destroyActionModeIfNeeded() {
         if (actionMode != null) {
             actionMode.finish();
         }
@@ -283,7 +283,7 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
                 .doOnCompleted(adapter::notifyDataSetChanged);
 
         getPresenter().downloadChapters(observable);
-        closeActionMode();
+        destroyActionModeIfNeeded();
         return true;
     }
 
@@ -311,7 +311,7 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
                 .finallyDo(dialog::dismiss);
 
         getPresenter().deleteChapters(observable);
-        closeActionMode();
+        destroyActionModeIfNeeded();
         return true;
     }
 
@@ -347,7 +347,7 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
     }
 
     private void setContextTitle(int count) {
-        actionMode.setTitle(getString(R.string.selected_chapters_title, count));
+        actionMode.setTitle(getString(R.string.label_selected, count));
     }
 
     public void setSortIcon() {

+ 3 - 3
app/src/main/java/eu/kanade/mangafeed/ui/setting/SettingsActivity.java

@@ -22,15 +22,15 @@ public class SettingsActivity extends BaseActivity {
     @Bind(R.id.toolbar) Toolbar toolbar;
 
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
         App.get(this).getComponent().inject(this);
         setContentView(R.layout.activity_preferences);
         ButterKnife.bind(this);
 
         setupToolbar(toolbar);
 
-        if (savedInstanceState == null)
+        if (savedState == null)
             getFragmentManager().beginTransaction().replace(R.id.settings_content,
                     new SettingsMainFragment())
                     .commit();

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


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


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


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


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


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


+ 26 - 0
app/src/main/res/layout/fragment_edit_categories.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.v7.widget.RecyclerView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/categories_list"
+        android:choiceMode="multipleChoice"
+        android:listSelector="@color/list_choice_pressed_bg_light" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="end|bottom"
+        android:layout_margin="@dimen/fab_margin"
+        android:clickable="true"
+        android:src="@drawable/ic_action_add_18dp"
+        app:borderWidth="0dp"/>
+
+</android.support.design.widget.CoordinatorLayout>

+ 39 - 0
app/src/main/res/layout/item_edit_categories.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                xmlns:tools="http://schemas.android.com/tools"
+                android:layout_width="match_parent"
+                android:layout_height="?android:attr/listPreferredItemHeightLarge"
+                android:paddingTop="@dimen/margin_top"
+                android:paddingBottom="@dimen/margin_bottom"
+                android:background="@drawable/selector_chapter_light">
+
+    <ImageView
+        android:id="@+id/image"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentStart="true"
+        android:layout_centerInParent="true"
+        android:elevation="4dp"
+        android:clickable="true"
+        android:layout_marginLeft="@dimen/margin_left"
+        android:layout_marginStart="@dimen/margin_left"
+        android:layout_marginRight="@dimen/margin_right"
+        android:layout_marginEnd="@dimen/margin_right"/>
+
+    <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_centerInParent="true"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
+        android:textColor="@color/primary_text"
+        tools:text="Title"/>
+
+</RelativeLayout>

+ 12 - 0
app/src/main/res/menu/category_selection.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item android:id="@+id/action_delete"
+          android:title="@string/action_delete"
+          android:icon="@drawable/ic_action_delete"
+          android:orderInCategory="1"
+          app:showAsAction="always"/>
+
+</menu>

+ 9 - 1
app/src/main/res/menu/library.xml

@@ -1,17 +1,25 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
+
     <item
         android:id="@+id/action_search"
         android:title="@string/action_search"
         android:icon="@drawable/ic_action_search"
         android:orderInCategory="100"
         app:showAsAction="collapseActionView|ifRoom"
-        app:actionViewClass="android.support.v7.widget.SearchView"/>
+        app:actionViewClass="android.support.v7.widget.SearchView" />
+
     <item
         android:id="@+id/action_refresh"
         android:title="@string/action_refresh"
         android:icon="@drawable/ic_action_refresh"
         android:orderInCategory="1"
         app:showAsAction="ifRoom" />
+
+    <item
+        android:id="@+id/action_edit_categories"
+        android:title="@string/action_edit_categories"
+        app:showAsAction="never" />
+
 </menu>

+ 5 - 0
app/src/main/res/values/dimens.xml

@@ -2,6 +2,11 @@
     <!-- Default screen margins, per the Android Design guidelines. -->
     <dimen name="activity_horizontal_margin">16dp</dimen>
     <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="margin_top">16dp</dimen>
+    <dimen name="margin_bottom">16dp</dimen>
+    <dimen name="margin_left">16dp</dimen>
+    <dimen name="margin_right">16dp</dimen>
+    <dimen name="fab_margin">16dp</dimen>
 
     <dimen name="text_headline">24sp</dimen>
     <dimen name="text_large_title">22sp</dimen>

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

@@ -1,12 +1,15 @@
 <resources>
     <string name="app_name">Mangafeed</string>
 
+    <string name="name">Name</string>
+
     <!-- Activities and fragments labels (toolbar title) -->
     <string name="label_settings">Settings</string>
     <string name="label_download_queue">Download queue</string>
     <string name="label_library">My library</string>
     <string name="label_recent_updates">Recent updates</string>
     <string name="label_catalogues">Catalogues</string>
+    <string name="label_selected">Selected: %1$d</string>
 
     <!-- Actions -->
     <string name="action_settings">Settings</string>
@@ -19,6 +22,8 @@
     <string name="action_delete">Delete</string>
     <string name="action_update">Update</string>
     <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_sort_up">Sort up</string>
     <string name="action_sort_down">Sort down</string>
     <string name="action_show_unread">Unread</string>
@@ -118,7 +123,6 @@
 
     <!-- Manga chapters fragment -->
     <string name="manga_chapters_tab">Chapters</string>
-    <string name="selected_chapters_title">Selected: %1$d</string>
     <string name="manga_chapter_no_title">No title</string>
     <string name="chapter_downloaded">Downloaded</string>
     <string name="chapter_queued">Queued</string>

+ 1 - 0
build.gradle

@@ -22,5 +22,6 @@ allprojects {
         maven { url "https://clojars.org/repo/" }
         maven { url "http://dl.bintray.com/davideas/maven" }
         maven { url "https://jitpack.io" }
+        maven { url 'http://dl.bintray.com/amulyakhare/maven' }
     }
 }