Edit favourite category activity

This commit is contained in:
Koitharu
2022-05-09 09:02:58 +03:00
parent 345a1379ae
commit 4771882f50
24 changed files with 480 additions and 193 deletions

View File

@@ -53,7 +53,8 @@
<activity
android:name="org.koitharu.kotatsu.search.ui.SearchActivity"
android:label="@string/search" />
<activity android:name="org.koitharu.kotatsu.search.ui.MangaListActivity"
<activity
android:name="org.koitharu.kotatsu.search.ui.MangaListActivity"
android:label="@string/search_manga" />
<activity
android:name="org.koitharu.kotatsu.settings.SettingsActivity"
@@ -78,7 +79,8 @@
<activity
android:name="org.koitharu.kotatsu.widget.shelf.ShelfConfigActivity"
android:exported="true"
android:label="@string/manga_shelf">
android:label="@string/manga_shelf"
android:theme="@style/Theme.Kotatsu.DialogWhenLarge">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
@@ -95,9 +97,13 @@
android:windowSoftInputMode="adjustResize" />
<activity
android:name="org.koitharu.kotatsu.download.ui.DownloadsActivity"
android:label="@string/downloads"
android:launchMode="singleTop"
android:label="@string/downloads" />
<activity android:name="org.koitharu.kotatsu.image.ui.ImageActivity"/>
android:theme="@style/Theme.Kotatsu.DialogWhenLarge" />
<activity android:name="org.koitharu.kotatsu.image.ui.ImageActivity" />
<activity
android:name="org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity"
android:theme="@style/Theme.Kotatsu.DialogWhenLarge" />
<service
android:name="org.koitharu.kotatsu.download.ui.service.DownloadService"

View File

@@ -4,6 +4,7 @@ import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditViewModel
import org.koitharu.kotatsu.favourites.ui.categories.select.MangaCategoriesViewModel
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListViewModel
@@ -19,4 +20,5 @@ val favouritesModule
viewModel { manga ->
MangaCategoriesViewModel(manga.get(), get())
}
viewModel { params -> FavouritesCategoryEditViewModel(params[0], get(), get()) }
}

View File

