Merge pull request #178 from NoodleMage/kotlin

Category rewrite + FAB rewrite to Kotlin
This commit is contained in:
inorichi 2016-02-25 13:27:21 +01:00
commit 9062e40ec5
32 changed files with 710 additions and 507 deletions

View file

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

View file

@ -40,11 +40,10 @@
android:parentActivityName=".ui.main.MainActivity" >
</activity>
<activity
android:name=".ui.library.category.CategoryActivity"
android:name=".ui.category.CategoryActivity"
android:label="@string/label_categories"
android:parentActivityName=".ui.main.MainActivity">
</activity>
<activity
android:name=".ui.setting.SettingsDownloadsFragment$CustomLayoutPickerActivity"
android:label="@string/app_name"

View file

@ -6,17 +6,17 @@ import javax.inject.Singleton;
import dagger.Component;
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.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.injection.module.AppModule;
import eu.kanade.tachiyomi.injection.module.DataModule;
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.library.LibraryPresenter;
import eu.kanade.tachiyomi.ui.library.category.CategoryPresenter;
import eu.kanade.tachiyomi.ui.manga.MangaActivity;
import eu.kanade.tachiyomi.ui.manga.MangaPresenter;
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter;

View file

@ -28,6 +28,7 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
return makeMovementFlags(dragFlags, swipeFlags);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {

View file

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

View file

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

View file

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

View file

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

View file

@ -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.
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 B

View file

@ -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">
<include layout="@layout/toolbar"/>
@ -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"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
@ -25,8 +27,8 @@
android:scaleType="fitCenter"
android:src="@drawable/ic_add_white_24dp"
app:backgroundTint="@color/colorPrimary"
app:layout_anchor="@id/categories_list"
app:layout_anchor="@id/recycler"
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>

View file

@ -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">
<ImageView
@ -14,7 +14,6 @@
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerInParent="true"
android:elevation="4dp"
android:clickable="true"
android:layout_marginLeft="@dimen/margin_left"
android:layout_marginStart="@dimen/margin_left"
@ -30,8 +29,10 @@
android:layout_marginRight="@dimen/margin_right"
android:layout_marginEnd="@dimen/margin_right"
android:scaleType="center"
android:layout_centerInParent="true"
android:layout_alignParentRight="true"
android:src="@drawable/ic_reorder_grey_600_24dp"/>
android:layout_alignParentEnd="true"
android:src="@drawable/ic_action_reorder"/>
<TextView
android:id="@+id/title"