diff --git a/app/build.gradle b/app/build.gradle index 3ad59e14e..fe215f29f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ import java.text.SimpleDateFormat apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'me.tatarka.retrolambda' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ed8a82fa7..2c5fc5bfc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -40,11 +40,10 @@ android:parentActivityName=".ui.main.MainActivity" > - 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) {} +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fab/FABAnimationUpDown.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fab/FABAnimationUpDown.kt new file mode 100644 index 000000000..347f9792c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fab/FABAnimationUpDown.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fab/ScrollAwareFABBehavior.java b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fab/ScrollAwareFABBehavior.java deleted file mode 100644 index cbcd0100c..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fab/ScrollAwareFABBehavior.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt new file mode 100644 index 000000000..346873539 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt @@ -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(), 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) { + destroyActionModeIfNeeded() + adapter.setItems(categories) + } + + /** + * Delete selected categories + * + * @param categories list containing categories + */ + private fun deleteCategories(categories: List?) { + presenter.deleteCategories(categories) + } + + /** + * Returns the selected categories + * + * @return list of selected categories + */ + private fun getSelectedCategories(): List? { + // 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) + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt new file mode 100644 index 000000000..a377276a1 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt @@ -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(), 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) { + 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. + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt new file mode 100644 index 000000000..f4cce6540 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt @@ -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)) + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItemTouchHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItemTouchHelper.kt new file mode 100644 index 000000000..1e1ad8df3 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItemTouchHelper.kt @@ -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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt new file mode 100644 index 000000000..9a67159eb --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt @@ -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() { + + /** + * Used to connect to database + */ + @Inject lateinit var db: DatabaseHelper + + /** + * List containing categories + */ + private var categories: List? = 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?) { + db.deleteCategories(categories).asRxObservable().subscribe() + } + + /** + * Reorder categories in database + * + * @param categories list of categories + */ + fun reorderCategories(categories: List) { + 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() + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.java index 217600448..e397f2045 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.java @@ -38,7 +38,7 @@ import eu.kanade.tachiyomi.data.io.IOHandler; import eu.kanade.tachiyomi.data.library.LibraryUpdateService; import eu.kanade.tachiyomi.event.LibraryMangasEvent; 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.util.ToastUtil; import icepick.State; diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryActivity.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryActivity.java deleted file mode 100644 index b5409cde5..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryActivity.java +++ /dev/null @@ -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 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 categories) { - destroyActionModeIfNeeded(); - adapter.setItems(categories); - } - - private List 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 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); - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryAdapter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryAdapter.java deleted file mode 100644 index e1a845385..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryAdapter.java +++ /dev/null @@ -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 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 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) { - - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryHolder.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryHolder.java deleted file mode 100644 index feec63a71..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryHolder.java +++ /dev/null @@ -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); - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryItemTouchHelper.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryItemTouchHelper.java deleted file mode 100644 index 7e38a25de..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryItemTouchHelper.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryPresenter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryPresenter.java deleted file mode 100644 index edb305c9a..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/category/CategoryPresenter.java +++ /dev/null @@ -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 { - - @Inject DatabaseHelper db; - - private List 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 categories) { - db.deleteCategories(categories).asRxObservable().subscribe(); - } - - public void reorderCategories(List 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(); - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt new file mode 100644 index 000000000..377fd1bf5 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_action_reorder.png b/app/src/main/res/drawable-hdpi/ic_action_reorder.png new file mode 100644 index 000000000..9aa1825a0 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_reorder.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_reorder_grey_600_24dp.png b/app/src/main/res/drawable-hdpi/ic_reorder_grey_600_24dp.png deleted file mode 100644 index 29a4975ff..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_reorder_grey_600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldpi/ic_reorder_grey_600_24dp.png b/app/src/main/res/drawable-ldpi/ic_reorder_grey_600_24dp.png deleted file mode 100644 index b5d38304b..000000000 Binary files a/app/src/main/res/drawable-ldpi/ic_reorder_grey_600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_reorder.png b/app/src/main/res/drawable-mdpi/ic_action_reorder.png new file mode 100644 index 000000000..9a7eab26f Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_reorder.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_reorder_grey_600_24dp.png b/app/src/main/res/drawable-mdpi/ic_reorder_grey_600_24dp.png deleted file mode 100644 index 3dbdf4e6c..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_reorder_grey_600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_reorder.png b/app/src/main/res/drawable-xhdpi/ic_action_reorder.png new file mode 100644 index 000000000..7e448783e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_reorder.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_reorder_grey_600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_reorder_grey_600_24dp.png deleted file mode 100644 index f36c16996..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_reorder_grey_600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_reorder.png b/app/src/main/res/drawable-xxhdpi/ic_action_reorder.png new file mode 100644 index 000000000..45c2557b5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_reorder.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_reorder_grey_600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_reorder_grey_600_24dp.png deleted file mode 100644 index 4c72a1429..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_reorder_grey_600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_reorder.png b/app/src/main/res/drawable-xxxhdpi/ic_action_reorder.png new file mode 100644 index 000000000..a6a017b43 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_reorder.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_reorder_grey_600_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_reorder_grey_600_24dp.png deleted file mode 100644 index 525ebee16..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_reorder_grey_600_24dp.png and /dev/null differ diff --git a/app/src/main/res/layout/activity_edit_categories.xml b/app/src/main/res/layout/activity_edit_categories.xml index 4e8b4af20..5f044468f 100644 --- a/app/src/main/res/layout/activity_edit_categories.xml +++ b/app/src/main/res/layout/activity_edit_categories.xml @@ -4,6 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" + xmlns:tools="http://schemas.android.com/tools" android:gravity="center"> @@ -12,9 +13,10 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="?attr/actionBarSize" - android:id="@+id/categories_list" + android:id="@+id/recycler" 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"/> + app:layout_behavior="eu.kanade.tachiyomi.ui.base.fab.FABAnimationUpDown"/> \ No newline at end of file diff --git a/app/src/main/res/layout/item_edit_categories.xml b/app/src/main/res/layout/item_edit_categories.xml index 97457cf7a..a4b1a6c3d 100644 --- a/app/src/main/res/layout/item_edit_categories.xml +++ b/app/src/main/res/layout/item_edit_categories.xml @@ -3,8 +3,8 @@ 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:paddingTop="8dp" + android:paddingBottom="8dp" android:background="@drawable/selector_chapter_light"> + android:layout_alignParentEnd="true" + android:src="@drawable/ic_action_reorder"/>