Category controller is no longer rx, md2 design (really just google keep)

pull/3117/head
Jay 5 years ago
parent 0bc81a8237
commit fc0ab3e878

@ -41,7 +41,7 @@ class SourceHolder(view: View, override val adapter: CatalogueAdapter) :
// Set circle letter image. // Set circle letter image.
itemView.post { itemView.post {
image.setImageDrawable(image.getRound(source.name.take(1).toUpperCase(), false)) edit_button.setImageDrawable(edit_button.getRound(source.name.take(1).toUpperCase(), false))
} }
// If source is login, show only login option // If source is login, show only login option

@ -13,38 +13,26 @@ class CategoryAdapter(controller: CategoryController) :
/** /**
* Listener called when an item of the list is released. * Listener called when an item of the list is released.
*/ */
val onItemReleaseListener: OnItemReleaseListener = controller val categoryItemListener: CategoryItemListener = controller
/**
* Clears the active selections from the list and the model.
*/
override fun clearSelection() {
super.clearSelection()
(0 until itemCount).forEach { getItem(it)?.isSelected = false }
}
/** /**
* Clears the active selections from the model. * Clears the active selections from the model.
*/ */
fun clearModelSelection() { fun resetEditing(position: Int) {
selectedPositions.forEach { getItem(it)?.isSelected = false } for (i in 0..itemCount) {
getItem(i)?.isEditing = false
} }
getItem(position)?.isEditing = true
/** notifyDataSetChanged()
* Toggles the selection of the given position.
*
* @param position The position to toggle.
*/
override fun toggleSelection(position: Int) {
super.toggleSelection(position)
getItem(position)?.isSelected = isSelected(position)
} }
interface OnItemReleaseListener { interface CategoryItemListener {
/** /**
* Called when an item of the list is released. * Called when an item of the list is released.
*/ */
fun onItemReleased(position: Int) fun onItemReleased(position: Int)
fun onCategoryRename(position: Int, newName: String): Boolean
fun onItemDelete(position: Int)
} }
} }

