Selection and reodering favourites categories

pull/163/head
Koitharu 4 years ago
parent 451b9fc0f1
commit 80db7f0b74
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -23,7 +23,7 @@ class ListSelectionController(
private val activity: Activity, private val activity: Activity,
private val decoration: AbstractSelectionItemDecoration, private val decoration: AbstractSelectionItemDecoration,
private val registryOwner: SavedStateRegistryOwner, private val registryOwner: SavedStateRegistryOwner,
private val callback: Callback, private val callback: Callback2,
) : ActionMode.Callback, SavedStateRegistry.SavedStateProvider { ) : ActionMode.Callback, SavedStateRegistry.SavedStateProvider {
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
@ -89,19 +89,19 @@ class ListSelectionController(
} }
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
return callback.onCreateActionMode(mode, menu) return callback.onCreateActionMode(this, mode, menu)
} }
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
return callback.onPrepareActionMode(mode, menu) return callback.onPrepareActionMode(this, mode, menu)
} }
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return callback.onActionItemClicked(mode, item) return callback.onActionItemClicked(this, mode, item)
} }
override fun onDestroyActionMode(mode: ActionMode) { override fun onDestroyActionMode(mode: ActionMode) {
callback.onDestroyActionMode(mode) callback.onDestroyActionMode(this, mode)
clear() clear()
actionMode = null actionMode = null
} }
@ -114,7 +114,7 @@ class ListSelectionController(
private fun notifySelectionChanged() { private fun notifySelectionChanged() {
val count = decoration.checkedItemsCount val count = decoration.checkedItemsCount
callback.onSelectionChanged(count) callback.onSelectionChanged(this, count)
if (count == 0) { if (count == 0) {
actionMode?.finish() actionMode?.finish()
} else { } else {
@ -131,17 +131,53 @@ class ListSelectionController(
notifySelectionChanged() notifySelectionChanged()
} }
interface Callback : ActionMode.Callback { @Deprecated("")
interface Callback : Callback2 {
fun onSelectionChanged(count: Int) fun onSelectionChanged(count: Int)
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean
override fun onDestroyActionMode(mode: ActionMode) = Unit fun onDestroyActionMode(mode: ActionMode) = Unit
override fun onSelectionChanged(controller: ListSelectionController, count: Int) {
onSelectionChanged(count)
}
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
return onCreateActionMode(mode, menu)
}
override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
return onPrepareActionMode(mode, menu)
}
override fun onActionItemClicked(
controller: ListSelectionController,
mode: ActionMode,
item: MenuItem
): Boolean = onActionItemClicked(mode, item)
override fun onDestroyActionMode(controller: ListSelectionController, mode: ActionMode) {
onDestroyActionMode(mode)
}
}
interface Callback2 {
fun onSelectionChanged(controller: ListSelectionController, count: Int)
fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean
fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean
fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean
fun onDestroyActionMode(controller: ListSelectionController, mode: ActionMode) = Unit
} }
private inner class StateEventObserver : LifecycleEventObserver { private inner class StateEventObserver : LifecycleEventObserver {

@ -11,11 +11,11 @@ import android.view.MenuItem
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import com.google.android.material.R as materialR
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.network.UserAgentInterceptor import org.koitharu.kotatsu.core.network.UserAgentInterceptor
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import com.google.android.material.R as materialR
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback { class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
@ -59,8 +59,9 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.opt_browser, menu) menuInflater.inflate(R.menu.opt_browser, menu)
return super.onCreateOptionsMenu(menu) return true
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {

@ -161,8 +161,9 @@ class DetailsActivity :
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.opt_details, menu) menuInflater.inflate(R.menu.opt_details, menu)
return super.onCreateOptionsMenu(menu) return true
} }
override fun onPrepareOptionsMenu(menu: Menu): Boolean { override fun onPrepareOptionsMenu(menu: Menu): Boolean {

@ -5,7 +5,10 @@ import kotlinx.coroutines.flow.*
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.* import org.koitharu.kotatsu.core.db.entity.*
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.favourites.data.* import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.favourites.data.FavouriteManga
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
@ -122,6 +125,14 @@ class FavouritesRepository(
channels.deleteChannel(id) channels.deleteChannel(id)
} }
suspend fun removeCategories(ids: Collection<Long>) {
db.withTransaction {
for (id in ids) {
removeCategory(id)
}
}
}
suspend fun setCategoryOrder(id: Long, order: SortOrder) { suspend fun setCategoryOrder(id: Long, order: SortOrder) {
db.favouriteCategoriesDao.updateOrder(id, order.name) db.favouriteCategoriesDao.updateOrder(id, order.name)
} }

@ -1,178 +0,0 @@
package org.koitharu.kotatsu.favourites.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.Insets
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.util.ActionModeListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.FragmentFavouritesBinding
import org.koitharu.kotatsu.databinding.ItemEmptyStateBinding
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate
import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import org.koitharu.kotatsu.utils.ext.addMenuProvider
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class FavouritesContainerFragment :
BaseFragment<FragmentFavouritesBinding>(),
FavouritesTabLongClickListener,
CategoriesEditDelegate.CategoriesEditCallback,
ActionModeListener,
View.OnClickListener {
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
private val editDelegate by lazy(LazyThreadSafetyMode.NONE) {
CategoriesEditDelegate(requireContext(), this)
}
private var pagerAdapter: FavouritesPagerAdapter? = null
private var stubBinding: ItemEmptyStateBinding? = null
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentFavouritesBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = FavouritesPagerAdapter(this, this)
viewModel.allCategories.value?.let(::onCategoriesChanged)
binding.pager.adapter = adapter
pagerAdapter = adapter
TabLayoutMediator(binding.tabs, binding.pager, adapter).attach()
actionModeDelegate.addListener(this, viewLifecycleOwner)
addMenuProvider(FavouritesContainerMenuProvider(view.context))
viewModel.allCategories.observe(viewLifecycleOwner, ::onCategoriesChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError)
}
override fun onDestroyView() {
pagerAdapter = null
stubBinding = null
super.onDestroyView()
}
override fun onActionModeStarted(mode: ActionMode) {
binding.pager.isUserInputEnabled = false
binding.tabs.setTabsEnabled(false)
}
override fun onActionModeFinished(mode: ActionMode) {
binding.pager.isUserInputEnabled = true
binding.tabs.setTabsEnabled(true)
}
override fun onWindowInsetsChanged(insets: Insets) {
binding.tabs.apply {
updatePadding(
left = insets.left,
right = insets.right
)
}
}
private fun onCategoriesChanged(categories: List<CategoryListModel>) {
pagerAdapter?.replaceData(categories)
if (categories.isEmpty()) {
binding.pager.isVisible = false
binding.tabs.isVisible = false
showStub()
} else {
binding.pager.isVisible = true
binding.tabs.isVisible = true
(stubBinding?.root ?: binding.stubEmptyState).isVisible = false
}
}
private fun onError(e: Throwable) {
Snackbar.make(binding.pager, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
}
override fun onTabLongClick(tabView: View, item: CategoryListModel): Boolean {
/*when (item) {
is CategoryListModel.All -> showAllCategoriesMenu(tabView)
is CategoryListModel.CategoryItem -> showCategoryMenu(tabView, item.category)
}*/
return true
}
override fun onClick(v: View) {
when (v.id) {
R.id.button_retry -> startActivity(FavouritesCategoryEditActivity.newIntent(v.context))
}
}
override fun onDeleteCategory(category: FavouriteCategory) {
viewModel.deleteCategory(category.id)
}
private fun TabLayout.setTabsEnabled(enabled: Boolean) {
val tabStrip = getChildAt(0) as? ViewGroup ?: return
for (tab in tabStrip.children) {
tab.isEnabled = enabled
}
}
private fun showCategoryMenu(tabView: View, category: FavouriteCategory) {
val menu = PopupMenu(tabView.context, tabView)
menu.inflate(R.menu.popup_category)
menu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_remove -> editDelegate.deleteCategory(category)
R.id.action_edit -> startActivity(
FavouritesCategoryEditActivity.newIntent(
tabView.context,
category.id
)
)
else -> return@setOnMenuItemClickListener false
}
true
}
menu.show()
}
private fun showAllCategoriesMenu(tabView: View) {
val menu = PopupMenu(tabView.context, tabView)
menu.inflate(R.menu.popup_category_all)
menu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_create -> startActivity(FavouritesCategoryEditActivity.newIntent(requireContext()))
R.id.action_hide -> viewModel.setAllCategoriesVisible(false)
}
true
}
menu.show()
}
private fun showStub() {
val stub = stubBinding ?: ItemEmptyStateBinding.bind(binding.stubEmptyState.inflate())
stub.root.isVisible = true
stub.icon.setImageResource(R.drawable.ic_heart_outline)
stub.textPrimary.setText(R.string.text_empty_holder_primary)
stub.textSecondary.setText(R.string.empty_favourite_categories)
stub.buttonRetry.setText(R.string.add)
stub.buttonRetry.isVisible = true
stub.buttonRetry.setOnClickListener(this)
stubBinding = stub
}
companion object {
fun newInstance() = FavouritesContainerFragment()
}
}