@@ -6,6 +6,9 @@ import kotlinx.coroutines.flow.Flow
@Dao
abstract class FavouriteCategoriesDao {
@Query("SELECT * FROM favourite_categories WHERE category_id = :id")
abstract suspend fun find(id: Int): FavouriteCategoryEntity
@Query("SELECT * FROM favourite_categories ORDER BY sort_key")
abstract suspend fun findAll(): List<FavouriteCategoryEntity>
@@ -27,6 +30,9 @@ abstract class FavouriteCategoriesDao {
@Query("UPDATE favourite_categories SET title = :title WHERE category_id = :id")
abstract suspend fun updateTitle(id: Long, title: String)
@Query("UPDATE favourite_categories SET title = :title, `order` = :order, `track` = :tracker WHERE category_id = :id")
abstract suspend fun update(id: Long, title: String, order: String, tracker: Boolean)
@Query("UPDATE favourite_categories SET `order` = :order WHERE category_id = :id")
abstract suspend fun updateOrder(id: Long, order: String)

View File

@@ -52,6 +52,11 @@ class FavouritesRepository(
}.distinctUntilChanged()
}
fun observeCategory(id: Long): Flow<FavouriteCategory> {
return db.favouriteCategoriesDao.observe(id)
.map { it.toFavouriteCategory() }
}
fun observeCategories(mangaId: Long): Flow<List<FavouriteCategory>> {
return db.favouritesDao.observe(mangaId).map { entity ->
entity?.categories?.map { it.toFavouriteCategory() }.orEmpty()
@@ -62,6 +67,29 @@ class FavouritesRepository(
return db.favouritesDao.observeIds(mangaId).map { it.toSet() }
}
suspend fun getCategory(id: Long): FavouriteCategory {
return db.favouriteCategoriesDao.find(id.toInt()).toFavouriteCategory()
}
suspend fun createCategory(title: String, sortOrder: SortOrder, isTrackerEnabled: Boolean): FavouriteCategory {
val entity = FavouriteCategoryEntity(
title = title,
createdAt = System.currentTimeMillis(),
sortKey = db.favouriteCategoriesDao.getNextSortKey(),
categoryId = 0,
order = sortOrder.name,
track = isTrackerEnabled,
)
val id = db.favouriteCategoriesDao.insert(entity)
val category = entity.toFavouriteCategory(id)
channels.createChannel(category)
return category
}
suspend fun updateCategory(id: Long, title: String, sortOrder: SortOrder, isTrackerEnabled: Boolean) {
db.favouriteCategoriesDao.update(id, title, sortOrder.name, isTrackerEnabled)
}
suspend fun addCategory(title: String): FavouriteCategory {
val entity = FavouriteCategoryEntity(
title = title,

View File

@@ -16,12 +16,12 @@ 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.core.ui.titleRes
import org.koitharu.kotatsu.databinding.FragmentFavouritesBinding
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
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.main.ui.AppBarOwner
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.measureHeight
@@ -134,28 +134,6 @@ class FavouritesContainerFragment :
viewModel.deleteCategory(category.id)
}
override fun onRenameCategory(category: FavouriteCategory, newName: String) {
viewModel.renameCategory(category.id, newName)
}
override fun onCreateCategory(name: String) {
viewModel.createCategory(name)
}
private fun createOrderSubmenu(menu: Menu, category: FavouriteCategory) {
val submenu = menu.findItem(R.id.action_order)?.subMenu ?: return
for ((i, item) in CategoriesActivity.SORT_ORDERS.withIndex()) {
val menuItem = submenu.add(R.id.group_order, Menu.NONE, i, item.titleRes)
menuItem.isCheckable = true
menuItem.isChecked = item == category.order
}
submenu.setGroupCheckable(R.id.group_order, true, true)
menu.findItem(R.id.action_tracking)?.run {
isVisible = viewModel.isFavouritesTrackerEnabled
isChecked = category.isTrackingEnabled
}
}
private fun TabLayout.setTabsEnabled(enabled: Boolean) {
val tabStrip = getChildAt(0) as? ViewGroup ?: return
for (tab in tabStrip.children) {
@@ -166,19 +144,11 @@ class FavouritesContainerFragment :
private fun showCategoryMenu(tabView: View, category: FavouriteCategory) {
val menu = PopupMenu(tabView.context, tabView)
menu.inflate(R.menu.popup_category)
createOrderSubmenu(menu.menu, category)
menu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_remove -> editDelegate.deleteCategory(category)
R.id.action_rename -> editDelegate.renameCategory(category)
R.id.action_create -> editDelegate.createCategory()
R.id.action_tracking -> viewModel.setCategoryTracking(category.id, !category.isTrackingEnabled)
R.id.action_order -> return@setOnMenuItemClickListener false
else -> {
val order = CategoriesActivity.SORT_ORDERS.getOrNull(it.order)
?: return@setOnMenuItemClickListener false
viewModel.setCategoryOrder(category.id, order)
}
R.id.action_edit -> FavouritesCategoryEditActivity.newIntent(tabView.context, category.id)
else -> return@setOnMenuItemClickListener false
}
true
}
@@ -190,7 +160,7 @@ class FavouritesContainerFragment :
menu.inflate(R.menu.popup_category_all)
menu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_create -> editDelegate.createCategory()
R.id.action_create -> FavouritesCategoryEditActivity.newIntent(requireContext())
R.id.action_hide -> viewModel.setAllCategoriesVisible(false)
}
true

View File

@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.favourites.ui.categories
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
@@ -19,9 +18,9 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.ui.titleRes
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.measureHeight
@@ -57,24 +56,17 @@ class CategoriesActivity :
override fun onClick(v: View) {
when (v.id) {
R.id.fab_add -> editDelegate.createCategory()
R.id.fab_add -> startActivity(FavouritesCategoryEditActivity.newIntent(this))
}
}
override fun onItemClick(item: FavouriteCategory, view: View) {
val menu = PopupMenu(view.context, view)
menu.inflate(R.menu.popup_category)
prepareCategoryMenu(menu.menu, item)
menu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.action_remove -> editDelegate.deleteCategory(item)
R.id.action_rename -> editDelegate.renameCategory(item)
R.id.action_tracking -> viewModel.setCategoryTracking(item.id, !item.isTrackingEnabled)
R.id.action_order -> return@setOnMenuItemClickListener false
else -> {
val order = SORT_ORDERS.getOrNull(menuItem.order) ?: return@setOnMenuItemClickListener false
viewModel.setCategoryOrder(item.id, order)
}
R.id.action_edit -> startActivity(FavouritesCategoryEditActivity.newIntent(this, item.id))
}
true
}
@@ -118,33 +110,6 @@ class CategoriesActivity :
viewModel.deleteCategory(category.id)
}
override fun onRenameCategory(category: FavouriteCategory, newName: String) {
viewModel.renameCategory(category.id, newName)
}
override fun onCreateCategory(name: String) {
viewModel.createCategory(name)
}
private fun prepareCategoryMenu(menu: Menu, category: FavouriteCategory) {
val submenu = menu.findItem(R.id.action_order)?.subMenu ?: return
for ((i, item) in SORT_ORDERS.withIndex()) {
val menuItem = submenu.add(
R.id.group_order,
Menu.NONE,
i,
item.titleRes
)
menuItem.isCheckable = true
menuItem.isChecked = item == category.order
}
submenu.setGroupCheckable(R.id.group_order, true, true)
menu.findItem(R.id.action_tracking)?.run {
isVisible = viewModel.isFavouritesTrackerEnabled
isChecked = category.isTrackingEnabled
}
}
private inner class ReorderHelperCallback : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.DOWN or ItemTouchHelper.UP, 0
) {

View File

@@ -1,15 +1,10 @@
package org.koitharu.kotatsu.favourites.ui.categories
import android.content.Context
import android.text.InputType
import android.widget.Toast
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.dialog.TextInputDialog
import org.koitharu.kotatsu.core.model.FavouriteCategory
private const val MAX_TITLE_LENGTH = 24
class CategoriesEditDelegate(
private val context: Context,
private val callback: CategoriesEditCallback
@@ -26,49 +21,8 @@ class CategoriesEditDelegate(
.show()
}
fun renameCategory(category: FavouriteCategory) {
TextInputDialog.Builder(context)
.setTitle(R.string.rename)
.setText(category.title)
.setHint(R.string.enter_category_name)
.setInputType(InputType.TYPE_TEXT_VARIATION_PERSON_NAME or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES)
.setNegativeButton(android.R.string.cancel)
.setMaxLength(MAX_TITLE_LENGTH, false)
.setPositiveButton(R.string.rename) { _, name ->
val trimmed = name.trim()
if (trimmed.isEmpty()) {
Toast.makeText(context, R.string.error_empty_name, Toast.LENGTH_SHORT).show()
} else {
callback.onRenameCategory(category, name)
}
}.create()
.show()
}
fun createCategory() {
TextInputDialog.Builder(context)
.setTitle(R.string.add_new_category)
.setHint(R.string.enter_category_name)
.setInputType(InputType.TYPE_TEXT_VARIATION_PERSON_NAME or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES)
.setNegativeButton(android.R.string.cancel)
.setMaxLength(MAX_TITLE_LENGTH, false)
.setPositiveButton(R.string.add) { _, name ->
val trimmed = name.trim()
if (trimmed.isEmpty()) {
Toast.makeText(context, R.string.error_empty_name, Toast.LENGTH_SHORT).show()
} else {
callback.onCreateCategory(trimmed)
}
}.create()
.show()
}
interface CategoriesEditCallback {
fun onDeleteCategory(category: FavouriteCategory)
fun onRenameCategory(category: FavouriteCategory, newName: String)
fun onCreateCategory(name: String)
}
}

View File

@@ -1,7 +1,6 @@
package org.koitharu.kotatsu.favourites.ui.categories
import androidx.lifecycle.viewModelScope
import java.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
@@ -10,8 +9,8 @@ import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import java.util.*
class FavouritesCategoriesViewModel(
private val repository: FavouritesRepository,
@@ -34,39 +33,12 @@ class FavouritesCategoriesViewModel(
mapCategories(list, showAll, showAll)
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
val isFavouritesTrackerEnabled: Boolean
get() = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources
fun createCategory(name: String) {
launchJob {
repository.addCategory(name)
}
}
fun renameCategory(id: Long, name: String) {
launchJob {
repository.renameCategory(id, name)
}
}
fun deleteCategory(id: Long) {
launchJob {
repository.removeCategory(id)
}
}
fun setCategoryOrder(id: Long, order: SortOrder) {
launchJob {
repository.setCategoryOrder(id, order)
}
}
fun setCategoryTracking(id: Long, isEnabled: Boolean) {
launchJob {
repository.setCategoryTracking(id, isEnabled)
}
}
fun setAllCategoriesVisible(isVisible: Boolean) {
settings.isAllFavouritesVisible = isVisible
}

View File

@@ -0,0 +1,147 @@
package org.koitharu.kotatsu.favourites.ui.categories.edit
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.ui.titleRes
import org.koitharu.kotatsu.databinding.ActivityCategoryEditBinding
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>(), AdapterView.OnItemClickListener {
private val viewModel by viewModel<FavouritesCategoryEditViewModel> {
parametersOf(intent.getLongExtra(EXTRA_ID, NO_ID))
}
private var selectedSortOrder: SortOrder? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityCategoryEditBinding.inflate(layoutInflater))
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(com.google.android.material.R.drawable.abc_ic_clear_material)
}
initSortSpinner()
viewModel.onSaved.observe(this) { finishAfterTransition() }
viewModel.category.observe(this, ::onCategoryChanged)
viewModel.isLoading.observe(this, ::onLoadingStateChanged)
viewModel.onError.observe(this, ::onError)
viewModel.isTrackerEnabled.observe(this) {
binding.switchTracker.isVisible = it
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putSerializable(KEY_SORT_ORDER, selectedSortOrder)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
val order = savedInstanceState.getSerializable(KEY_SORT_ORDER)
if (order != null && order is SortOrder) {
selectedSortOrder = order
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.opt_config, menu)
menu.findItem(R.id.action_done)?.setTitle(R.string.save)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.action_done -> {
viewModel.save(
title = binding.editName.text?.toString().orEmpty(),
sortOrder = getSelectedSortOrder(),
isTrackerEnabled = binding.switchTracker.isChecked,
)
true
}
else -> super.onOptionsItemSelected(item)
}
override fun onWindowInsetsChanged(insets: Insets) {
binding.scrollView.updatePadding(
left = insets.left,
right = insets.right,
bottom = insets.bottom,
)
binding.toolbar.updatePadding(
top = insets.top,
)
}
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
selectedSortOrder = CategoriesActivity.SORT_ORDERS.getOrNull(position)
}
private fun onCategoryChanged(category: FavouriteCategory?) {
setTitle(if (category == null) R.string.create_category else R.string.edit_category)
if (selectedSortOrder != null) {
return
}
binding.editName.setText(category?.title)
selectedSortOrder = category?.order
val sortText = getString((category?.order ?: SortOrder.NEWEST).titleRes)
binding.editSort.setText(sortText, false)
binding.switchTracker.isChecked = category?.isTrackingEnabled ?: true
}
private fun onError(e: Throwable) {
binding.textViewError.text = e.getDisplayMessage(resources)
binding.textViewError.isVisible = true
}
private fun onLoadingStateChanged(isLoading: Boolean) {
binding.editSort.isEnabled = !isLoading
binding.editName.isEnabled = !isLoading
binding.switchTracker.isEnabled = !isLoading
if (isLoading) {
binding.textViewError.isVisible = false
}
}
private fun initSortSpinner() {
val entries = CategoriesActivity.SORT_ORDERS.map { getString(it.titleRes) }
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, entries)
binding.editSort.setAdapter(adapter)
binding.editSort.onItemClickListener = this
}
private fun getSelectedSortOrder(): SortOrder {
selectedSortOrder?.let { return it }
val entries = CategoriesActivity.SORT_ORDERS.map { getString(it.titleRes) }
val index = entries.indexOf(binding.editSort.text.toString())
return CategoriesActivity.SORT_ORDERS.getOrNull(index) ?: SortOrder.NEWEST
}
companion object {
private const val EXTRA_ID = "id"
private const val KEY_SORT_ORDER = "sort"
private const val NO_ID = -1L
fun newIntent(context: Context, id: Long = NO_ID): Intent {
return Intent(context, FavouritesCategoryEditActivity::class.java)
.putExtra(EXTRA_ID, id)
}
}
}