@ -1,46 +1,33 @@
package eu.kanade.tachiyomi.ui.category package eu.kanade.tachiyomi.ui.category
import com.google.android.material.snackbar.Snackbar import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import android.view.LayoutInflater
import androidx.appcompat.view.ActionMode import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import com.afollestad.materialdialogs.MaterialDialog
import android.view.*
import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.BaseTransientBottomBar
import com.jakewharton.rxbinding.view.clicks import com.google.android.material.snackbar.Snackbar
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter
import eu.davidea.flexibleadapter.helpers.UndoHelper import eu.davidea.flexibleadapter.helpers.UndoHelper
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.category.CategoryPresenter.Companion.CREATE_CATEGORY_ORDER
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.view.marginBottom
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.android.synthetic.main.categories_controller.empty_view import eu.kanade.tachiyomi.util.view.snack
import kotlinx.android.synthetic.main.categories_controller.fab import kotlinx.android.synthetic.main.categories_controller.*
import kotlinx.android.synthetic.main.categories_controller.recycler
/** /**
* Controller to manage the categories for the users' library. * Controller to manage the categories for the users' library.
*/ */
class CategoryController : NucleusController<CategoryPresenter>(), class CategoryController(bundle: Bundle? = null) : BaseController(bundle),
ActionMode.Callback,
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener, CategoryAdapter.CategoryItemListener,
CategoryAdapter.OnItemReleaseListener,
CategoryCreateDialog.Listener, CategoryCreateDialog.Listener,
CategoryRenameDialog.Listener, CategoryRenameDialog.Listener {
UndoHelper.OnActionListener {
/**
* Object used to show ActionMode toolbar.
*/
private var actionMode: ActionMode? = null
/** /**
* Adapter containing category items. * Adapter containing category items.
@ -55,7 +42,7 @@ class CategoryController : NucleusController<CategoryPresenter>(),
/** /**
* Creates the presenter for this controller. Not to be manually called. * Creates the presenter for this controller. Not to be manually called.
*/ */
override fun createPresenter() = CategoryPresenter() private val presenter = CategoryPresenter(this)
/** /**
* Returns the toolbar title to show when this controller is attached. * Returns the toolbar title to show when this controller is attached.
@ -89,20 +76,8 @@ class CategoryController : NucleusController<CategoryPresenter>(),
adapter?.isHandleDragEnabled = true adapter?.isHandleDragEnabled = true
adapter?.isPermanentDelete = false adapter?.isPermanentDelete = false
fab.clicks().subscribeUntilDestroy { activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
CategoryCreateDialog(this@CategoryController).showDialog(router, null) presenter.getCategories()
}
val fabBaseMarginBottom = fab?.marginBottom ?: 0
recycler.doOnApplyWindowInsets { v, insets, padding ->
fab?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = fabBaseMarginBottom + insets.systemWindowInsetBottom
}
// offset the recycler by the fab's inset + some inset on top
v.updatePaddingRelative(bottom = padding.bottom + (fab?.marginBottom ?: 0) +
fabBaseMarginBottom + (fab?.height ?: 0))
}
} }
/** /**
@ -113,86 +88,71 @@ class CategoryController : NucleusController<CategoryPresenter>(),
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
// Manually call callback to delete categories if required // Manually call callback to delete categories if required
snack?.dismiss() snack?.dismiss()
view.clearFocus()
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
confirmDelete() confirmDelete()
snack = null snack = null
actionMode = null
adapter = null adapter = null
super.onDestroyView(view) super.onDestroyView(view)
} }
override fun handleBack(): Boolean {
view?.clearFocus()
confirmDelete()
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
return super.handleBack()
}
/** /**
* Called from the presenter when the categories are updated. * Called from the presenter when the categories are updated.
* *
* @param categories The new list of categories to display. * @param categories The new list of categories to display.
*/ */
fun setCategories(categories: List<CategoryItem>) { fun setCategories(categories: List<CategoryItem>) {
actionMode?.finish()
adapter?.updateDataSet(categories) adapter?.updateDataSet(categories)
if (categories.isNotEmpty()) { if (categories.isNotEmpty()) {
empty_view.hide() empty_view.hide()
val selected = categories.filter { it.isSelected }
if (selected.isNotEmpty()) {
selected.forEach { onItemLongClick(categories.indexOf(it)) }
}
} else { } else {
empty_view.show(R.drawable.ic_shape_black_128dp, R.string.information_empty_category) empty_view.show(R.drawable.ic_shape_black_128dp, R.string.information_empty_category)
} }
} }
/** /**
* Called when action mode is first created. The menu supplied will be used to generate action * Called when an item in the list is clicked.
* buttons for the action mode.
* *
* @param mode ActionMode being created. * @param position The position of the clicked item.
* @param menu Menu used to populate action buttons. * @return true if this click should enable selection mode.
* @return true if the action mode should be created, false if entering this mode should be
* aborted.
*/ */
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onItemClick(view: View?, position: Int): Boolean {
// Inflate menu. adapter?.resetEditing(position)
mode.menuInflater.inflate(R.menu.category_selection, menu)
// Enable adapter multi selection.
adapter?.mode = SelectableAdapter.Mode.MULTI
return true return true
} }
/** override fun onCategoryRename(position: Int, newName: String): Boolean {
* Called to refresh an action mode's action menu whenever it is invalidated. val category = adapter?.getItem(position)?.category ?: return false
* if (category.order == CREATE_CATEGORY_ORDER)
* @param mode ActionMode being prepared. return (presenter.createCategory(newName))
* @param menu Menu used to populate action buttons. return (presenter.renameCategory(category, newName))
* @return true if the menu or action mode was updated, false otherwise.
*/
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
val adapter = adapter ?: return false
val count = adapter.selectedItemCount
mode.title = resources?.getString(R.string.label_selected, count)
// Show edit button only when one item is selected
val editItem = mode.menu.findItem(R.id.action_edit)
editItem.isVisible = count == 1
return true
} }
/** override fun onItemDelete(position: Int) {
* Called to report a user click on an action button. MaterialDialog(activity!!)
* .title(R.string.confirm_category_deletion)
* @param mode The current ActionMode. .message(R.string.confirm_category_deletion_message)
* @param item The item that was clicked. .positiveButton(R.string.action_delete) {
* @return true if this callback handled the event, false if the standard MenuItem invocation deleteCategory(position)
* should continue. }
*/ .negativeButton(android.R.string.no)
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { .show()
val adapter = adapter ?: return false }
when (item.itemId) { private fun deleteCategory(position: Int) {
R.id.action_delete -> { adapter?.removeItem(position)
adapter.removeItems(adapter.selectedPositions)
snack = snack =
view?.snack(R.string.snack_categories_deleted, Snackbar.LENGTH_INDEFINITE) { view?.snack(R.string.snack_category_deleted, Snackbar.LENGTH_INDEFINITE) {
var undoing = false var undoing = false
setAction(R.string.action_undo) { setAction(R.string.action_undo) {
adapter.restoreDeletedItems() adapter?.restoreDeletedItems()
undoing = true undoing = true
} }
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() { addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
@ -203,86 +163,6 @@ class CategoryController : NucleusController<CategoryPresenter>(),
}) })
} }
(activity as? MainActivity)?.setUndoSnackBar(snack) (activity as? MainActivity)?.setUndoSnackBar(snack)
mode.finish()
}
R.id.action_edit -> {
// Edit selected category
if (adapter.selectedItemCount == 1) {
val position = adapter.selectedPositions.first()
val category = adapter.getItem(position)?.category
if (category != null) {
editCategory(category)
}
}
}
else -> return false
}
return true
}
/**
* Called when an action mode is about to be exited and destroyed.
*
* @param mode The current ActionMode being destroyed.
*/
override fun onDestroyActionMode(mode: ActionMode) {
// Reset adapter to single selection
adapter?.mode = SelectableAdapter.Mode.IDLE
adapter?.clearSelection()
actionMode = null
}
/**
* Called when an item in the list is clicked.
*
* @param position The position of the clicked item.
* @return true if this click should enable selection mode.
*/
override fun onItemClick(view: View?, position: Int): Boolean {
// Check if action mode is initialized and selected item exist.
return if (actionMode != null && position != RecyclerView.NO_POSITION) {
toggleSelection(position)
true
} else {
false
}
}
/**
* Called when an item in the list is long clicked.
*
* @param position The position of the clicked item.
*/
override fun onItemLongClick(position: Int) {
val activity = activity as? AppCompatActivity ?: return
// Check if action mode is initialized.
if (actionMode == null) {
// Initialize action mode
actionMode = activity.startSupportActionMode(this)
}
// Set item as selected
toggleSelection(position)
}
/**
* Toggle the selection state of an item.
* If the item was the last one in the selection and is unselected, the ActionMode is finished.
*
* @param position The position of the item to toggle.
*/
private fun toggleSelection(position: Int) {
val adapter = adapter ?: return
//Mark the position selected
adapter.toggleSelection(position)
if (adapter.selectedItemCount == 0) {
actionMode?.finish()
} else {
actionMode?.invalidate()
}
} }
/** /**
@ -296,31 +176,10 @@ class CategoryController : NucleusController<CategoryPresenter>(),
presenter.reorderCategories(categories) presenter.reorderCategories(categories)
} }
/**
* Called when the undo action is clicked in the snackbar.
*
* @param action The action performed.
*/
override fun onActionCanceled(action: Int, positions: MutableList<Int>?) {
adapter?.restoreDeletedItems()
snack = null
}
/**
* Called when the time to restore the items expires.
*
* @param action The action performed.
* @param event The event that triggered the action
*/
override fun onActionConfirmed(action: Int, event: Int) {
val adapter = adapter ?: return
presenter.deleteCategories(adapter.deletedItems.map { it.category })
snack = null
}
fun confirmDelete() { fun confirmDelete() {
val adapter = adapter ?: return val adapter = adapter ?: return
presenter.deleteCategories(adapter.deletedItems.map { it.category }) presenter.deleteCategory(adapter.deletedItems.map { it.category }.firstOrNull())
adapter.confirmDeletion()
snack = null snack = null
} }