@ -1,28 +0,0 @@
package org.koitharu.kotatsu.favourites.ui
import android.content.Context
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
class FavouritesContainerMenuProvider(
private val context: Context,
) : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_favourites, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.action_categories -> {
context.startActivity(FavouriteCategoriesActivity.newIntent(context))
true
}
else -> false
}
}
}

@ -1,70 +0,0 @@
package org.koitharu.kotatsu.favourites.ui
import android.annotation.SuppressLint
import android.view.View
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
class FavouritesPagerAdapter(
fragment: Fragment,
private val longClickListener: FavouritesTabLongClickListener
) : FragmentStateAdapter(fragment.childFragmentManager, fragment.viewLifecycleOwner.lifecycle),
TabLayoutMediator.TabConfigurationStrategy,
View.OnLongClickListener {
private val differ = AsyncListDiffer(this, DiffCallback())
override fun getItemCount() = differ.currentList.size
override fun createFragment(position: Int): Fragment {
val item = differ.currentList[position]
return FavouritesListFragment.newInstance(item.category.id)
}
override fun getItemId(position: Int): Long {
return differ.currentList[position].category.id
}
override fun containsItem(itemId: Long): Boolean {
return differ.currentList.any { it.category.id == itemId }
}
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
val item = differ.currentList[position]
tab.text = item.category.title
tab.view.tag = item.category.id
tab.view.setOnLongClickListener(this)
}
fun replaceData(data: List<CategoryListModel>) {
differ.submitList(data)
}
override fun onLongClick(v: View): Boolean {
val itemId = v.tag as? Long ?: return false
val item = differ.currentList.find { x -> x.category.id == itemId } ?: return false
return longClickListener.onTabLongClick(v, item)
}
private class DiffCallback : DiffUtil.ItemCallback<CategoryListModel>() {
override fun areItemsTheSame(
oldItem: CategoryListModel,
newItem: CategoryListModel
): Boolean {
return oldItem.category.id == newItem.category.id
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(
oldItem: CategoryListModel,
newItem: CategoryListModel
): Boolean = oldItem == newItem
}
}

