Merge pull request #178 from NoodleMage/kotlin
Category rewrite + FAB rewrite to Kotlin
@ -2,6 +2,7 @@ import java.text.SimpleDateFormat
|
|||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'com.neenbedankt.android-apt'
|
apply plugin: 'com.neenbedankt.android-apt'
|
||||||
apply plugin: 'me.tatarka.retrolambda'
|
apply plugin: 'me.tatarka.retrolambda'
|
||||||
|
|
||||||
|
@ -40,11 +40,10 @@
|
|||||||
android:parentActivityName=".ui.main.MainActivity" >
|
android:parentActivityName=".ui.main.MainActivity" >
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.library.category.CategoryActivity"
|
android:name=".ui.category.CategoryActivity"
|
||||||
android:label="@string/label_categories"
|
android:label="@string/label_categories"
|
||||||
android:parentActivityName=".ui.main.MainActivity">
|
android:parentActivityName=".ui.main.MainActivity">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.SettingsDownloadsFragment$CustomLayoutPickerActivity"
|
android:name=".ui.setting.SettingsDownloadsFragment$CustomLayoutPickerActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
@ -6,17 +6,17 @@ import javax.inject.Singleton;
|
|||||||
|
|
||||||
import dagger.Component;
|
import dagger.Component;
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService;
|
import eu.kanade.tachiyomi.data.download.DownloadService;
|
||||||
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService;
|
|
||||||
import eu.kanade.tachiyomi.data.source.base.Source;
|
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService;
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService;
|
||||||
import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService;
|
import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService;
|
||||||
|
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService;
|
||||||
|
import eu.kanade.tachiyomi.data.source.base.Source;
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateDownloader;
|
import eu.kanade.tachiyomi.data.updater.UpdateDownloader;
|
||||||
import eu.kanade.tachiyomi.injection.module.AppModule;
|
import eu.kanade.tachiyomi.injection.module.AppModule;
|
||||||
import eu.kanade.tachiyomi.injection.module.DataModule;
|
import eu.kanade.tachiyomi.injection.module.DataModule;
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter;
|
import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter;
|
||||||
|
import eu.kanade.tachiyomi.ui.category.CategoryPresenter;
|
||||||
import eu.kanade.tachiyomi.ui.download.DownloadPresenter;
|
import eu.kanade.tachiyomi.ui.download.DownloadPresenter;
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryPresenter;
|
import eu.kanade.tachiyomi.ui.library.LibraryPresenter;
|
||||||
import eu.kanade.tachiyomi.ui.library.category.CategoryPresenter;
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaActivity;
|
import eu.kanade.tachiyomi.ui.manga.MangaActivity;
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaPresenter;
|
import eu.kanade.tachiyomi.ui.manga.MangaPresenter;
|
||||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter;
|
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter;
|
||||||
|
@ -28,6 +28,7 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
|||||||
return makeMovementFlags(dragFlags, swipeFlags);
|
return makeMovementFlags(dragFlags, swipeFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
|
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
|
||||||
RecyclerView.ViewHolder target) {
|
RecyclerView.ViewHolder target) {
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.base.fab
|
||||||
|
|
||||||
|
import android.support.design.widget.CoordinatorLayout
|
||||||
|
import android.support.design.widget.FloatingActionButton
|
||||||
|
import android.support.v4.view.ViewCompat
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
open class FABAnimationBase() : FloatingActionButton.Behavior()
|
||||||
|
{
|
||||||
|
open val mIsAnimatingOut = false;
|
||||||
|
|
||||||
|
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout?, child: FloatingActionButton?, directTargetChild: View?, target: View?, nestedScrollAxes: Int): Boolean {
|
||||||
|
// Ensure we react to vertical scrolling
|
||||||
|
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
|
||||||
|
super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout?, child: FloatingActionButton?, target: View?, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int) {
|
||||||
|
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed)
|
||||||
|
if (dyConsumed > 0 && !this.mIsAnimatingOut && child!!.visibility == View.VISIBLE) {
|
||||||
|
// User scrolled down and the FAB is currently visible -> hide the FAB
|
||||||
|
animateOut(child)
|
||||||
|
} else if (dyConsumed < 0 && child!!.visibility != View.VISIBLE) {
|
||||||
|
// User scrolled up and the FAB is currently not visible -> show the FAB
|
||||||
|
animateIn(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun animateOut(button : FloatingActionButton) {}
|
||||||
|
open fun animateIn(button : FloatingActionButton) {}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.base.fab
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.design.widget.FloatingActionButton
|
||||||
|
import android.support.v4.view.animation.FastOutSlowInInterpolator
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.AnimationUtils
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
class FABAnimationUpDown() : FABAnimationBase()
|
||||||
|
{
|
||||||
|
override var mIsAnimatingOut: Boolean = false
|
||||||
|
get() = super.mIsAnimatingOut
|
||||||
|
|
||||||
|
private val INTERPOLATOR = FastOutSlowInInterpolator()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needed to prevent NoSuchMethodException
|
||||||
|
*/
|
||||||
|
constructor(ctx: Context, attrs: AttributeSet) : this() { }
|
||||||
|
|
||||||
|
override fun animateOut(button: FloatingActionButton) {
|
||||||
|
super.animateIn(button)
|
||||||
|
val anim = AnimationUtils.loadAnimation(button.context, R.anim.fab_hide_to_bottom)
|
||||||
|
anim.interpolator = INTERPOLATOR
|
||||||
|
anim.duration = 200L
|
||||||
|
anim.setAnimationListener(object : Animation.AnimationListener {
|
||||||
|
override fun onAnimationStart(animation: Animation) {
|
||||||
|
mIsAnimatingOut = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationEnd(animation: Animation) {
|
||||||
|
mIsAnimatingOut = false
|
||||||
|
button.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationRepeat(animation: Animation) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
button.startAnimation(anim)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun animateIn(button: FloatingActionButton) {
|
||||||
|
super.animateOut(button)
|
||||||
|
button.visibility = View.VISIBLE
|
||||||
|
val anim = AnimationUtils.loadAnimation(button.context, R.anim.fab_show_from_bottom)
|
||||||
|
anim.duration = 200L
|
||||||
|
anim.interpolator = INTERPOLATOR
|
||||||
|
button.startAnimation(anim)
|
||||||
|
}
|
||||||
|
}
|
@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* 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.tachiyomi.ui.base.fab;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.design.widget.CoordinatorLayout;
|
|
||||||
import android.support.design.widget.FloatingActionButton;
|
|
||||||
import android.support.v4.view.ViewCompat;
|
|
||||||
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.animation.Animation;
|
|
||||||
import android.view.animation.AnimationUtils;
|
|
||||||
import android.view.animation.Interpolator;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
|
|
||||||
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
|
|
||||||
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
|
|
||||||
private boolean mIsAnimatingOut = false;
|
|
||||||
|
|
||||||
public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
|
|
||||||
final View directTargetChild, final View target, final int nestedScrollAxes) {
|
|
||||||
// Ensure we react to vertical scrolling
|
|
||||||
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
|
|
||||||
|| super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
|
|
||||||
final View target, final int dxConsumed, final int dyConsumed,
|
|
||||||
final int dxUnconsumed, final int dyUnconsumed) {
|
|
||||||
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
|
|
||||||
if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
|
|
||||||
// User scrolled down and the FAB is currently visible -> hide the FAB
|
|
||||||
animateOut(child);
|
|
||||||
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
|
|
||||||
// User scrolled up and the FAB is currently not visible -> show the FAB
|
|
||||||
animateIn(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same animation that FloatingActionButton.Behavior uses to hide the FAB when the AppBarLayout exits
|
|
||||||
private void animateOut(final FloatingActionButton button) {
|
|
||||||
Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_hide_to_bottom);
|
|
||||||
anim.setInterpolator(INTERPOLATOR);
|
|
||||||
anim.setDuration(200L);
|
|
||||||
anim.setAnimationListener(new Animation.AnimationListener() {
|
|
||||||
public void onAnimationStart(Animation animation) {
|
|
||||||
ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onAnimationEnd(Animation animation) {
|
|
||||||
ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
|
|
||||||
button.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationRepeat(final Animation animation) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
button.startAnimation(anim);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same animation that FloatingActionButton.Behavior uses to show the FAB when the AppBarLayout enters
|
|
||||||
private void animateIn(FloatingActionButton button) {
|
|
||||||
button.setVisibility(View.VISIBLE);
|
|
||||||
Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_show_from_bottom);
|
|
||||||
anim.setDuration(200L);
|
|
||||||
anim.setInterpolator(INTERPOLATOR);
|
|
||||||
button.startAnimation(anim);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,276 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.category
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
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.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
|
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
|
||||||
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener
|
||||||
|
import eu.kanade.tachiyomi.ui.library.LibraryCategoryAdapter
|
||||||
|
import kotlinx.android.synthetic.main.activity_edit_categories.*
|
||||||
|
import kotlinx.android.synthetic.main.toolbar.*
|
||||||
|
import nucleus.factory.RequiresPresenter
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity that shows categories.
|
||||||
|
* Uses R.layout.activity_edit_categories.
|
||||||
|
* UI related actions should be called from here.
|
||||||
|
*/
|
||||||
|
@RequiresPresenter(CategoryPresenter::class)
|
||||||
|
class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener, OnStartDragListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object used to show actionMode toolbar.
|
||||||
|
*/
|
||||||
|
var actionMode: ActionMode? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter containing category items.
|
||||||
|
*/
|
||||||
|
private lateinit var adapter: CategoryAdapter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TouchHelper used for reorder animation and movement.
|
||||||
|
*/
|
||||||
|
private lateinit var touchHelper: ItemTouchHelper
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Create new CategoryActivity intent.
|
||||||
|
*
|
||||||
|
* @param context context information.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun newIntent(context: Context): Intent? {
|
||||||
|
return Intent(context, CategoryActivity::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
// Inflate activity_edit_categories.xml.
|
||||||
|
setContentView(R.layout.activity_edit_categories)
|
||||||
|
|
||||||
|
// Setup the toolbar.
|
||||||
|
setupToolbar(toolbar)
|
||||||
|
|
||||||
|
// Get new adapter.
|
||||||
|
adapter = CategoryAdapter(this)
|
||||||
|
|
||||||
|
// Create view and inject category items into view
|
||||||
|
recycler.layoutManager = LinearLayoutManager(this)
|
||||||
|
recycler.setHasFixedSize(true)
|
||||||
|
recycler.adapter = adapter
|
||||||
|
|
||||||
|
// Touch helper to drag and reorder categories
|
||||||
|
touchHelper = ItemTouchHelper(CategoryItemTouchHelper(adapter))
|
||||||
|
touchHelper.attachToRecyclerView(recycler)
|
||||||
|
|
||||||
|
// Create OnClickListener for creating new category
|
||||||
|
fab.setOnClickListener({ v ->
|
||||||
|
MaterialDialog.Builder(this)
|
||||||
|
.title(R.string.action_add_category)
|
||||||
|
.negativeText(R.string.button_cancel)
|
||||||
|
.input(R.string.name, 0, false)
|
||||||
|
{ dialog, input -> presenter.createCategory(input.toString()) }
|
||||||
|
.show()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finishes action mode.
|
||||||
|
* Call this when action mode action is finished.
|
||||||
|
*/
|
||||||
|
fun destroyActionModeIfNeeded() {
|
||||||
|
actionMode?.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill adapter with category items
|
||||||
|
*
|
||||||
|
* @param categories list containing categories
|
||||||
|
*/
|
||||||
|
fun setCategories(categories: List<Category>) {
|
||||||
|
destroyActionModeIfNeeded()
|
||||||
|
adapter.setItems(categories)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete selected categories
|
||||||
|
*
|
||||||
|
* @param categories list containing categories
|
||||||
|
*/
|
||||||
|
private fun deleteCategories(categories: List<Category?>?) {
|
||||||
|
presenter.deleteCategories(categories)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the selected categories
|
||||||
|
*
|
||||||
|
* @return list of selected categories
|
||||||
|
*/
|
||||||
|
private fun getSelectedCategories(): List<Category?>? {
|
||||||
|
// Create a list of the selected categories
|
||||||
|
return adapter.selectedItems.map { adapter.getItem(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show MaterialDialog which let user change category name.
|
||||||
|
*
|
||||||
|
* @param category category that will be edited.
|
||||||
|
*/
|
||||||
|
private fun editCategory(category: Category?) {
|
||||||
|
MaterialDialog.Builder(this)
|
||||||
|
.title(R.string.action_rename_category)
|
||||||
|
.negativeText(R.string.button_cancel)
|
||||||
|
.onNegative { materialDialog, dialogAction -> destroyActionModeIfNeeded() }
|
||||||
|
.input(getString(R.string.name), category?.name, false)
|
||||||
|
{ dialog, input -> presenter.renameCategory(category as Category, input.toString()) }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle actionMode selection
|
||||||
|
*
|
||||||
|
* @param position position of selected item
|
||||||
|
*/
|
||||||
|
private fun toggleSelection(position: Int) {
|
||||||
|
adapter.toggleSelection(position, false)
|
||||||
|
|
||||||
|
// Get selected item count
|
||||||
|
val count = adapter.selectedItemCount
|
||||||
|
|
||||||
|
// If no item is selected finish action mode
|
||||||
|
if (count == 0) {
|
||||||
|
actionMode?.finish()
|
||||||
|
} else {
|
||||||
|
// This block will only run if actionMode is not null
|
||||||
|
actionMode?.let {
|
||||||
|
|
||||||
|
// Set title equal to selected item
|
||||||
|
it.title = getString(R.string.label_selected, count)
|
||||||
|
it.invalidate()
|
||||||
|
|
||||||
|
// Show edit button only when one item is selected
|
||||||
|
val editItem = it.menu?.findItem(R.id.action_edit)
|
||||||
|
editItem?.isVisible = count == 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called each time the action mode is shown.
|
||||||
|
* Always called after onCreateActionMode
|
||||||
|
*
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
|
override fun onPrepareActionMode(p0: ActionMode?, p1: Menu?): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when action mode item clicked.
|
||||||
|
*
|
||||||
|
* @param actionMode action mode toolbar.
|
||||||
|
* @param menuItem selected menu item.
|
||||||
|
*
|
||||||
|
* @return action mode item clicked exist status
|
||||||
|
*/
|
||||||
|
override fun onActionItemClicked(actionMode: ActionMode, menuItem: MenuItem): Boolean {
|
||||||
|
when (menuItem.itemId) {
|
||||||
|
R.id.action_delete -> {
|
||||||
|
// Delete select categories.
|
||||||
|
deleteCategories(getSelectedCategories())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
R.id.action_edit -> {
|
||||||
|
// Edit selected category
|
||||||
|
editCategory(getSelectedCategories()?.get(0))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflate menu when action mode selected.
|
||||||
|
*
|
||||||
|
* @param mode ActionMode object
|
||||||
|
* @param menu Menu object
|
||||||
|
*
|
||||||
|
* @return true
|
||||||
|
*/
|
||||||
|
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||||
|
// Inflate menu.
|
||||||
|
mode.menuInflater.inflate(R.menu.category_selection, menu)
|
||||||
|
// Enable adapter multi selection.
|
||||||
|
adapter.mode = LibraryCategoryAdapter.MODE_MULTI
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when action mode destroyed.
|
||||||
|
*
|
||||||
|
* @param mode ActionMode object.
|
||||||
|
*/
|
||||||
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
|
// Reset adapter to single selection
|
||||||
|
adapter.mode = LibraryCategoryAdapter.MODE_SINGLE
|
||||||
|
// Clear selected items
|
||||||
|
adapter.clearSelection()
|
||||||
|
actionMode = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when item in list is clicked.
|
||||||
|
*
|
||||||
|
* @param position position of clicked item.
|
||||||
|
*/
|
||||||
|
override fun onListItemClick(position: Int): Boolean {
|
||||||
|
// Check if action mode is initialized and selected item exist.
|
||||||
|
if (actionMode != null && position != -1) {
|
||||||
|
// Toggle selection of clicked item.
|
||||||
|
toggleSelection(position)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when item long clicked
|
||||||
|
*
|
||||||
|
* @param position position of clicked item.
|
||||||
|
*/
|
||||||
|
override fun onListItemLongClick(position: Int) {
|
||||||
|
// Check if action mode is initialized.
|
||||||
|
if (actionMode == null)
|
||||||
|
// Initialize action mode
|
||||||
|
actionMode = startSupportActionMode(this)
|
||||||
|
|
||||||
|
// Set item as selected
|
||||||
|
toggleSelection(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when item is dragged
|
||||||
|
*
|
||||||
|
* @param viewHolder view that contains dragged item
|
||||||
|
*/
|
||||||
|
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder?) {
|
||||||
|
// Notify touchHelper
|
||||||
|
touchHelper.startDrag(viewHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.category
|
||||||
|
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
|
import eu.kanade.tachiyomi.ui.base.adapter.ItemTouchHelperAdapter
|
||||||
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter of CategoryHolder.
|
||||||
|
* Connection between Activity and Holder
|
||||||
|
* Holder updates should be called from here.
|
||||||
|
*
|
||||||
|
* @param activity activity that created adapter
|
||||||
|
* @constructor Creates a CategoryAdapter object
|
||||||
|
*/
|
||||||
|
class CategoryAdapter(private val activity: CategoryActivity) : FlexibleAdapter<CategoryHolder, Category>(), ItemTouchHelperAdapter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generator used to generate circle letter icons
|
||||||
|
*/
|
||||||
|
private val generator: ColorGenerator
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Let generator use Material Design colors.
|
||||||
|
// Material design is love, material design is live!
|
||||||
|
generator = ColorGenerator.MATERIAL
|
||||||
|
|
||||||
|
// Set unique id's
|
||||||
|
setHasStableIds(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when ViewHolder is created
|
||||||
|
*
|
||||||
|
* @param parent parent View
|
||||||
|
* @param viewType int containing viewType
|
||||||
|
*/
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryHolder {
|
||||||
|
// Inflate layout with item_edit_categories.xml
|
||||||
|
val view = parent.inflate(R.layout.item_edit_categories)
|
||||||
|
return CategoryHolder(view, this, activity, activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when ViewHolder is bind
|
||||||
|
*
|
||||||
|
* @param holder bind holder
|
||||||
|
* @param position position of holder
|
||||||
|
*/
|
||||||
|
override fun onBindViewHolder(holder: CategoryHolder, position: Int) {
|
||||||
|
// Update holder values.
|
||||||
|
val category = getItem(position)
|
||||||
|
holder.onSetValues(category, generator)
|
||||||
|
|
||||||
|
//When user scrolls this bind the correct selection status
|
||||||
|
holder.itemView.isActivated = isSelected(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update items with list of categories
|
||||||
|
*
|
||||||
|
* @param items list of categories
|
||||||
|
*/
|
||||||
|
fun setItems(items: List<Category>) {
|
||||||
|
mItems = ArrayList(items)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get category by position
|
||||||
|
*
|
||||||
|
* @param position position of item
|
||||||
|
*/
|
||||||
|
override fun getItemId(position: Int): Long {
|
||||||
|
return mItems[position].id!!.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when item is moved
|
||||||
|
*
|
||||||
|
* @param fromPosition previous position of item.
|
||||||
|
* @param toPosition new position of item.
|
||||||
|
*/
|
||||||
|
override fun onItemMove(fromPosition: Int, toPosition: Int) {
|
||||||
|
// Move items and notify touch helper
|
||||||
|
Collections.swap(mItems, fromPosition, toPosition)
|
||||||
|
notifyItemMoved(fromPosition, toPosition)
|
||||||
|
|
||||||
|
// Update database
|
||||||
|
activity.presenter.reorderCategories(mItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be implemented, not used
|
||||||
|
*/
|
||||||
|
override fun onItemDismiss(position: Int) {
|
||||||
|
// Empty method.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be implemented, not used
|
||||||
|
*/
|
||||||
|
override fun updateDataSet(p0: String?) {
|
||||||
|
// Empty method.
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.category
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.support.v4.view.MotionEventCompat
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
|
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener
|
||||||
|
import kotlinx.android.synthetic.main.item_edit_categories.view.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holder that contains category item.
|
||||||
|
* Uses R.layout.item_edit_categories.
|
||||||
|
* UI related actions should be called from here.
|
||||||
|
*
|
||||||
|
* @param view view of category item.
|
||||||
|
* @param adapter adapter belonging to holder.
|
||||||
|
* @param listener called when item clicked.
|
||||||
|
* @param dragListener called when item dragged.
|
||||||
|
*
|
||||||
|
* @constructor Create CategoryHolder object
|
||||||
|
*/
|
||||||
|
class CategoryHolder(view: View, adapter: CategoryAdapter, listener: FlexibleViewHolder.OnListItemClickListener, dragListener: OnStartDragListener) : FlexibleViewHolder(view, adapter, listener) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Create round letter image onclick to simulate long click
|
||||||
|
itemView.image.setOnClickListener({ v ->
|
||||||
|
// Simulate long click on this view to enter selection mode
|
||||||
|
onLongClick(view)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set on touch listener for reorder image
|
||||||
|
itemView.reorder.setOnTouchListener({ v, event ->
|
||||||
|
if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
|
||||||
|
dragListener.onStartDrag(this)
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update category item values.
|
||||||
|
*
|
||||||
|
* @param category category of item.
|
||||||
|
* @param generator generator used to generate circle letter icons.
|
||||||
|
*/
|
||||||
|
fun onSetValues(category: Category, generator: ColorGenerator) {
|
||||||
|
// Set capitalized title.
|
||||||
|
itemView.title.text = category.name.capitalize()
|
||||||
|
|
||||||
|
// Update circle letter image.
|
||||||
|
itemView.image.setImageDrawable(getRound(category.name.substring(0, 1).toUpperCase(), generator))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns circle letter image
|
||||||
|
*
|
||||||
|
* @param text first letter of string
|
||||||
|
* @param generator the generator used to generate circle letter image
|
||||||
|
*/
|
||||||
|
private fun getRound(text: String, generator: ColorGenerator): TextDrawable {
|
||||||
|
return TextDrawable.builder()
|
||||||
|
.beginConfig()
|
||||||
|
.textColor(Color.WHITE)
|
||||||
|
.useFont(Typeface.DEFAULT)
|
||||||
|
.toUpperCase()
|
||||||
|
.endConfig()
|
||||||
|
.buildRound(text, generator.getColor(text))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.category
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.ui.base.adapter.ItemTouchHelperAdapter
|
||||||
|
import eu.kanade.tachiyomi.ui.base.adapter.SimpleItemTouchHelperCallback
|
||||||
|
|
||||||
|
class CategoryItemTouchHelper(adapter: ItemTouchHelperAdapter) : SimpleItemTouchHelperCallback(adapter) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable items swipe remove
|
||||||
|
*
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
|
override fun isItemViewSwipeEnabled(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable long press item drag
|
||||||
|
*
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
|
override fun isLongPressDragEnabled(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.category
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter of CategoryActivity.
|
||||||
|
* Contains information and data for activity.
|
||||||
|
* Observable updates should be called from here.
|
||||||
|
*/
|
||||||
|
class CategoryPresenter : BasePresenter<CategoryActivity>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to connect to database
|
||||||
|
*/
|
||||||
|
@Inject lateinit var db: DatabaseHelper
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List containing categories
|
||||||
|
*/
|
||||||
|
private var categories: List<Category>? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* The id of the restartable.
|
||||||
|
*/
|
||||||
|
final private val GET_CATEGORIES = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedState: Bundle?) {
|
||||||
|
super.onCreate(savedState)
|
||||||
|
|
||||||
|
// Get categories as list
|
||||||
|
restartableLatestCache(GET_CATEGORIES,
|
||||||
|
{
|
||||||
|
db.categories.asRxObservable()
|
||||||
|
.doOnNext { categories -> this.categories = categories }
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
}, CategoryActivity::setCategories)
|
||||||
|
|
||||||
|
// Start get categories as list task
|
||||||
|
start(GET_CATEGORIES)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create category and add it to database
|
||||||
|
*
|
||||||
|
* @param name name of category
|
||||||
|
*/
|
||||||
|
fun createCategory(name: String) {
|
||||||
|
// Create category.
|
||||||
|
val cat = Category.create(name)
|
||||||
|
|
||||||
|
// Set the new item in the last position.
|
||||||
|
var max = 0
|
||||||
|
if (categories != null) {
|
||||||
|
for (cat2 in categories!!) {
|
||||||
|
if (cat2.order > max) {
|
||||||
|
max = cat2.order + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cat.order = max
|
||||||
|
|
||||||
|
// Insert into database.
|
||||||
|
db.insertCategory(cat).asRxObservable().subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete category from database
|
||||||
|
*
|
||||||
|
* @param categories list of categories
|
||||||
|
*/
|
||||||
|
fun deleteCategories(categories: List<Category?>?) {
|
||||||
|
db.deleteCategories(categories).asRxObservable().subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorder categories in database
|
||||||
|
*
|
||||||
|
* @param categories list of categories
|
||||||
|
*/
|
||||||
|
fun reorderCategories(categories: List<Category>) {
|
||||||
|
for (i in categories.indices) {
|
||||||
|
categories[i].order = i
|
||||||
|
}
|
||||||
|
|
||||||
|
db.insertCategories(categories).asRxObservable().subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename a category
|
||||||
|
*
|
||||||
|
* @param category category that gets renamed
|
||||||
|
* @param name new name of category
|
||||||
|
*/
|
||||||
|
fun renameCategory(category: Category, name: String) {
|
||||||
|
category.name = name
|
||||||
|
db.insertCategory(category).asRxObservable().subscribe()
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@ import eu.kanade.tachiyomi.data.io.IOHandler;
|
|||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService;
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService;
|
||||||
import eu.kanade.tachiyomi.event.LibraryMangasEvent;
|
import eu.kanade.tachiyomi.event.LibraryMangasEvent;
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
|
||||||
import eu.kanade.tachiyomi.ui.library.category.CategoryActivity;
|
import eu.kanade.tachiyomi.ui.category.CategoryActivity;
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity;
|
import eu.kanade.tachiyomi.ui.main.MainActivity;
|
||||||
import eu.kanade.tachiyomi.util.ToastUtil;
|
import eu.kanade.tachiyomi.util.ToastUtil;
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
|
@ -1,180 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library.category;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
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.support.v7.widget.Toolbar;
|
|
||||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import com.afollestad.materialdialogs.MaterialDialog;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener;
|
|
||||||
import eu.kanade.tachiyomi.ui.decoration.DividerItemDecoration;
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryCategoryAdapter;
|
|
||||||
import nucleus.factory.RequiresPresenter;
|
|
||||||
import rx.Observable;
|
|
||||||
|
|
||||||
@RequiresPresenter(CategoryPresenter.class)
|
|
||||||
public class CategoryActivity extends BaseRxActivity<CategoryPresenter> implements
|
|
||||||
ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener, OnStartDragListener {
|
|
||||||
|
|
||||||
@Bind(R.id.toolbar) Toolbar toolbar;
|
|
||||||
@Bind(R.id.categories_list) RecyclerView recycler;
|
|
||||||
@Bind(R.id.fab) FloatingActionButton fab;
|
|
||||||
|
|
||||||
private CategoryAdapter adapter;
|
|
||||||
private ActionMode actionMode;
|
|
||||||
private ItemTouchHelper touchHelper;
|
|
||||||
|
|
||||||
public static Intent newIntent(Context context) {
|
|
||||||
return new Intent(context, CategoryActivity.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedState) {
|
|
||||||
super.onCreate(savedState);
|
|
||||||
setContentView(R.layout.activity_edit_categories);
|
|
||||||
ButterKnife.bind(this);
|
|
||||||
|
|
||||||
setupToolbar(toolbar);
|
|
||||||
|
|
||||||
adapter = new CategoryAdapter(this);
|
|
||||||
recycler.setLayoutManager(new LinearLayoutManager(this));
|
|
||||||
recycler.setHasFixedSize(true);
|
|
||||||
recycler.setAdapter(adapter);
|
|
||||||
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(this)
|
|
||||||
.title(R.string.action_add_category)
|
|
||||||
.input(R.string.name, 0, false, (dialog, input) -> {
|
|
||||||
getPresenter().createCategory(input.toString());
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = 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();
|
|
||||||
MenuItem editItem = actionMode.getMenu().findItem(R.id.action_edit);
|
|
||||||
editItem.setVisible(count == 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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:
|
|
||||||
deleteCategories(getSelectedCategories());
|
|
||||||
return true;
|
|
||||||
case R.id.action_edit:
|
|
||||||
editCategory(getSelectedCategories().get(0));
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteCategories(List<Category> categories) {
|
|
||||||
getPresenter().deleteCategories(categories);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void editCategory(Category category) {
|
|
||||||
new MaterialDialog.Builder(this)
|
|
||||||
.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,80 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library.category;
|
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
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.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.ItemTouchHelperAdapter;
|
|
||||||
|
|
||||||
public class CategoryAdapter extends FlexibleAdapter<CategoryHolder, Category> implements
|
|
||||||
ItemTouchHelperAdapter {
|
|
||||||
|
|
||||||
private final CategoryActivity activity;
|
|
||||||
private final ColorGenerator generator;
|
|
||||||
|
|
||||||
public CategoryAdapter(CategoryActivity activity) {
|
|
||||||
this.activity = activity;
|
|
||||||
generator = ColorGenerator.DEFAULT;
|
|
||||||
setHasStableIds(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItems(List<Category> items) {
|
|
||||||
mItems = new ArrayList<>(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 = activity.getLayoutInflater();
|
|
||||||
View v = inflater.inflate(R.layout.item_edit_categories, parent, false);
|
|
||||||
return new CategoryHolder(v, this, activity, activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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));
|
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activity.getPresenter().reorderCategories(mItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemDismiss(int position) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.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;
|
|
||||||
|
|
||||||
import com.amulyakhare.textdrawable.TextDrawable;
|
|
||||||
import com.amulyakhare.textdrawable.util.ColorGenerator;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import eu.kanade.tachiyomi.R;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener;
|
|
||||||
|
|
||||||
public class CategoryHolder extends FlexibleViewHolder {
|
|
||||||
|
|
||||||
private View view;
|
|
||||||
|
|
||||||
@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, 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) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library.category;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.ItemTouchHelperAdapter;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.SimpleItemTouchHelperCallback;
|
|
||||||
|
|
||||||
public class CategoryItemTouchHelper extends SimpleItemTouchHelperCallback {
|
|
||||||
|
|
||||||
public CategoryItemTouchHelper(ItemTouchHelperAdapter adapter) {
|
|
||||||
super(adapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isItemViewSwipeEnabled() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library.category;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category;
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
|
||||||
|
|
||||||
public class CategoryPresenter extends BasePresenter<CategoryActivity> {
|
|
||||||
|
|
||||||
@Inject DatabaseHelper db;
|
|
||||||
|
|
||||||
private List<Category> categories;
|
|
||||||
|
|
||||||
private static final int GET_CATEGORIES = 1;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedState) {
|
|
||||||
super.onCreate(savedState);
|
|
||||||
|
|
||||||
restartableLatestCache(GET_CATEGORIES,
|
|
||||||
() -> db.getCategories().asRxObservable()
|
|
||||||
.doOnNext(categories -> this.categories = categories)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread()),
|
|
||||||
CategoryActivity::setCategories);
|
|
||||||
|
|
||||||
start(GET_CATEGORIES);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createCategory(String name) {
|
|
||||||
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).asRxObservable().subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteCategories(List<Category> categories) {
|
|
||||||
db.deleteCategories(categories).asRxObservable().subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reorderCategories(List<Category> categories) {
|
|
||||||
for (int i = 0; i < categories.size(); i++) {
|
|
||||||
categories.get(i).order = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
db.insertCategories(categories).asRxObservable().subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renameCategory(Category category, String name) {
|
|
||||||
category.name = name;
|
|
||||||
db.insertCategory(category).asRxObservable().subscribe();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,15 @@
|
|||||||
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
|
import android.support.annotation.LayoutRes
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension method to inflate a view directly from its parent.
|
||||||
|
* @param layout the layout to inflate.
|
||||||
|
* @param attachToRoot whether to attach the view to the root or not. Defaults to false.
|
||||||
|
*/
|
||||||
|
fun ViewGroup.inflate(@LayoutRes layout: Int, attachToRoot: Boolean = false): View {
|
||||||
|
return LayoutInflater.from(context).inflate(layout, this, attachToRoot)
|
||||||
|
}
|
BIN
app/src/main/res/drawable-hdpi/ic_action_reorder.png
Normal file
After Width: | Height: | Size: 327 B |
Before Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 148 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_reorder.png
Normal file
After Width: | Height: | Size: 203 B |
Before Width: | Height: | Size: 89 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_reorder.png
Normal file
After Width: | Height: | Size: 368 B |
Before Width: | Height: | Size: 114 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_reorder.png
Normal file
After Width: | Height: | Size: 671 B |
Before Width: | Height: | Size: 137 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_reorder.png
Normal file
After Width: | Height: | Size: 836 B |
Before Width: | Height: | Size: 174 B |
@ -4,6 +4,7 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:gravity="center">
|
android:gravity="center">
|
||||||
|
|
||||||
<include layout="@layout/toolbar"/>
|
<include layout="@layout/toolbar"/>
|
||||||
@ -12,9 +13,10 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="?attr/actionBarSize"
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
android:id="@+id/categories_list"
|
android:id="@+id/recycler"
|
||||||
android:choiceMode="multipleChoice"
|
android:choiceMode="multipleChoice"
|
||||||
android:listSelector="@color/list_choice_pressed_bg_light" />
|
android:listSelector="@color/list_choice_pressed_bg_light"
|
||||||
|
tools:listitem="@layout/item_edit_categories"/>
|
||||||
|
|
||||||
<android.support.design.widget.FloatingActionButton
|
<android.support.design.widget.FloatingActionButton
|
||||||
android:id="@+id/fab"
|
android:id="@+id/fab"
|
||||||
@ -25,8 +27,8 @@
|
|||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:src="@drawable/ic_add_white_24dp"
|
android:src="@drawable/ic_add_white_24dp"
|
||||||
app:backgroundTint="@color/colorPrimary"
|
app:backgroundTint="@color/colorPrimary"
|
||||||
app:layout_anchor="@id/categories_list"
|
app:layout_anchor="@id/recycler"
|
||||||
app:layout_anchorGravity="bottom|right|end"
|
app:layout_anchorGravity="bottom|right|end"
|
||||||
app:layout_behavior="eu.kanade.tachiyomi.ui.base.fab.ScrollAwareFABBehavior"/>
|
app:layout_behavior="eu.kanade.tachiyomi.ui.base.fab.FABAnimationUpDown"/>
|
||||||
|
|
||||||
</android.support.design.widget.CoordinatorLayout>
|
</android.support.design.widget.CoordinatorLayout>
|
@ -3,8 +3,8 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?android:attr/listPreferredItemHeightLarge"
|
android:layout_height="?android:attr/listPreferredItemHeightLarge"
|
||||||
android:paddingTop="@dimen/margin_top"
|
android:paddingTop="8dp"
|
||||||
android:paddingBottom="@dimen/margin_bottom"
|
android:paddingBottom="8dp"
|
||||||
android:background="@drawable/selector_chapter_light">
|
android:background="@drawable/selector_chapter_light">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
@ -14,7 +14,6 @@
|
|||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:elevation="4dp"
|
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:layout_marginLeft="@dimen/margin_left"
|
android:layout_marginLeft="@dimen/margin_left"
|
||||||
android:layout_marginStart="@dimen/margin_left"
|
android:layout_marginStart="@dimen/margin_left"
|
||||||
@ -30,8 +29,10 @@
|
|||||||
android:layout_marginRight="@dimen/margin_right"
|
android:layout_marginRight="@dimen/margin_right"
|
||||||
android:layout_marginEnd="@dimen/margin_right"
|
android:layout_marginEnd="@dimen/margin_right"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:src="@drawable/ic_reorder_grey_600_24dp"/>
|
android:layout_alignParentEnd="true"
|
||||||
|
android:src="@drawable/ic_action_reorder"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
|