@ -1,12 +1,21 @@
package eu.kanade.tachiyomi.ui.category package eu.kanade.tachiyomi.ui.category
import android.content.Context
import android.graphics.drawable.Drawable
import android.text.InputType
import android.view.View import android.view.View
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.core.content.ContextCompat
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.view.getRound import eu.kanade.tachiyomi.ui.category.CategoryPresenter.Companion.CREATE_CATEGORY_ORDER
import kotlinx.android.synthetic.main.categories_item.image import eu.kanade.tachiyomi.util.system.getResourceColor
import kotlinx.android.synthetic.main.categories_item.reorder import eu.kanade.tachiyomi.util.view.gone
import kotlinx.android.synthetic.main.categories_item.title import eu.kanade.tachiyomi.util.view.visible
import kotlinx.android.synthetic.main.categories_item.*
/** /**
* Holder used to display category items. * Holder used to display category items.
@ -17,15 +26,14 @@ import kotlinx.android.synthetic.main.categories_item.title
class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleViewHolder(view, adapter) { class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleViewHolder(view, adapter) {
init { init {
// Create round letter image onclick to simulate long click edit_button.setOnClickListener {
image.setOnClickListener { submitChanges()
// Simulate long click on this view to enter selection mode
onLongClick(view)
} }
setDragHandleView(reorder)
} }
var createCategory = false
private var regularDrawable: Drawable? = null
/** /**
* Binds this holder with the given category. * Binds this holder with the given category.
* *
@ -34,13 +42,92 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
fun bind(category: Category) { fun bind(category: Category) {
// Set capitalized title. // Set capitalized title.
title.text = category.name.capitalize() title.text = category.name.capitalize()
edit_text.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
submitChanges()
}
true
}
createCategory = category.order == CREATE_CATEGORY_ORDER
if (createCategory) {
title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorHint))
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable
.ic_add_white_24dp)
edit_button.gone()
image.gone()
edit_text.setText("")
edit_text.hint = title.text
}
else {
title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorPrimary))
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable
.ic_reorder_grey_24dp)
edit_button.visible()
image.visible()
edit_text.setText(title.text)
}
}
fun isEditing(editing: Boolean) {
itemView.isActivated = editing
title.visibility = if (editing) View.INVISIBLE else View.VISIBLE
edit_text.visibility = if (!editing) View.INVISIBLE else View.VISIBLE
if (editing) {
edit_text.inputType = InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
edit_text.requestFocus()
edit_text.selectAll()
edit_button.setImageDrawable(ContextCompat.getDrawable(itemView.context, R.drawable.ic_check_white_24dp))
edit_button.drawable.mutate().setTint(itemView.context.getResourceColor(R.attr.colorAccent))
showKeyboard()
if (!createCategory) {
reorder.setImageDrawable(
ContextCompat.getDrawable(
itemView.context, R.drawable.ic_delete_white_24dp
)
)
reorder.setOnClickListener {
adapter.categoryItemListener.onItemDelete(adapterPosition)
}
}
}
else {
if (!createCategory) {
setDragHandleView(reorder)
}
else {
reorder.setOnTouchListener { _, _ -> true}
}
edit_text.clearFocus()
edit_button.setImageDrawable(ContextCompat.getDrawable(itemView.context, R.drawable.ic_edit_white_24dp))
edit_button.drawable.mutate().setTint(ContextCompat.getColor(itemView.context, R
.color.gray_button))
reorder.setImageDrawable(regularDrawable)
}
}
// Update circle letter image. private fun submitChanges() {
itemView.post { if (edit_text.visibility == View.VISIBLE ) {
image.setImageDrawable(image.getRound(category.name.take(1).toUpperCase(),false)) if (adapter.categoryItemListener
.onCategoryRename(adapterPosition, edit_text.text.toString())) {
isEditing(false)
edit_text.inputType = InputType.TYPE_NULL
if (!createCategory)
title.text = edit_text.text.toString()
}
}
else {
itemView.performClick()
} }
} }
private fun showKeyboard() {
val inputMethodManager: InputMethodManager =
itemView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.showSoftInput(edit_text, WindowManager.LayoutParams
.SOFT_INPUT_ADJUST_PAN)
}
/** /**
* Called when an item is released. * Called when an item is released.
* *
@ -48,7 +135,7 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
*/ */
override fun onItemReleased(position: Int) { override fun onItemReleased(position: Int) {
super.onItemReleased(position) super.onItemReleased(position)
adapter.onItemReleaseListener.onItemReleased(position) adapter.categoryItemListener.onItemReleased(position)
} }
} }