@ -1,9 +0,0 @@
package org.koitharu.kotatsu.favourites.ui
import android.view.View
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
fun interface FavouritesTabLongClickListener {
fun onTabLongClick(tabView: View, item: CategoryListModel): Boolean
}

@ -4,8 +4,6 @@ import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import coil.ImageLoader import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.favourites.ui.categories.adapter.categoryAD import org.koitharu.kotatsu.favourites.ui.categories.adapter.categoryAD
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
@ -17,7 +15,7 @@ import kotlin.jvm.internal.Intrinsics
class CategoriesAdapter( class CategoriesAdapter(
coil: ImageLoader, coil: ImageLoader,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
onItemClickListener: OnListItemClickListener<FavouriteCategory>, onItemClickListener: FavouriteCategoriesListListener,
listListener: ListStateHolderListener, listListener: ListStateHolderListener,
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) { ) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
@ -43,7 +41,16 @@ class CategoriesAdapter(
} }
override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? { override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? {
return super.getChangePayload(oldItem, newItem) return when {
oldItem is CategoryListModel && newItem is CategoryListModel -> {
if (oldItem.isReorderMode != newItem.isReorderMode) {
Unit
} else {
super.getChangePayload(oldItem, newItem)
}
}
else -> super.getChangePayload(oldItem, newItem)
}
} }
} }
} }

@ -1,30 +0,0 @@
package org.koitharu.kotatsu.favourites.ui.categories
import android.content.Context
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory
import com.google.android.material.R as materialR
class CategoriesEditDelegate(
private val context: Context,
private val callback: CategoriesEditCallback
) {
fun deleteCategory(category: FavouriteCategory) {
MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered)
.setMessage(context.getString(R.string.category_delete_confirm, category.title))
.setTitle(R.string.remove_category)
.setIcon(R.drawable.ic_delete)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.remove) { _, _ ->
callback.onDeleteCategory(category)
}.create()
.show()
}
interface CategoriesEditCallback {
fun onDeleteCategory(category: FavouriteCategory)
}
}