View File

@@ -0,0 +1,53 @@
package org.koitharu.kotatsu.favourites.ui.categories.edit
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.SingleLiveEvent
private const val NO_ID = -1L
class FavouritesCategoryEditViewModel(
private val categoryId: Long,
private val repository: FavouritesRepository,
private val settings: AppSettings,
) : BaseViewModel() {
val onSaved = SingleLiveEvent<Unit>()
val category = MutableLiveData<FavouriteCategory?>()
val isTrackerEnabled = liveData(viewModelScope.coroutineContext + Dispatchers.Default) {
emit(settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources)
}
init {
launchLoadingJob {
category.value = if (categoryId != NO_ID) {
repository.getCategory(categoryId)
} else {
null
}
}
}
fun save(
title: String,
sortOrder: SortOrder,
isTrackerEnabled: Boolean,
) {
launchLoadingJob {
if (categoryId == NO_ID) {
repository.createCategory(title, sortOrder, isTrackerEnabled)
} else {
repository.updateCategory(categoryId, title, sortOrder, isTrackerEnabled)
}
onSaved.call(Unit)
}
}
}

View File

@@ -17,6 +17,7 @@ import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
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.select.adapter.MangaCategoriesAdapter
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
import org.koitharu.kotatsu.parsers.model.Manga
@@ -34,9 +35,6 @@ class FavouriteCategoriesDialog :
}
private var adapter: MangaCategoriesAdapter? = null
private val editDelegate by lazy(LazyThreadSafetyMode.NONE) {
CategoriesEditDelegate(requireContext(), this@FavouriteCategoriesDialog)
}
override fun onInflateView(
inflater: LayoutInflater,
@@ -61,7 +59,7 @@ class FavouriteCategoriesDialog :
override fun onMenuItemClick(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_create -> {
editDelegate.createCategory()
FavouritesCategoryEditActivity.newIntent(requireContext())
true
}
else -> false
@@ -74,12 +72,6 @@ class FavouriteCategoriesDialog :
override fun onDeleteCategory(category: FavouriteCategory) = Unit
override fun onRenameCategory(category: FavouriteCategory, newName: String) = Unit
override fun onCreateCategory(name: String) {
viewModel.createCategory(name)
}
private fun onContentChanged(categories: List<MangaCategoryItem>) {
adapter?.items = categories
}

View File

@@ -38,12 +38,6 @@ class MangaCategoriesViewModel(
}
}
fun createCategory(name: String) {
launchJob(Dispatchers.Default) {
favouritesRepository.addCategory(name)
}
}
private fun observeCategoriesIds() = if (manga.size == 1) {
// Fast path
favouritesRepository.observeCategoriesIds(manga[0].id)

View File

@@ -1,11 +1,17 @@
package org.koitharu.kotatsu.favourites.ui.list
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.appcompat.view.ActionMode
import androidx.core.view.iterator
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.titleRes
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.ext.withArgs
@@ -17,12 +23,54 @@ class FavouritesListFragment : MangaListFragment() {
}
private val categoryId: Long
get() = arguments?.getLong(ARG_CATEGORY_ID) ?: 0L
get() = arguments?.getLong(ARG_CATEGORY_ID) ?: NO_ID
override val isSwipeRefreshEnabled = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.sortOrder.observe(viewLifecycleOwner) { activity?.invalidateOptionsMenu() }
}
override fun onScrolledToEnd() = Unit
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
if (categoryId != NO_ID) {
inflater.inflate(R.menu.opt_favourites_list, menu)
menu.findItem(R.id.action_order)?.subMenu?.let { submenu ->
for ((i, item) in CategoriesActivity.SORT_ORDERS.withIndex()) {
val menuItem = submenu.add(R.id.group_order, Menu.NONE, i, item.titleRes)
menuItem.isCheckable = true
}
submenu.setGroupCheckable(R.id.group_order, true, true)
}
}
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
menu.findItem(R.id.action_order)?.subMenu?.let { submenu ->
val selectedOrder = viewModel.sortOrder.value
for (item in submenu) {
val order = CategoriesActivity.SORT_ORDERS.getOrNull(item.order)
item.isChecked = order == selectedOrder
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when {
item.itemId == R.id.action_order -> false
item.groupId == R.id.group_order -> {
val order = CategoriesActivity.SORT_ORDERS.getOrNull(item.order) ?: return false
viewModel.setSortOrder(order)
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.mode_favourites, menu)
return super.onCreateActionMode(mode, menu)
@@ -48,6 +96,7 @@ class FavouritesListFragment : MangaListFragment() {
companion object {
const val NO_ID = 0L
private const val ARG_CATEGORY_ID = "category_id"
fun newInstance(categoryId: Long) = FavouritesListFragment().withArgs(1) {

View File

@@ -1,12 +1,16 @@
package org.koitharu.kotatsu.favourites.ui.list
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
import org.koitharu.kotatsu.list.domain.CountersProvider
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
@@ -24,8 +28,16 @@ class FavouritesListViewModel(
settings: AppSettings,
) : MangaListViewModel(settings), CountersProvider {
var sortOrder: LiveData<SortOrder> = if (categoryId == NO_ID) {
MutableLiveData(null)
} else {
repository.observeCategory(categoryId)
.map { it.order }
.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
}
override val content = combine(
if (categoryId == 0L) {
if (categoryId == NO_ID) {
repository.observeAll(SortOrder.NEWEST)
} else {
repository.observeAll(categoryId)
@@ -37,7 +49,7 @@ class FavouritesListViewModel(
EmptyState(
icon = R.drawable.ic_heart_outline,
textPrimary = R.string.text_empty_holder_primary,
textSecondary = if (categoryId == 0L) {
textSecondary = if (categoryId == NO_ID) {
R.string.you_have_not_favourites_yet
} else {
R.string.favourites_category_empty
@@ -60,7 +72,7 @@ class FavouritesListViewModel(
return
}
launchJob {
if (categoryId == 0L) {
if (categoryId == NO_ID) {
repository.removeFromFavourites(ids)
} else {
repository.removeFromCategory(categoryId, ids)
@@ -68,6 +80,15 @@ class FavouritesListViewModel(
}
}
fun setSortOrder(order: SortOrder) {
if (categoryId == NO_ID) {
return
}
launchJob {
repository.setCategoryOrder(categoryId, order)
}
}
override suspend fun getCounter(mangaId: Long): Int {
return trackingRepository.getNewChaptersCount(mangaId)
}

View File

@@ -62,6 +62,7 @@
android:layout_height="match_parent"
android:background="@null"
android:drawablePadding="16dp"
android:layout_marginEnd="4dp"
android:gravity="center_vertical"
android:hint="@string/search_manga"
android:imeOptions="actionSearch"

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:overScrollMode="ifContentScrolls">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_name"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/name" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_sort"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:endIconMode="dropdown_menu">
<AutoCompleteTextView
android:id="@+id/edit_sort"
style="?editTextStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/sort_order"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_tracker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/check_for_new_chapters"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/textView_error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textColor="?colorError"
android:visibility="gone"
tools:text="@tools:sample/lorem[4]"
tools:visibility="visible" />
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -51,7 +51,7 @@
style="@style/Widget.Kotatsu.SearchView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="2dp"
android:layout_marginEnd="4dp"
android:background="@null"
android:gravity="center_vertical"
android:hint="@string/search_manga"

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_order"
android:orderInCategory="30"
android:title="@string/sort_order">
<menu>
<group
android:id="@+id/group_order"
android:checkableBehavior="single" />
</menu>
</item>
</menu>

View File

@@ -7,25 +7,7 @@
android:title="@string/remove" />
<item
android:id="@+id/action_rename"
android:title="@string/rename" />
<item
android:id="@+id/action_order"
android:title="@string/sort_order">
<menu>
<group
android:id="@+id/group_order"
android:checkableBehavior="single" />
</menu>
</item>
<item
android:id="@+id/action_tracking"
android:title="@string/check_for_new_chapters"
android:checkable="true" />
android:id="@+id/action_edit"
android:title="@string/edit" />
</menu>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Kotatsu.DialogWhenLarge">
<item name="windowFixedWidthMajor">@dimen/abc_dialog_fixed_width_major</item>
<item name="windowFixedWidthMinor">@dimen/abc_dialog_fixed_width_minor</item>
<item name="windowFixedHeightMajor">@dimen/abc_dialog_fixed_height_major</item>
<item name="windowFixedHeightMinor">@dimen/abc_dialog_fixed_height_minor</item>
<item name="android:windowElevation">@dimen/abc_floating_window_z</item>
<item name="android:colorBackground">?attr/colorBackgroundFloating</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowFrame">@null</item>
<item name="android:windowTitleStyle">@style/RtlOverlay.DialogWindowTitle.AppCompat</item>
<item name="android:windowTitleBackgroundStyle">@style/Base.DialogWindowTitleBackground.AppCompat</item>
<item name="android:windowBackground">@drawable/abc_dialog_material_background</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowAnimationStyle">@style/Animation.AppCompat.Dialog</item>
<item name="android:windowSoftInputMode">stateUnspecified|adjustResize</item>
<item name="windowActionBar">false</item>
<item name="windowActionModeOverlay">true</item>
<item name="listPreferredItemPaddingLeft">24dip</item>
<item name="listPreferredItemPaddingRight">24dip</item>
<item name="android:listDivider">@null</item>
<item name="android:buttonBarStyle">@style/Widget.AppCompat.ButtonBar.AlertDialog</item>
<item name="android:borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
<item name="android:windowCloseOnTouchOutside">true</item>
</style>
</resources>

View File

@@ -281,4 +281,7 @@
<string name="show_notification_new_chapters_on">Вы будете получать уведомления об обновлении манги, которую Вы читаете</string>
<string name="show_notification_new_chapters_off">Вы не будете получать уведомления, но новые главы будут отображаться в списке</string>
<string name="notifications_enable">Включить уведомления</string>
<string name="name">Название</string>
<string name="edit">Изменить</string>
<string name="edit_category">Изменить категорию</string>
</resources>

View File

@@ -284,4 +284,7 @@
<string name="show_notification_new_chapters_on">You will receive notifications about updates of manga you are reading</string>
<string name="show_notification_new_chapters_off">You will not receive notifications but new chapters will be highlighted in the lists</string>
<string name="notifications_enable">Enable notifications</string>
<string name="name">Name</string>
<string name="edit">Edit</string>
<string name="edit_category">Edit category</string>
</resources>

View File

@@ -119,6 +119,8 @@
<style name="TextAppearance.Kotatsu.Preference.Secondary" parent="TextAppearance.Material3.BodySmall" />
<style name="TextAppearance.Kotatsu.Menu" parent="TextAppearance.Material3.BodyLarge" />
<style name="TextAppearance.Kotatsu.SectionHeader" parent="TextAppearance.Material3.LabelLarge">
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>

View File

@@ -71,7 +71,8 @@
<item name="recyclerViewStyle">@style/Widget.Kotatsu.RecyclerView</item>
<item name="listItemTextViewStyle">@style/Widget.Kotatsu.ListItemTextView</item>
<!-- Preference text appearance -->
<!-- Text appearance -->
<item name="actionMenuTextAppearance">@style/TextAppearance.Kotatsu.Menu</item>
<item name="android:textAppearanceListItem">?attr/textAppearanceBodyLarge</item>
<item name="android:textAppearanceListItemSecondary">@style/TextAppearance.Kotatsu.Preference.Secondary</item>
@@ -80,6 +81,8 @@
<!--== Default Theme ==-->
<style name="Theme.Kotatsu" parent="Base.Theme.Kotatsu" />
<style name="Theme.Kotatsu.DialogWhenLarge" />
<!-- Monet theme only support S+ -->
<style name="Theme.Kotatsu.Monet" />