@ -7,6 +7,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.category.CategoryPresenter.Companion.CREATE_CATEGORY_ORDER
/** /**
* Category item for a recycler view. * Category item for a recycler view.
@ -16,7 +17,7 @@ class CategoryItem(val category: Category) : AbstractFlexibleItem<CategoryHolder
/** /**
* Whether this item is currently selected. * Whether this item is currently selected.
*/ */
var isSelected = false var isEditing = false
/** /**
* Returns the layout resource for this item. * Returns the layout resource for this item.
@ -45,13 +46,14 @@ class CategoryItem(val category: Category) : AbstractFlexibleItem<CategoryHolder
*/ */
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: CategoryHolder, position: Int, payloads: MutableList<Any>) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: CategoryHolder, position: Int, payloads: MutableList<Any>) {
holder.bind(category) holder.bind(category)
holder.isEditing(isEditing)
} }
/** /**
* Returns true if this item is draggable. * Returns true if this item is draggable.
*/ */
override fun isDraggable(): Boolean { override fun isDraggable(): Boolean {
return true return category.order != CREATE_CATEGORY_ORDER && !isEditing
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

@ -1,11 +1,13 @@
package eu.kanade.tachiyomi.ui.category package eu.kanade.tachiyomi.ui.category
import android.os.Bundle import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import rx.Observable import kotlinx.coroutines.Dispatchers
import rx.android.schedulers.AndroidSchedulers import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -13,27 +15,41 @@ import uy.kohesive.injekt.api.get
* Presenter of [CategoryController]. Used to manage the categories of the library. * Presenter of [CategoryController]. Used to manage the categories of the library.
*/ */
class CategoryPresenter( class CategoryPresenter(
private val db: DatabaseHelper = Injekt.get() private val controller: CategoryController,
) : BasePresenter<CategoryController>() { private val db: DatabaseHelper = Injekt.get(),
preferences: PreferencesHelper = Injekt.get()
) {
private val context = preferences.context
/** /**
* List containing categories. * List containing categories.
*/ */
private var categories: List<Category> = emptyList() private var categories: MutableList<Category> = mutableListOf()
/** /**
* Called when the presenter is created. * Called when the presenter is created.
*
* @param savedState The saved state of this presenter.
*/ */
override fun onCreate(savedState: Bundle?) { fun getCategories() {
super.onCreate(savedState) if (categories.isNotEmpty()) {
controller.setCategories(categories.map(::CategoryItem))
db.getCategories().asRxObservable() }
.doOnNext { categories = it } GlobalScope.launch(Dispatchers.IO) {
.map { it.map(::CategoryItem) } categories.clear()
.observeOn(AndroidSchedulers.mainThread()) categories.add(newCategory())
.subscribeLatestCache(CategoryController::setCategories) categories.addAll(db.getCategories().executeAsBlocking())
val catItems = categories.map(::CategoryItem)
withContext(Dispatchers.Main) {
controller.setCategories(catItems)
}
}
}
private fun newCategory(): Category {
val default = Category.create(context.getString(R.string.create_new_category))
default.order = CREATE_CATEGORY_ORDER
default.id = Int.MIN_VALUE
return default
} }
/** /**
@ -41,11 +57,11 @@ class CategoryPresenter(
* *
* @param name The name of the category to create. * @param name The name of the category to create.
*/ */
fun createCategory(name: String) { fun createCategory(name: String): Boolean {
// Do not allow duplicate categories. // Do not allow duplicate categories.
if (categoryExists(name)) { if (categoryExists(name)) {
Observable.just(Unit).subscribeFirst({ view, _ -> view.onCategoryExistsError() }) controller.onCategoryExistsError()
return return false
} }
// Create category. // Create category.
@ -55,16 +71,25 @@ class CategoryPresenter(
cat.order = categories.map { it.order + 1 }.max() ?: 0 cat.order = categories.map { it.order + 1 }.max() ?: 0
// Insert into database. // Insert into database.
db.insertCategory(cat).asRxObservable().subscribe()
db.insertCategory(cat).executeAsBlocking()
val cats = db.getCategories().executeAsBlocking()
val newCat = cats.find { it.name == name } ?: return false
categories.add(1, newCat)
reorderCategories(categories)
return true
} }
/** /**
* Deletes the given categories from the database. * Deletes the given categories from the database.
* *
* @param categories The list of categories to delete. * @param category The category to delete.
*/ */
fun deleteCategories(categories: List<Category>) { fun deleteCategory(category: Category?) {
db.deleteCategories(categories).asRxObservable().subscribe() val safeCategory = category ?: return
db.deleteCategory(safeCategory).executeAsBlocking()
categories.remove(safeCategory)
controller.setCategories(categories.map(::CategoryItem))
} }
/** /**
@ -74,10 +99,12 @@ class CategoryPresenter(
*/ */
fun reorderCategories(categories: List<Category>) { fun reorderCategories(categories: List<Category>) {
categories.forEachIndexed { i, category -> categories.forEachIndexed { i, category ->
category.order = i if (category.order != CREATE_CATEGORY_ORDER)
category.order = i - 1
} }
db.insertCategories(categories.filter { it.order != CREATE_CATEGORY_ORDER }).executeAsBlocking()
db.insertCategories(categories).asRxObservable().subscribe() this.categories = categories.sortedBy { it.order }.toMutableList()
controller.setCategories(categories.map(::CategoryItem))
} }
/** /**
@ -86,22 +113,29 @@ class CategoryPresenter(
* @param category The category to rename. * @param category The category to rename.
* @param name The new name of the category. * @param name The new name of the category.
*/ */
fun renameCategory(category: Category, name: String) { fun renameCategory(category: Category, name: String): Boolean {
// Do not allow duplicate categories. // Do not allow duplicate categories.
if (categoryExists(name)) { if (categoryExists(name)) {
Observable.just(Unit).subscribeFirst({ view, _ -> view.onCategoryExistsError() }) controller.onCategoryExistsError()
return return false
} }
category.name = name category.name = name
db.insertCategory(category).asRxObservable().subscribe() db.insertCategory(category).executeAsBlocking()
categories.find { it.id == category.id }?.name = name
controller.setCategories(categories.map(::CategoryItem))
return true
} }
/** /**
* Returns true if a category with the given name already exists. * Returns true if a category with the given name already exists.
*/ */
fun categoryExists(name: String): Boolean { private fun categoryExists(name: String): Boolean {
return categories.any { it.name.equals(name, true) } return categories.any { it.name.equals(name, true) }
} }
companion object {
const val CREATE_CATEGORY_ORDER = -2
}
} }

@ -44,13 +44,13 @@ class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) :
itemView.context.getString(R.string.ext_untrusted).toUpperCase() itemView.context.getString(R.string.ext_untrusted).toUpperCase()
} }
GlideApp.with(itemView.context).clear(image) GlideApp.with(itemView.context).clear(edit_button)
if (extension is Extension.Available) { if (extension is Extension.Available) {
GlideApp.with(itemView.context) GlideApp.with(itemView.context)
.load(extension.iconUrl) .load(extension.iconUrl)
.into(image) .into(edit_button)
} else { } else {
extension.getApplicationIcon(itemView.context)?.let { image.setImageDrawable(it) } extension.getApplicationIcon(itemView.context)?.let { edit_button.setImageDrawable(it) }
} }
bindButton(item) bindButton(item)
} }