@ -0,0 +1,64 @@
package org.koitharu.kotatsu.favourites.ui.categories
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import com.google.android.material.R as materialR
class CategoriesSelectionCallback(
private val recyclerView: RecyclerView,
private val viewModel: FavouritesCategoriesViewModel,
) : ListSelectionController.Callback2 {
override fun onSelectionChanged(controller: ListSelectionController, count: Int) {
recyclerView.invalidateItemDecorations()
}
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.mode_category, menu)
return true
}
override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
val isOneItem = controller.count == 1
menu.findItem(R.id.action_edit)?.isVisible = isOneItem
mode.title = controller.count.toString()
return true
}
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_edit -> {
val id = controller.peekCheckedIds().singleOrNull() ?: return false
val context = recyclerView.context
val intent = FavouritesCategoryEditActivity.newIntent(context, id)
context.startActivity(intent)
mode.finish()
true
}
R.id.action_remove -> {
confirmDeleteCategories(controller.snapshot(), mode)
true
}
else -> false
}
}
private fun confirmDeleteCategories(ids: Set<Long>, mode: ActionMode) {
val context = recyclerView.context
MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered)
.setMessage(R.string.categories_delete_confirm)
.setTitle(R.string.remove_category)
.setIcon(R.drawable.ic_delete)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.remove) { _, _ ->
viewModel.deleteCategories(ids)
mode.finish()
}.show()
}
}

@ -0,0 +1,57 @@
package org.koitharu.kotatsu.favourites.ui.categories
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.view.View
import androidx.core.graphics.ColorUtils
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.utils.ext.getItem
import org.koitharu.kotatsu.utils.ext.getThemeColor
import com.google.android.material.R as materialR
class CategoriesSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val radius = context.resources.getDimension(R.dimen.list_selector_corner)
private val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED)
private val fillColor = ColorUtils.setAlphaComponent(
ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),
0x74
)
private val padding = context.resources.getDimension(R.dimen.grid_spacing_outer)
init {
paint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width)
hasForeground = true
hasBackground = false
isIncludeDecorAndMargins = false
}
override fun getItemId(parent: RecyclerView, child: View): Long {
val holder = parent.getChildViewHolder(child) ?: return RecyclerView.NO_ID
val item = holder.getItem(CategoryListModel::class.java) ?: return RecyclerView.NO_ID
return item.category.id
}
override fun onDrawForeground(
canvas: Canvas,
parent: RecyclerView,
child: View,
bounds: RectF,
state: RecyclerView.State,
) {
bounds.inset(padding, padding)
paint.color = fillColor
paint.style = Paint.Style.FILL
canvas.drawRoundRect(bounds, radius, radius, paint)
paint.color = strokeColor
paint.style = Paint.Style.STROKE
canvas.drawRoundRect(bounds, radius, radius, paint)
}
}

