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

@ -13,38 +13,26 @@ class CategoryAdapter(controller: CategoryController) :
/**
* Listener called when an item of the list is released.
*/
val onItemReleaseListener: OnItemReleaseListener = controller
/**
* Clears the active selections from the list and the model.
*/
override fun clearSelection() {
super.clearSelection()
(0 until itemCount).forEach { getItem(it)?.isSelected = false }
}
val categoryItemListener: CategoryItemListener = controller
/**
* Clears the active selections from the model.
*/
fun clearModelSelection() {
selectedPositions.forEach { getItem(it)?.isSelected = false }
fun resetEditing(position: Int) {
for (i in 0..itemCount) {
getItem(i)?.isEditing = false
}
/**
* 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)
getItem(position)?.isEditing = true
notifyDataSetChanged()
}
interface OnItemReleaseListener {
interface CategoryItemListener {
/**
* Called when an item of the list is released.
*/
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
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.*
import com.afollestad.materialdialogs.MaterialDialog
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.SelectableAdapter
import eu.davidea.flexibleadapter.helpers.UndoHelper
import eu.kanade.tachiyomi.R
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.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 kotlinx.android.synthetic.main.categories_controller.empty_view
import kotlinx.android.synthetic.main.categories_controller.fab
import kotlinx.android.synthetic.main.categories_controller.recycler
import eu.kanade.tachiyomi.util.view.snack
import kotlinx.android.synthetic.main.categories_controller.*
/**
* Controller to manage the categories for the users' library.
*/
class CategoryController : NucleusController<CategoryPresenter>(),
ActionMode.Callback,
class CategoryController(bundle: Bundle? = null) : BaseController(bundle),
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
CategoryAdapter.OnItemReleaseListener,
CategoryAdapter.CategoryItemListener,
CategoryCreateDialog.Listener,
CategoryRenameDialog.Listener,
UndoHelper.OnActionListener {
/**
* Object used to show ActionMode toolbar.
*/
private var actionMode: ActionMode? = null
CategoryRenameDialog.Listener {
/**
* Adapter containing category items.
@ -55,7 +42,7 @@ class CategoryController : NucleusController<CategoryPresenter>(),
/**
* 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.
@ -89,20 +76,8 @@ class CategoryController : NucleusController<CategoryPresenter>(),
adapter?.isHandleDragEnabled = true
adapter?.isPermanentDelete = false
fab.clicks().subscribeUntilDestroy {
CategoryCreateDialog(this@CategoryController).showDialog(router, null)
}
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))
}
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
presenter.getCategories()
}
/**
@ -113,86 +88,71 @@ class CategoryController : NucleusController<CategoryPresenter>(),
override fun onDestroyView(view: View) {
// Manually call callback to delete categories if required
snack?.dismiss()
view.clearFocus()
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
confirmDelete()
snack = null
actionMode = null
adapter = null
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.
*
* @param categories The new list of categories to display.
*/
fun setCategories(categories: List<CategoryItem>) {
actionMode?.finish()
adapter?.updateDataSet(categories)
if (categories.isNotEmpty()) {
empty_view.hide()
val selected = categories.filter { it.isSelected }
if (selected.isNotEmpty()) {
selected.forEach { onItemLongClick(categories.indexOf(it)) }
}
} else {
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
* buttons for the action mode.
* Called when an item in the list is clicked.
*
* @param mode ActionMode being created.
* @param menu Menu used to populate action buttons.
* @return true if the action mode should be created, false if entering this mode should be
* aborted.
* @param position The position of the clicked item.
* @return true if this click should enable selection mode.
*/
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
// Inflate menu.
mode.menuInflater.inflate(R.menu.category_selection, menu)
// Enable adapter multi selection.
adapter?.mode = SelectableAdapter.Mode.MULTI
override fun onItemClick(view: View?, position: Int): Boolean {
adapter?.resetEditing(position)
return true
}
/**
* Called to refresh an action mode's action menu whenever it is invalidated.
*
* @param mode ActionMode being prepared.
* @param menu Menu used to populate action buttons.
* @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 onCategoryRename(position: Int, newName: String): Boolean {
val category = adapter?.getItem(position)?.category ?: return false
if (category.order == CREATE_CATEGORY_ORDER)
return (presenter.createCategory(newName))
return (presenter.renameCategory(category, newName))
}
/**
* Called to report a user click on an action button.
*
* @param mode The current ActionMode.
* @param item The item that was clicked.
* @return true if this callback handled the event, false if the standard MenuItem invocation
* should continue.
*/
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
val adapter = adapter ?: return false
override fun onItemDelete(position: Int) {
MaterialDialog(activity!!)
.title(R.string.confirm_category_deletion)
.message(R.string.confirm_category_deletion_message)
.positiveButton(R.string.action_delete) {
deleteCategory(position)
}
.negativeButton(android.R.string.no)
.show()
}
when (item.itemId) {
R.id.action_delete -> {
adapter.removeItems(adapter.selectedPositions)
private fun deleteCategory(position: Int) {
adapter?.removeItem(position)
snack =
view?.snack(R.string.snack_categories_deleted, Snackbar.LENGTH_INDEFINITE) {
view?.snack(R.string.snack_category_deleted, Snackbar.LENGTH_INDEFINITE) {
var undoing = false
setAction(R.string.action_undo) {
adapter.restoreDeletedItems()
adapter?.restoreDeletedItems()
undoing = true
}
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
@ -203,86 +163,6 @@ class CategoryController : NucleusController<CategoryPresenter>(),
})
}
(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)
}
/**
* 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() {
val adapter = adapter ?: return
presenter.deleteCategories(adapter.deletedItems.map { it.category })
presenter.deleteCategory(adapter.deletedItems.map { it.category }.firstOrNull())
adapter.confirmDeletion()
snack = null
}

@ -1,12 +1,21 @@
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.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.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.view.getRound
import kotlinx.android.synthetic.main.categories_item.image
import kotlinx.android.synthetic.main.categories_item.reorder
import kotlinx.android.synthetic.main.categories_item.title
import eu.kanade.tachiyomi.ui.category.CategoryPresenter.Companion.CREATE_CATEGORY_ORDER
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.visible
import kotlinx.android.synthetic.main.categories_item.*
/**
* 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) {
init {
// Create round letter image onclick to simulate long click
image.setOnClickListener {
// Simulate long click on this view to enter selection mode
onLongClick(view)
edit_button.setOnClickListener {
submitChanges()
}
setDragHandleView(reorder)
}
var createCategory = false
private var regularDrawable: Drawable? = null
/**
* Binds this holder with the given category.
*
@ -34,13 +42,92 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
fun bind(category: Category) {
// Set capitalized title.
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.
itemView.post {
image.setImageDrawable(image.getRound(category.name.take(1).toUpperCase(),false))
private fun submitChanges() {
if (edit_text.visibility == View.VISIBLE ) {
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.
*
@ -48,7 +135,7 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
*/
override fun onItemReleased(position: Int) {
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.kanade.tachiyomi.R
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.
@ -16,7 +17,7 @@ class CategoryItem(val category: Category) : AbstractFlexibleItem<CategoryHolder
/**
* Whether this item is currently selected.
*/
var isSelected = false
var isEditing = false
/**
* 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>) {
holder.bind(category)
holder.isEditing(isEditing)
}
/**
* Returns true if this item is draggable.
*/
override fun isDraggable(): Boolean {
return true
return category.order != CREATE_CATEGORY_ORDER && !isEditing
}
override fun equals(other: Any?): Boolean {

@ -1,11 +1,13 @@
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.models.Category
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt
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.
*/
class CategoryPresenter(
private val db: DatabaseHelper = Injekt.get()
) : BasePresenter<CategoryController>() {
private val controller: CategoryController,
private val db: DatabaseHelper = Injekt.get(),
preferences: PreferencesHelper = Injekt.get()
) {
private val context = preferences.context
/**
* List containing categories.
*/
private var categories: List<Category> = emptyList()
private var categories: MutableList<Category> = mutableListOf()
/**
* Called when the presenter is created.
*
* @param savedState The saved state of this presenter.
*/
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
db.getCategories().asRxObservable()
.doOnNext { categories = it }
.map { it.map(::CategoryItem) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(CategoryController::setCategories)
fun getCategories() {
if (categories.isNotEmpty()) {
controller.setCategories(categories.map(::CategoryItem))
}
GlobalScope.launch(Dispatchers.IO) {
categories.clear()
categories.add(newCategory())
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.
*/
fun createCategory(name: String) {
fun createCategory(name: String): Boolean {
// Do not allow duplicate categories.
if (categoryExists(name)) {
Observable.just(Unit).subscribeFirst({ view, _ -> view.onCategoryExistsError() })
return
controller.onCategoryExistsError()
return false
}
// Create category.
@ -55,16 +71,25 @@ class CategoryPresenter(
cat.order = categories.map { it.order + 1 }.max() ?: 0
// 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.
*
* @param categories The list of categories to delete.
* @param category The category to delete.
*/
fun deleteCategories(categories: List<Category>) {
db.deleteCategories(categories).asRxObservable().subscribe()
fun deleteCategory(category: Category?) {
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>) {
categories.forEachIndexed { i, category ->
category.order = i
if (category.order != CREATE_CATEGORY_ORDER)
category.order = i - 1
}
db.insertCategories(categories).asRxObservable().subscribe()
db.insertCategories(categories.filter { it.order != CREATE_CATEGORY_ORDER }).executeAsBlocking()
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 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.
if (categoryExists(name)) {
Observable.just(Unit).subscribeFirst({ view, _ -> view.onCategoryExistsError() })
return
controller.onCategoryExistsError()
return false
}
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.
*/
fun categoryExists(name: String): Boolean {
private fun categoryExists(name: String): Boolean {
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()
}
GlideApp.with(itemView.context).clear(image)
GlideApp.with(itemView.context).clear(edit_button)
if (extension is Extension.Available) {
GlideApp.with(itemView.context)
.load(extension.iconUrl)
.into(image)
.into(edit_button)
} else {
extension.getApplicationIcon(itemView.context)?.let { image.setImageDrawable(it) }
extension.getApplicationIcon(itemView.context)?.let { edit_button.setImageDrawable(it) }
}
bindButton(item)
}

@ -9,6 +9,8 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.category.CategoryAdapter
import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.lang.removeArticles
import eu.kanade.tachiyomi.util.system.launchUI
import kotlinx.coroutines.delay
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Calendar
@ -28,9 +30,6 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
*/
private var mangas: List<LibraryItem> = emptyList()
val onItemReleaseListener: CategoryAdapter.OnItemReleaseListener = view
/**
* 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.
*/
fun startReading(position: Int)
fun onItemReleased(position: Int)
}
}

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

@ -31,9 +31,27 @@ class LibraryGridHolder(
private val view: View,
adapter: LibraryCategoryAdapter,
var width:Int,
var fixedSize: Boolean
private var fixedSize: Boolean
) : 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
* holder with the given manga.
@ -44,6 +62,7 @@ class LibraryGridHolder(
// Update the title and subtitle of the manga.
title.text = item.manga.currentTitle()
subtitle.text = item.manga.originalAuthor()?.trim()
if (!fixedSize) {
title.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
@ -74,25 +93,13 @@ class LibraryGridHolder(
0 -> if (item.manga.unread > 0) -1 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)
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.
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) {
super.onItemReleased(position)
(adapter as? LibraryCategoryAdapter)?.onItemReleaseListener?.onItemReleased(position)
(adapter as? LibraryCategoryAdapter)?.libraryListener?.onItemReleased(position)
}
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
marginParams.bottomMargin = 6.dpToPx
card.layoutParams = marginParams
cover_thumbnail.maxHeight = Integer.MAX_VALUE
constraint_layout.minHeight = 0
cover_thumbnail.adjustViewBounds = false
cover_thumbnail.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, coverHeight)

@ -38,8 +38,16 @@ class LibraryListHolder(
title.text = item.manga.currentTitle()
badge_view.setUnreadDownload(
if (item.unreadType == 1) item.manga.unread else (item.unreadType - 1),
if (item.manga.source == LocalSource.ID) -2 else item.downloadCount)
when (item.unreadType) {
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.visibility = if (!item.manga.originalAuthor().isNullOrBlank()) View.VISIBLE

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.ui.main
import android.app.SearchManager
import android.content.ComponentCallbacks2
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
@ -11,6 +12,7 @@ import android.provider.Settings
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.webkit.WebView
import android.widget.FrameLayout
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.util.system.getResourceColor
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.updatePadding
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.visible
import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.coroutines.Dispatchers
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_HIDE_NAVIGATION
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 ->
// if device doesn't support light nav bar
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {

@ -17,20 +17,13 @@ class MangaHolder(
fun bind(item: MangaItem) {
// Update the title of the manga.
title.text = item.manga.currentTitle()
// Create thumbnail onclick to simulate long click
cover_thumbnail.setOnClickListener {
// Simulate long click on this view to enter selection mode
onLongClick(itemView)
}
subtitle.text = item.manga.currentAuthor()?.trim()
// Update the cover.
GlideApp.with(itemView.context).clear(cover_thumbnail)
GlideApp.with(itemView.context)
.load(item.manga)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
// .centerCrop()
// .circleCrop()
.dontAnimate()
.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.migration.manga.design.PreMigrationController
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.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.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -91,12 +91,17 @@ class MigrationController : NucleusController<MigrationPresenter>(),
}
adapter?.updateDataSet(state.sourcesWithManga)
} else {
val switching = title == resources?.getString(R.string.label_migration)
title = state.selectedSource.toString()
if (adapter !is MangaAdapter) {
adapter = MangaAdapter(this)
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
if (item is MangaItem) {
val controller = SearchController(item.manga)
val controller = PreMigrationController.create(listOf(item.manga.id!!))
controller.targetController = this
router.pushController(controller.withFadeTransaction())

@ -20,7 +20,7 @@ class SourceHolder(view: View, override val adapter: SourceAdapter) :
get() = card
init {
source_latest.text = "Auto"
source_latest.text = view.context.getString(R.string.action_auto)
source_browse.setText(R.string.select)
source_browse.setOnClickListener {
adapter.selectClickListener?.onSelectClick(adapterPosition)
@ -39,7 +39,7 @@ class SourceHolder(view: View, override val adapter: SourceAdapter) :
// Set circle letter image.
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
// Update circle letter image.
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) {
title.alpha = 1.0f
image.alpha = 1.0f
edit_button.alpha = 1.0f
title.paintFlags = title.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
} else {
title.alpha = DISABLED_ALPHA
image.alpha = DISABLED_ALPHA
edit_button.alpha = DISABLED_ALPHA
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>
<selector>
<item android:state_activated="false">
<shape
android:shape="rectangle">
<shape android:shape="rectangle">
<corners android:radius="16dp" />
<size
android:height="32dp"
android:width="32dp" />
android:width="32dp"
android:height="32dp" />
<solid android:color="@android:color/transparent" />
</shape>
</item>
<item android:state_activated="true">
<shape
android:shape="rectangle">
<shape android:shape="rectangle">
<corners android:radius="16dp" />
<size
android:height="32dp"
android:width="32dp" />
android:width="32dp"
android:height="32dp" />
<solid android:color="?attr/colorAccent" />
</shape>
</item>

@ -1,13 +1,7 @@
<?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: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:layout_width="match_parent"
android:layout_height="wrap_content"
@ -157,4 +151,3 @@
tools:text="Sample artist" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

@ -13,7 +13,7 @@
android:background="?attr/selectable_list_drawable">
<ImageView
android:id="@+id/image"
android:id="@+id/edit_button"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="8dp"
@ -33,7 +33,7 @@
android:ellipsize="end"
android:textAppearance="@style/TextAppearance.Regular.SubHeading"
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_constraintEnd_toStartOf="@+id/source_latest"
tools:text="Source title"/>

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
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_height="match_parent"
android:clipToPadding="false"
@ -13,20 +13,13 @@
android:layout_height="match_parent"
android:choiceMode="multipleChoice"
android:clipToPadding="false"
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"/>
tools:listitem="@layout/categories_item" />
<eu.kanade.tachiyomi.widget.EmptyView
android:id="@+id/empty_view"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_height="wrap_content" />
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -1,40 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
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
android:id="@+id/image"
android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height"
android:id="@+id/reorder"
android:layout_width="54dp"
android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
android:clickable="true"
android:paddingStart="@dimen/material_component_lists_icon_left_padding"
android:paddingEnd="0dp"
tools:src="@mipmap/ic_launcher_round"/>
android:scaleType="center"
android:tint="?android:attr/textColorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
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
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/material_component_lists_text_left_padding"
android:layout_marginEnd="@dimen/material_component_lists_single_line_with_avatar_height"
android:ellipsize="end"
android:layout_width="0dp"
android:layout_height="match_parent"
android:gravity="center|start"
android:layout_marginStart="3dp"
android:inputType="none"
android:maxLines="1"
android:layout_gravity="center_vertical"
android:textAppearance="@style/TextAppearance.Regular.SubHeading"
android:textColor="@color/textColorPrimary"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/edit_button"
app:layout_constraintStart_toEndOf="@+id/image"
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">
<ImageView
android:id="@+id/image"
android:id="@+id/edit_button"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="12dp"
@ -33,7 +33,7 @@
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Regular.SubHeading"
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_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/lang"
@ -47,7 +47,7 @@
android:layout_height="wrap_content"
android:maxLines="1"
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_constraintBottom_toBottomOf="parent"
tools:text="English"

@ -8,7 +8,7 @@
android:background="?attr/selectable_list_drawable">
<ImageView
android:id="@+id/image"
android:id="@+id/edit_button"
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:paddingLeft="@dimen/material_component_lists_icon_left_padding"

@ -175,6 +175,7 @@
<string name="portrait">Portrait</string>
<string name="landscape">Landscape</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_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="local_source_badge">Local</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">
<item quantity="one">New chapter</item>
<item quantity="other">%d unread</item>
@ -526,7 +530,7 @@
<!-- Category activity -->
<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 -->
<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="reset_tags">Reset Tags</string>
<string name="display_as">Display as</string>
<string name="action_auto">Auto</string>
</resources>

Loading…
Cancel
Save