@ -9,6 +9,8 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.category.CategoryAdapter import eu.kanade.tachiyomi.ui.category.CategoryAdapter
import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.lang.removeArticles import eu.kanade.tachiyomi.util.lang.removeArticles
import eu.kanade.tachiyomi.util.system.launchUI
import kotlinx.coroutines.delay
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
@ -28,9 +30,6 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
*/ */
private var mangas: List<LibraryItem> = emptyList() private var mangas: List<LibraryItem> = emptyList()
val onItemReleaseListener: CategoryAdapter.OnItemReleaseListener = view
/** /**
* Listener called when an item of the list press start reading. * Listener called when an item of the list press start reading.
*/ */
@ -145,5 +144,6 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
* Called when an item of the list is released. * Called when an item of the list is released.
*/ */
fun startReading(position: Int) fun startReading(position: Int)
fun onItemReleased(position: Int)
} }
} }

@ -8,7 +8,6 @@ import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.SelectableAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -18,7 +17,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.category.CategoryAdapter
import eu.kanade.tachiyomi.util.lang.plusAssign import eu.kanade.tachiyomi.util.lang.plusAssign
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
@ -40,8 +38,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemLongClickListener,
FlexibleAdapter.OnItemMoveListener, FlexibleAdapter.OnItemMoveListener,
LibraryCategoryAdapter.LibraryListener, LibraryCategoryAdapter.LibraryListener {
CategoryAdapter.OnItemReleaseListener {
/** /**
* Preferences. * Preferences.
@ -209,16 +206,12 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
* *
* @param event the event received. * @param event the event received.
*/ */
fun onNextLibraryManga(event: LibraryMangaEvent) { private fun onNextLibraryManga(event: LibraryMangaEvent) {
// Get the manga list for this category. // Get the manga list for this category.
adapter.isLongPressDragEnabled = canDrag() adapter.isLongPressDragEnabled = canDrag()
val mangaForCategory = event.getMangaForCategory(category).orEmpty() val mangaForCategory = event.getMangaForCategory(category).orEmpty()
// Update the category with its manga.
// if (!justDraggedAndDropped)
adapter.setItems(mangaForCategory) adapter.setItems(mangaForCategory)
// else
// justDraggedAndDropped = false
swipe_refresh.isEnabled = !preferences.hideCategories().getOrDefault() swipe_refresh.isEnabled = !preferences.hideCategories().getOrDefault()

@ -31,9 +31,27 @@ class LibraryGridHolder(
private val view: View, private val view: View,
adapter: LibraryCategoryAdapter, adapter: LibraryCategoryAdapter,
var width:Int, var width:Int,
var fixedSize: Boolean private var fixedSize: Boolean
) : LibraryHolder(view, adapter) { ) : LibraryHolder(view, adapter) {
init {
play_layout.setOnClickListener { playButtonClicked() }
if (fixedSize) {
title.gone()
subtitle.gone()
}
else {
compact_title.gone()
gradient.gone()
val playLayout = play_layout.layoutParams as FrameLayout.LayoutParams
val buttonLayout = play_button.layoutParams as FrameLayout.LayoutParams
playLayout.gravity = Gravity.BOTTOM or Gravity.END
buttonLayout.gravity = Gravity.BOTTOM or Gravity.END
play_layout.layoutParams = playLayout
play_button.layoutParams = buttonLayout
}
}
/** /**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* holder with the given manga. * holder with the given manga.
@ -44,6 +62,7 @@ class LibraryGridHolder(
// Update the title and subtitle of the manga. // Update the title and subtitle of the manga.
title.text = item.manga.currentTitle() title.text = item.manga.currentTitle()
subtitle.text = item.manga.originalAuthor()?.trim() subtitle.text = item.manga.originalAuthor()?.trim()
if (!fixedSize) { if (!fixedSize) {
title.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { title.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean { override fun onPreDraw(): Boolean {
@ -74,25 +93,13 @@ class LibraryGridHolder(
0 -> if (item.manga.unread > 0) -1 else -2 0 -> if (item.manga.unread > 0) -1 else -2
else -> -2 else -> -2
}, },
if (item.manga.source == LocalSource.ID) -2 else item.downloadCount) when {
item.downloadCount == -1 -> -1
item.manga.source == LocalSource.ID -> -2
else -> item.downloadCount
})
play_layout.visibility = if (item.manga.unread > 0 && item.unreadType > -1) play_layout.visibility = if (item.manga.unread > 0 && item.unreadType > -1)
View.VISIBLE else View.GONE View.VISIBLE else View.GONE
play_layout.setOnClickListener { playButtonClicked() }
if (fixedSize) {
title.gone()
subtitle.gone()
}
else {
compact_title.gone()
gradient.gone()
val playLayout = play_layout.layoutParams as FrameLayout.LayoutParams
val buttonLayout = play_button.layoutParams as FrameLayout.LayoutParams
playLayout.gravity = Gravity.BOTTOM or Gravity.END
buttonLayout.gravity = Gravity.BOTTOM or Gravity.END
play_layout.layoutParams = playLayout
play_button.layoutParams = buttonLayout
}
// Update the cover. // Update the cover.
if (item.manga.thumbnail_url == null) GlideApp.with(view.context).clear(cover_thumbnail) if (item.manga.thumbnail_url == null) GlideApp.with(view.context).clear(cover_thumbnail)

@ -35,7 +35,7 @@ abstract class LibraryHolder(
*/ */
override fun onItemReleased(position: Int) { override fun onItemReleased(position: Int) {
super.onItemReleased(position) super.onItemReleased(position)
(adapter as? LibraryCategoryAdapter)?.onItemReleaseListener?.onItemReleased(position) (adapter as? LibraryCategoryAdapter)?.libraryListener?.onItemReleased(position)
} }
protected fun convertColor(color: Int):String { protected fun convertColor(color: Int):String {

@ -48,6 +48,7 @@ class LibraryItem(val manga: LibraryManga, private val libraryLayout: Preference
val marginParams = card.layoutParams as ConstraintLayout.LayoutParams val marginParams = card.layoutParams as ConstraintLayout.LayoutParams
marginParams.bottomMargin = 6.dpToPx marginParams.bottomMargin = 6.dpToPx
card.layoutParams = marginParams card.layoutParams = marginParams
cover_thumbnail.maxHeight = Integer.MAX_VALUE
constraint_layout.minHeight = 0 constraint_layout.minHeight = 0
cover_thumbnail.adjustViewBounds = false cover_thumbnail.adjustViewBounds = false
cover_thumbnail.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, coverHeight) cover_thumbnail.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, coverHeight)

@ -38,8 +38,16 @@ class LibraryListHolder(
title.text = item.manga.currentTitle() title.text = item.manga.currentTitle()
badge_view.setUnreadDownload( badge_view.setUnreadDownload(
if (item.unreadType == 1) item.manga.unread else (item.unreadType - 1), when (item.unreadType) {
if (item.manga.source == LocalSource.ID) -2 else item.downloadCount) 1 -> item.manga.unread
0 -> if (item.manga.unread > 0) -1 else -2
else -> -2
},
when {
item.downloadCount == -1 -> -1
item.manga.source == LocalSource.ID -> -2
else -> item.downloadCount
})
subtitle.text = item.manga.originalAuthor()?.trim() subtitle.text = item.manga.originalAuthor()?.trim()
subtitle.visibility = if (!item.manga.originalAuthor().isNullOrBlank()) View.VISIBLE subtitle.visibility = if (!item.manga.originalAuthor().isNullOrBlank()) View.VISIBLE

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.ui.main package eu.kanade.tachiyomi.ui.main
import android.app.SearchManager import android.app.SearchManager
import android.content.ComponentCallbacks2
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
@ -11,6 +12,7 @@ import android.provider.Settings
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager
import android.webkit.WebView import android.webkit.WebView
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
@ -53,8 +55,11 @@ import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import eu.kanade.tachiyomi.ui.setting.SettingsMainController import eu.kanade.tachiyomi.ui.setting.SettingsMainController
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePadding import eu.kanade.tachiyomi.util.view.updatePadding
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.visible
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -166,6 +171,21 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
updateRecentsIcon() updateRecentsIcon()
content.viewTreeObserver.addOnGlobalLayoutListener {
val heightDiff: Int = content.rootView.height - content.height
if (heightDiff > 200 &&
window.attributes.softInputMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
//keyboard is open, hide layout
navigationView.gone()
} else if (navigationView.visibility == View.GONE) {
//keyboard is hidden, show layout
// use coroutine to delay so the bottom bar doesn't flash on top of the keyboard
launchUI {
navigationView.visible()
}
}
}
content.setOnApplyWindowInsetsListener { v, insets -> content.setOnApplyWindowInsetsListener { v, insets ->
// if device doesn't support light nav bar // if device doesn't support light nav bar
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {

@ -17,20 +17,13 @@ class MangaHolder(
fun bind(item: MangaItem) { fun bind(item: MangaItem) {
// Update the title of the manga. // Update the title of the manga.
title.text = item.manga.currentTitle() title.text = item.manga.currentTitle()
subtitle.text = item.manga.currentAuthor()?.trim()
// Create thumbnail onclick to simulate long click
cover_thumbnail.setOnClickListener {
// Simulate long click on this view to enter selection mode
onLongClick(itemView)
}
// Update the cover. // Update the cover.
GlideApp.with(itemView.context).clear(cover_thumbnail) GlideApp.with(itemView.context).clear(cover_thumbnail)
GlideApp.with(itemView.context) GlideApp.with(itemView.context)
.load(item.manga) .load(item.manga)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
// .centerCrop()
// .circleCrop()
.dontAnimate() .dontAnimate()
.into(cover_thumbnail) .into(cover_thumbnail)
} }

@ -14,10 +14,10 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig
import eu.kanade.tachiyomi.util.system.await import eu.kanade.tachiyomi.util.system.await
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
import kotlinx.android.synthetic.main.migration_controller.* import kotlinx.android.synthetic.main.migration_controller.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -91,12 +91,17 @@ class MigrationController : NucleusController<MigrationPresenter>(),
} }
adapter?.updateDataSet(state.sourcesWithManga) adapter?.updateDataSet(state.sourcesWithManga)
} else { } else {
val switching = title == resources?.getString(R.string.label_migration)
title = state.selectedSource.toString() title = state.selectedSource.toString()
if (adapter !is MangaAdapter) { if (adapter !is MangaAdapter) {
adapter = MangaAdapter(this) adapter = MangaAdapter(this)
migration_recycler.adapter = adapter migration_recycler.adapter = adapter
} }
adapter?.updateDataSet(state.mangaForSource) adapter?.updateDataSet(state.mangaForSource, true)
/*if (switching) launchUI {
migration_recycler.alpha = 0f
migration_recycler.animate().alpha(1f).setStartDelay(100).setDuration(200).start()
}*/
} }
} }
@ -104,7 +109,7 @@ class MigrationController : NucleusController<MigrationPresenter>(),
val item = adapter?.getItem(position) ?: return false val item = adapter?.getItem(position) ?: return false
if (item is MangaItem) { if (item is MangaItem) {
val controller = SearchController(item.manga) val controller = PreMigrationController.create(listOf(item.manga.id!!))
controller.targetController = this controller.targetController = this
router.pushController(controller.withFadeTransaction()) router.pushController(controller.withFadeTransaction())

@ -20,7 +20,7 @@ class SourceHolder(view: View, override val adapter: SourceAdapter) :
get() = card get() = card
init { init {
source_latest.text = "Auto" source_latest.text = view.context.getString(R.string.action_auto)
source_browse.setText(R.string.select) source_browse.setText(R.string.select)
source_browse.setOnClickListener { source_browse.setOnClickListener {
adapter.selectClickListener?.onSelectClick(adapterPosition) adapter.selectClickListener?.onSelectClick(adapterPosition)
@ -39,7 +39,7 @@ class SourceHolder(view: View, override val adapter: SourceAdapter) :
// Set circle letter image. // Set circle letter image.
itemView.post { itemView.post {
image.setImageDrawable(image.getRound(source.name.take(1).toUpperCase(),false)) edit_button.setImageDrawable(edit_button.getRound(source.name.take(1).toUpperCase(),false))
} }
} }
} }

@ -24,16 +24,16 @@ class MigrationSourceHolder(view: View, val adapter: MigrationSourceAdapter):
title.text = sourceName title.text = sourceName
// Update circle letter image. // Update circle letter image.
itemView.post { itemView.post {
image.setImageDrawable(image.getRound(source.name.take(1).toUpperCase(),false)) edit_button.setImageDrawable(edit_button.getRound(source.name.take(1).toUpperCase(),false))
} }
if(sourceEnabled) { if(sourceEnabled) {
title.alpha = 1.0f title.alpha = 1.0f
image.alpha = 1.0f edit_button.alpha = 1.0f
title.paintFlags = title.paintFlags and STRIKE_THRU_TEXT_FLAG.inv() title.paintFlags = title.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
} else { } else {
title.alpha = DISABLED_ALPHA title.alpha = DISABLED_ALPHA
image.alpha = DISABLED_ALPHA edit_button.alpha = DISABLED_ALPHA
title.paintFlags = title.paintFlags or STRIKE_THRU_TEXT_FLAG title.paintFlags = title.paintFlags or STRIKE_THRU_TEXT_FLAG
} }
} }

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:background="@color/red_error">
<item
android:bottom="1dp"
android:left="-2dp"
android:right="-2dp"
android:top="1dp">
<selector>
<item android:state_activated="true">
<shape
android:right="8dp"
android:shape="rectangle">
<stroke
android:width="1dp"
android:color="@color/fullRippleColor" />
<solid android:color="#00FFFFFF" />
</shape>
</item>
</selector>
</item>
</layer-list>

@ -10,22 +10,20 @@
<item> <item>
<selector> <selector>
<item android:state_activated="false"> <item android:state_activated="false">
<shape <shape android:shape="rectangle">
android:shape="rectangle">
<corners android:radius="16dp" /> <corners android:radius="16dp" />
<size <size
android:height="32dp" android:width="32dp"
android:width="32dp" /> android:height="32dp" />
<solid android:color="@android:color/transparent" /> <solid android:color="@android:color/transparent" />
</shape> </shape>
</item> </item>
<item android:state_activated="true"> <item android:state_activated="true">
<shape <shape android:shape="rectangle">
android:shape="rectangle">
<corners android:radius="16dp" /> <corners android:radius="16dp" />
<size <size
android:height="32dp" android:width="32dp"
android:width="32dp" /> android:height="32dp" />
<solid android:color="?attr/colorAccent" /> <solid android:color="?attr/colorAccent" />
</shape> </shape>
</item> </item>

@ -1,13 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/manga_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_layout" android:id="@+id/constraint_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -157,4 +151,3 @@
tools:text="Sample artist" /> tools:text="Sample artist" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

@ -13,7 +13,7 @@
android:background="?attr/selectable_list_drawable"> android:background="?attr/selectable_list_drawable">
<ImageView <ImageView
android:id="@+id/image" android:id="@+id/edit_button"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:padding="8dp" android:padding="8dp"
@ -33,7 +33,7 @@
android:ellipsize="end" android:ellipsize="end"
android:textAppearance="@style/TextAppearance.Regular.SubHeading" android:textAppearance="@style/TextAppearance.Regular.SubHeading"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/image" app:layout_constraintStart_toEndOf="@+id/edit_button"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@+id/source_latest" app:layout_constraintEnd_toStartOf="@+id/source_latest"
tools:text="Source title"/> tools:text="Source title"/>

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/cat_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
@ -13,20 +13,13 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:choiceMode="multipleChoice" android:choiceMode="multipleChoice"
android:clipToPadding="false" android:clipToPadding="false"
tools:listitem="@layout/categories_item" tools:listitem="@layout/categories_item" />
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
app:layout_anchor="@id/recycler"
app:srcCompat="@drawable/ic_add_white_24dp"
style="@style/Theme.Widget.FABFixed"/>
<eu.kanade.tachiyomi.widget.EmptyView <eu.kanade.tachiyomi.widget.EmptyView
android:id="@+id/empty_view" android:id="@+id/empty_view"
android:visibility="gone"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_height="wrap_content" /> android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -1,40 +1,78 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
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="@dimen/material_component_lists_single_line_with_avatar_height" android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
android:background="?attr/selectable_list_drawable"> android:background="@drawable/bordered_list_selector">
<ImageView <ImageView
android:id="@+id/image" android:id="@+id/reorder"
android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height" android:layout_width="54dp"
android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height" android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
android:clickable="true" android:scaleType="center"
android:paddingStart="@dimen/material_component_lists_icon_left_padding" android:tint="?android:attr/textColorPrimary"
android:paddingEnd="0dp" app:layout_constraintBottom_toBottomOf="parent"
tools:src="@mipmap/ic_launcher_round"/> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_reorder_grey_24dp" />
<ImageView
android:id="@+id/image"
android:layout_width="24dp"
android:layout_height="match_parent"
android:tint="@color/gray_button"
android:src="@drawable/ic_label_outline_white_24dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="6dp"
app:layout_constraintStart_toEndOf="@+id/reorder"
app:layout_constraintEnd_toStartOf="@+id/title"
app:layout_constraintTop_toTopOf="parent"
/>
<ImageView
android:id="@+id/edit_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_edit_white_24dp"
android:tint="@color/gray_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/edit_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@null"
android:imeOptions="actionDone"
android:inputType="none"
android:maxLines="1"
android:textColor="@color/textColorPrimary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="@id/title"
app:layout_constraintTop_toTopOf="@id/title"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="@id/title"
tools:text="Title" />
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginStart="@dimen/material_component_lists_text_left_padding" android:gravity="center|start"
android:layout_marginEnd="@dimen/material_component_lists_single_line_with_avatar_height" android:layout_marginStart="3dp"
android:ellipsize="end" android:inputType="none"
android:maxLines="1" android:maxLines="1"
android:layout_gravity="center_vertical" android:textColor="@color/textColorPrimary"
android:textAppearance="@style/TextAppearance.Regular.SubHeading" android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/edit_button"
app:layout_constraintStart_toEndOf="@+id/image"
tools:text="Title" /> tools:text="Title" />
<ImageView
android:id="@+id/reorder"
android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height"
android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
android:scaleType="center"
android:layout_gravity="end"
app:srcCompat="@drawable/ic_reorder_grey_24dp"
android:tint="?android:attr/textColorPrimary"/>
</FrameLayout> </androidx.constraintlayout.widget.ConstraintLayout>

@ -13,7 +13,7 @@
android:background="?attr/selectable_list_drawable"> android:background="?attr/selectable_list_drawable">
<ImageView <ImageView
android:id="@+id/image" android:id="@+id/edit_button"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:padding="12dp" android:padding="12dp"
@ -33,7 +33,7 @@
android:maxLines="1" android:maxLines="1"
android:textAppearance="@style/TextAppearance.Regular.SubHeading" android:textAppearance="@style/TextAppearance.Regular.SubHeading"
android:textSize="14sp" android:textSize="14sp"
app:layout_constraintStart_toEndOf="@id/image" app:layout_constraintStart_toEndOf="@id/edit_button"
app:layout_constraintEnd_toStartOf="@id/ext_button" app:layout_constraintEnd_toStartOf="@id/ext_button"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/lang" app:layout_constraintBottom_toTopOf="@id/lang"
@ -47,7 +47,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:maxLines="1" android:maxLines="1"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintStart_toEndOf="@id/image" app:layout_constraintStart_toEndOf="@id/edit_button"
app:layout_constraintTop_toBottomOf="@+id/ext_title" app:layout_constraintTop_toBottomOf="@+id/ext_title"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
tools:text="English" tools:text="English"

@ -8,7 +8,7 @@
android:background="?attr/selectable_list_drawable"> android:background="?attr/selectable_list_drawable">
<ImageView <ImageView
android:id="@+id/image" android:id="@+id/edit_button"
android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height" android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height"
android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height" android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
android:paddingLeft="@dimen/material_component_lists_icon_left_padding" android:paddingLeft="@dimen/material_component_lists_icon_left_padding"

@ -175,6 +175,7 @@
<string name="portrait">Portrait</string> <string name="portrait">Portrait</string>
<string name="landscape">Landscape</string> <string name="landscape">Landscape</string>
<string name="default_columns">Default</string> <string name="default_columns">Default</string>
<string name="create_new_category">Create new category</string>
<string name="pref_category_library_update">Updates</string> <string name="pref_category_library_update">Updates</string>
<string name="pref_library_update_interval">Library update frequency</string> <string name="pref_library_update_interval">Library update frequency</string>
@ -412,6 +413,9 @@
<string name="category_already_in_queue">%1$s is already in queue</string> <string name="category_already_in_queue">%1$s is already in queue</string>
<string name="local_source_badge">Local</string> <string name="local_source_badge">Local</string>
<string name="confirm_manga_deletion">Remove from library?</string> <string name="confirm_manga_deletion">Remove from library?</string>
<string name="confirm_category_deletion">Delete category?</string>
<string name="confirm_category_deletion_message">Manga in this category will moved into the
default category.</string>
<plurals name="unread_count"> <plurals name="unread_count">
<item quantity="one">New chapter</item> <item quantity="one">New chapter</item>
<item quantity="other">%d unread</item> <item quantity="other">%d unread</item>
@ -526,7 +530,7 @@
<!-- Category activity --> <!-- Category activity -->
<string name="error_category_exists">A category with this name already exists!</string> <string name="error_category_exists">A category with this name already exists!</string>
<string name="snack_categories_deleted">Categories deleted</string> <string name="snack_category_deleted">Category deleted</string>
<!-- Dialog option with checkbox view --> <!-- Dialog option with checkbox view -->
<string name="dialog_with_checkbox_remove_description">This will remove the read date of this chapter. Are you sure?</string> <string name="dialog_with_checkbox_remove_description">This will remove the read date of this chapter. Are you sure?</string>
@ -649,5 +653,6 @@
<string name="pre_migration_skip_toast">To show this screen again, go to Settings -> Library.</string> <string name="pre_migration_skip_toast">To show this screen again, go to Settings -> Library.</string>
<string name="reset_tags">Reset Tags</string> <string name="reset_tags">Reset Tags</string>
<string name="display_as">Display as</string> <string name="display_as">Display as</string>
<string name="action_auto">Auto</string>
</resources> </resources>

Loading…
Cancel
Save