@ -4,9 +4,12 @@ import android.app.ActivityOptions
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
@ -16,7 +19,7 @@ import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
import org.koitharu.kotatsu.favourites.ui.FavouritesActivity import org.koitharu.kotatsu.favourites.ui.FavouritesActivity
@ -29,50 +32,78 @@ import org.koitharu.kotatsu.utils.ext.measureHeight
class FavouriteCategoriesActivity : class FavouriteCategoriesActivity :
BaseActivity<ActivityCategoriesBinding>(), BaseActivity<ActivityCategoriesBinding>(),
OnListItemClickListener<FavouriteCategory>, FavouriteCategoriesListListener,
View.OnClickListener, View.OnClickListener,
CategoriesEditDelegate.CategoriesEditCallback,
ListStateHolderListener { ListStateHolderListener {
private val viewModel by viewModel<FavouritesCategoriesViewModel>() private val viewModel by viewModel<FavouritesCategoriesViewModel>()
private lateinit var adapter: CategoriesAdapter private lateinit var adapter: CategoriesAdapter
private lateinit var reorderHelper: ItemTouchHelper private lateinit var selectionController: ListSelectionController
private lateinit var editDelegate: CategoriesEditDelegate private var reorderHelper: ItemTouchHelper? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivityCategoriesBinding.inflate(layoutInflater)) setContentView(ActivityCategoriesBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
adapter = CategoriesAdapter(get(), this, this, this) adapter = CategoriesAdapter(get(), this, this, this)
editDelegate = CategoriesEditDelegate(this, this) selectionController = ListSelectionController(
activity = this,
decoration = CategoriesSelectionDecoration(this),
registryOwner = this,
callback = CategoriesSelectionCallback(binding.recyclerView, viewModel),
)
binding.buttonDone.setOnClickListener(this)
selectionController.attachToRecyclerView(binding.recyclerView)
binding.recyclerView.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
binding.fabAdd.setOnClickListener(this) binding.fabAdd.setOnClickListener(this)
reorderHelper = ItemTouchHelper(ReorderHelperCallback())
reorderHelper.attachToRecyclerView(binding.recyclerView)
viewModel.detalizedCategories.observe(this, ::onCategoriesChanged) viewModel.detalizedCategories.observe(this, ::onCategoriesChanged)
viewModel.onError.observe(this, ::onError) viewModel.onError.observe(this, ::onError)
viewModel.isInReorderMode.observe(this, ::onReorderModeChanged)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.opt_categories, menu)
return true
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
menu.findItem(R.id.action_reorder)?.isVisible = !viewModel.isInReorderMode()
return super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_reorder -> {
viewModel.setReorderMode(true)
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onBackPressed() {
if (viewModel.isInReorderMode()) {
viewModel.setReorderMode(false)
} else {
super.onBackPressed()
}
} }
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.button_done -> viewModel.setReorderMode(false)
R.id.fab_add -> startActivity(FavouritesCategoryEditActivity.newIntent(this)) R.id.fab_add -> startActivity(FavouritesCategoryEditActivity.newIntent(this))
} }
} }
override fun onItemClick(item: FavouriteCategory, view: View) { override fun onItemClick(item: FavouriteCategory, view: View) {
/*val menu = PopupMenu(view.context, view) if (viewModel.isInReorderMode() || selectionController.onItemClick(item.id)) {
menu.inflate(R.menu.popup_category) return
menu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.action_remove -> editDelegate.deleteCategory(item)
R.id.action_edit -> startActivity(FavouritesCategoryEditActivity.newIntent(this, item.id))
}
true
} }
menu.show()*/
val intent = FavouritesActivity.newIntent(this, item) val intent = FavouritesActivity.newIntent(this, item)
val options = val options =
ActivityOptions.makeScaleUpAnimation(view, view.width / 2, view.height / 2, view.width, view.height) ActivityOptions.makeScaleUpAnimation(view, view.width / 2, view.height / 2, view.width, view.height)
@ -80,9 +111,11 @@ class FavouriteCategoriesActivity :
} }
override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean { override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean {
val viewHolder = binding.recyclerView.findContainingViewHolder(view) ?: return false return !viewModel.isInReorderMode() && selectionController.onItemLongClick(item.id)
reorderHelper.startDrag(viewHolder) }
return true
override fun onDragHandleTouch(holder: RecyclerView.ViewHolder): Boolean {
return reorderHelper?.startDrag(holder) != null
} }
override fun onRetryClick(error: Throwable) = Unit override fun onRetryClick(error: Throwable) = Unit
@ -111,8 +144,21 @@ class FavouriteCategoriesActivity :
.show() .show()
} }
override fun onDeleteCategory(category: FavouriteCategory) { private fun onReorderModeChanged(isReorderMode: Boolean) {
viewModel.deleteCategory(category.id) reorderHelper?.attachToRecyclerView(null)
reorderHelper = if (isReorderMode) {
selectionController.clear()
binding.fabAdd.hide()
ItemTouchHelper(ReorderHelperCallback()).apply {
attachToRecyclerView(binding.recyclerView)
}
} else {
binding.fabAdd.show()
null
}
binding.recyclerView.isNestedScrollingEnabled = !isReorderMode
invalidateOptionsMenu()
binding.buttonDone.isVisible = isReorderMode
} }
private inner class ReorderHelperCallback : ItemTouchHelper.SimpleCallback( private inner class ReorderHelperCallback : ItemTouchHelper.SimpleCallback(

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.favourites.ui.categories
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
interface FavouriteCategoriesListListener : OnListItemClickListener<FavouriteCategory> {
fun onDragHandleTouch(holder: RecyclerView.ViewHolder): Boolean
}

@ -1,9 +1,11 @@
package org.koitharu.kotatsu.favourites.ui.categories package org.koitharu.kotatsu.favourites.ui.categories
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
@ -20,6 +22,9 @@ class FavouritesCategoriesViewModel(
) : BaseViewModel() { ) : BaseViewModel() {
private var reorderJob: Job? = null private var reorderJob: Job? = null
private val isReorder = MutableStateFlow(false)
val isInReorderMode = isReorder.asLiveData(viewModelScope.coroutineContext)
val allCategories = repository.observeCategories() val allCategories = repository.observeCategories()
.mapItems { .mapItems {
@ -27,16 +32,20 @@ class FavouritesCategoriesViewModel(
mangaCount = 0, mangaCount = 0,
covers = listOf(), covers = listOf(),
category = it, category = it,
isReorderMode = false,
) )
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, emptyList()) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
val detalizedCategories = repository.observeCategoriesWithDetails() val detalizedCategories = combine(
.map { repository.observeCategoriesWithDetails(),
it.map { (category, covers) -> isReorder,
) { list, reordering ->
list.map { (category, covers) ->
CategoryListModel( CategoryListModel(
mangaCount = covers.size, mangaCount = covers.size,
covers = covers.take(3), covers = covers.take(3),
category = category, category = category,
isReorderMode = reordering,
) )
} }
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState)) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
@ -47,10 +56,22 @@ class FavouritesCategoriesViewModel(
} }
} }
fun deleteCategories(ids: Set<Long>) {
launchJob {
repository.removeCategories(ids)
}
}
fun setAllCategoriesVisible(isVisible: Boolean) { fun setAllCategoriesVisible(isVisible: Boolean) {
settings.isAllFavouritesVisible = isVisible settings.isAllFavouritesVisible = isVisible
} }
fun isInReorderMode(): Boolean = isReorder.value
fun setReorderMode(isReorderMode: Boolean) {
isReorder.value = isReorderMode
}
fun reorderCategories(oldPos: Int, newPos: Int) { fun reorderCategories(oldPos: Int, newPos: Int) {
val prevJob = reorderJob val prevJob = reorderJob
reorderJob = launchJob(Dispatchers.Default) { reorderJob = launchJob(Dispatchers.Default) {

@ -1,8 +1,14 @@
package org.koitharu.kotatsu.favourites.ui.categories.adapter package org.koitharu.kotatsu.favourites.ui.categories.adapter
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.View.OnClickListener import android.view.View.*
import android.view.View.OnLongClickListener import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import androidx.core.widget.ImageViewCompat
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
import coil.request.Disposable import coil.request.Disposable
@ -10,32 +16,50 @@ import coil.size.Scale
import coil.util.CoilUtils import coil.util.CoilUtils
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.ItemCategoryBinding import org.koitharu.kotatsu.databinding.ItemCategoryBinding
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.utils.ext.animatorDurationScale
import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.getThemeColor
import org.koitharu.kotatsu.utils.ext.newImageRequest import org.koitharu.kotatsu.utils.ext.newImageRequest
fun categoryAD( fun categoryAD(
coil: ImageLoader, coil: ImageLoader,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<FavouriteCategory> clickListener: FavouriteCategoriesListListener,
) = adapterDelegateViewBinding<CategoryListModel, ListModel, ItemCategoryBinding>( ) = adapterDelegateViewBinding<CategoryListModel, ListModel, ItemCategoryBinding>(
{ inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) } { inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) }
) { ) {
val eventListener = object : OnClickListener, OnLongClickListener { val eventListener = object : OnClickListener, OnLongClickListener, OnTouchListener {
override fun onClick(v: View) = clickListener.onItemClick(item.category, v) override fun onClick(v: View) = clickListener.onItemClick(item.category, v)
override fun onLongClick(v: View) = clickListener.onItemLongClick(item.category, v) override fun onLongClick(v: View) = clickListener.onItemLongClick(item.category, v)
override fun onTouch(v: View?, event: MotionEvent): Boolean = item.isReorderMode &&
event.actionMasked == MotionEvent.ACTION_DOWN &&
clickListener.onDragHandleTouch(this@adapterDelegateViewBinding)
} }
val backgroundColor = context.getThemeColor(android.R.attr.colorBackground)
ImageViewCompat.setImageTintList(
binding.imageViewCover3,
ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153))
)
ImageViewCompat.setImageTintList(
binding.imageViewCover2,
ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76))
)
val fallback = ColorDrawable(Color.TRANSPARENT)
val coverViews = arrayOf(binding.imageViewCover1, binding.imageViewCover2, binding.imageViewCover3) val coverViews = arrayOf(binding.imageViewCover1, binding.imageViewCover2, binding.imageViewCover3)
val imageRequests = arrayOfNulls<Disposable?>(coverViews.size) val imageRequests = arrayOfNulls<Disposable?>(coverViews.size)
val crossFadeDuration = (context.resources.getInteger(R.integer.config_defaultAnimTime) *
context.animatorDurationScale).toInt()
itemView.setOnClickListener(eventListener) itemView.setOnClickListener(eventListener)
itemView.setOnLongClickListener(eventListener) itemView.setOnLongClickListener(eventListener)
itemView.setOnTouchListener(eventListener)
bind { bind {
imageRequests.forEach { it?.dispose() } imageRequests.forEach { it?.dispose() }
binding.imageViewHandle.isVisible = item.isReorderMode
binding.textViewTitle.text = item.category.title binding.textViewTitle.text = item.category.title
binding.textViewSubtitle.text = context.resources.getQuantityString( binding.textViewSubtitle.text = context.resources.getQuantityString(
R.plurals.items, R.plurals.items,
@ -45,7 +69,8 @@ fun categoryAD(
repeat(coverViews.size) { i -> repeat(coverViews.size) { i ->
imageRequests[i] = coverViews[i].newImageRequest(item.covers.getOrNull(i)) imageRequests[i] = coverViews[i].newImageRequest(item.covers.getOrNull(i))
.placeholder(R.drawable.ic_placeholder) .placeholder(R.drawable.ic_placeholder)
.fallback(null) .crossfade(crossFadeDuration * (i + 1))
.fallback(fallback)
.error(R.drawable.ic_placeholder) .error(R.drawable.ic_placeholder)
.scale(Scale.FILL) .scale(Scale.FILL)
.allowRgb565(true) .allowRgb565(true)

@ -7,6 +7,7 @@ class CategoryListModel(
val mangaCount: Int, val mangaCount: Int,
val covers: List<String>, val covers: List<String>,
val category: FavouriteCategory, val category: FavouriteCategory,
val isReorderMode: Boolean,
) : ListModel { ) : ListModel {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@ -16,6 +17,7 @@ class CategoryListModel(
other as CategoryListModel other as CategoryListModel
if (mangaCount != other.mangaCount) return false if (mangaCount != other.mangaCount) return false
if (isReorderMode != other.isReorderMode) return false
if (covers != other.covers) return false if (covers != other.covers) return false
if (category.id != other.category.id) return false if (category.id != other.category.id) return false
if (category.title != other.category.title) return false if (category.title != other.category.title) return false
@ -26,6 +28,7 @@ class CategoryListModel(
override fun hashCode(): Int { override fun hashCode(): Int {
var result = mangaCount var result = mangaCount
result = 31 * result + isReorderMode.hashCode()
result = 31 * result + covers.hashCode() result = 31 * result + covers.hashCode()
result = 31 * result + category.id.hashCode() result = 31 * result + category.id.hashCode()
result = 31 * result + category.title.hashCode() result = 31 * result + category.title.hashCode()

@ -11,10 +11,8 @@ import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseBottomSheet import org.koitharu.kotatsu.base.ui.BaseBottomSheet
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.databinding.DialogFavoriteCategoriesBinding import org.koitharu.kotatsu.databinding.DialogFavoriteCategoriesBinding
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
@ -25,7 +23,6 @@ import org.koitharu.kotatsu.utils.ext.withArgs
class FavouriteCategoriesBottomSheet : class FavouriteCategoriesBottomSheet :
BaseBottomSheet<DialogFavoriteCategoriesBinding>(), BaseBottomSheet<DialogFavoriteCategoriesBinding>(),
OnListItemClickListener<MangaCategoryItem>, OnListItemClickListener<MangaCategoryItem>,
CategoriesEditDelegate.CategoriesEditCallback,
View.OnClickListener { View.OnClickListener {
private val viewModel by viewModel<MangaCategoriesViewModel> { private val viewModel by viewModel<MangaCategoriesViewModel> {
@ -66,8 +63,6 @@ class FavouriteCategoriesBottomSheet :
viewModel.setChecked(item.id, !item.isChecked) viewModel.setChecked(item.id, !item.isChecked)
} }
override fun onDeleteCategory(category: FavouriteCategory) = Unit
private fun onContentChanged(categories: List<MangaCategoryItem>) { private fun onContentChanged(categories: List<MangaCategoryItem>) {
adapter?.items = categories adapter?.items = categories
} }

@ -8,7 +8,7 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
import kotlin.math.roundToInt import org.koitharu.kotatsu.utils.ext.scale
class ReadingProgressDrawable( class ReadingProgressDrawable(
context: Context, context: Context,
@ -105,7 +105,7 @@ class ReadingProgressDrawable(
if (hasText) { if (hasText) {
if (checkDrawable != null && progress >= 1f - Math.ulp(progress)) { if (checkDrawable != null && progress >= 1f - Math.ulp(progress)) {
tempRect.set(bounds) tempRect.set(bounds)
tempRect *= 0.6 tempRect.scale(0.6)
checkDrawable.bounds = tempRect checkDrawable.bounds = tempRect
checkDrawable.draw(canvas) checkDrawable.draw(canvas)
} else { } else {
@ -139,13 +139,4 @@ class ReadingProgressDrawable(
paint.getTextBounds(text, 0, text.length, tempRect) paint.getTextBounds(text, 0, text.length, tempRect)
return testTextSize * width / tempRect.width() return testTextSize * width / tempRect.width()
} }
private operator fun Rect.timesAssign(factor: Double) {
val newWidth = (width() * factor).roundToInt()
val newHeight = (height() * factor).roundToInt()
inset(
(width() - newWidth) / 2,
(height() - newHeight) / 2,
)
}
} }

@ -133,8 +133,9 @@ class ReaderActivity :
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.opt_reader_top, menu) menuInflater.inflate(R.menu.opt_reader_top, menu)
return super.onCreateOptionsMenu(menu) return true
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {

@ -62,8 +62,9 @@ class SettingsActivity :
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.opt_settings, menu) menuInflater.inflate(R.menu.opt_settings, menu)
return super.onCreateOptionsMenu(menu) return true
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {

@ -0,0 +1,13 @@
package org.koitharu.kotatsu.utils.ext
import android.graphics.Rect
import kotlin.math.roundToInt
fun Rect.scale(factor: Double) {
val newWidth = (width() * factor).roundToInt()
val newHeight = (height() * factor).roundToInt()
inset(
(width() - newWidth) / 2,
(height() - newHeight) / 2,
)
}

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" />
</vector>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M9,3L5,7H8V14H10V7H13M16,17V10H14V17H11L15,21L19,17H16Z" />
</vector>

@ -6,9 +6,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/list_selector" android:background="@drawable/list_selector"
android:paddingVertical="12dp" android:minHeight="98dp"
android:paddingStart="?android:listPreferredItemPaddingStart" android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd"> tools:ignore="RtlSymmetry">
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover3" android:id="@+id/imageView_cover3"
@ -22,28 +22,30 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small" app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
tools:src="@tools:sample/backgrounds/scenic" /> app:tintMode="src_atop"
tools:src="@tools:sample/backgrounds/scenic"
tools:tint="#99FFFFFF" />
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover2" android:id="@+id/imageView_cover2"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="64dp" android:layout_height="64dp"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:alpha="50"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="W,13:18" app:layout_constraintDimensionRatio="W,13:18"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small" app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
tools:src="@tools:sample/backgrounds/scenic" /> app:tintMode="src_atop"
tools:src="@tools:sample/backgrounds/scenic"
tools:tint="#4DFFFFFF" />
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover1" android:id="@+id/imageView_cover1"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="64dp" android:layout_height="64dp"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:alpha="120"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="W,13:18" app:layout_constraintDimensionRatio="W,13:18"
@ -57,6 +59,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal" android:layout_marginStart="@dimen/margin_normal"
android:layout_marginEnd="?listPreferredItemPaddingEnd"
android:ellipsize="end" android:ellipsize="end"
android:singleLine="true" android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodyLarge" android:textAppearance="?attr/textAppearanceBodyLarge"
@ -73,6 +76,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal" android:layout_marginStart="@dimen/margin_normal"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:layout_marginEnd="?listPreferredItemPaddingEnd"
android:singleLine="true" android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall" android:textAppearance="?attr/textAppearanceBodySmall"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -82,4 +86,17 @@
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
tools:text="@tools:sample/lorem[1]" /> tools:text="@tools:sample/lorem[1]" />
<ImageView
android:id="@+id/imageView_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/reorder"
android:padding="@dimen/margin_normal"
android:src="@drawable/ic_reorder_handle"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_edit"
android:icon="@drawable/ic_edit"
android:title="@string/edit"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_remove"
android:icon="@drawable/ic_delete"
android:title="@string/remove"
app:showAsAction="ifRoom" />
</menu>

@ -4,9 +4,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/action_categories" android:id="@+id/action_reorder"
android:orderInCategory="50" android:icon="@drawable/ic_reorder"
android:title="@string/categories_" android:title="@string/reorder"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
</menu> </menu>

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_remove"
android:title="@string/remove" />
<item
android:id="@+id/action_edit"
android:title="@string/edit" />
</menu>

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_hide"
android:title="@string/hide" />
<item
android:id="@+id/action_create"
android:title="@string/create_category" />
</menu>

@ -330,4 +330,6 @@
<string name="no_manga_sources">No manga sources</string> <string name="no_manga_sources">No manga sources</string>
<string name="no_manga_sources_text">Enable manga sources to read manga online</string> <string name="no_manga_sources_text">Enable manga sources to read manga online</string>
<string name="random">Random</string> <string name="random">Random</string>
<string name="categories_delete_confirm">Are you sure you want to delete the selected favorite categories?\nAll manga in it will be lost and this cannot be undone.</string>
<string name="reorder">Reorder</string>
</resources> </resources>
Loading…
Cancel
Save