#8 Configure sort order for each favourites category

This commit is contained in:
Koitharu
2021-08-04 08:32:20 +03:00
parent 6f7efa9e26
commit fbd0f25b8f
19 changed files with 201 additions and 45 deletions

View File

@@ -70,19 +70,19 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
implementation 'androidx.core:core-ktx:1.6.0' implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.activity:activity-ktx:1.2.3' implementation 'androidx.activity:activity-ktx:1.3.0'
implementation 'androidx.fragment:fragment-ktx:1.3.5' implementation 'androidx.fragment:fragment-ktx:1.3.6'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-service:2.3.1' implementation 'androidx.lifecycle:lifecycle-service:2.3.1'
implementation 'androidx.lifecycle:lifecycle-process:2.3.1' implementation 'androidx.lifecycle:lifecycle-process:2.3.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01' implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.work:work-runtime-ktx:2.6.0-beta01' implementation 'androidx.work:work-runtime-ktx:2.6.0-beta02'
implementation 'com.google.android.material:material:1.4.0' implementation 'com.google.android.material:material:1.4.0'
//noinspection LifecycleAnnotationProcessorWithJava8 //noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.3.1' kapt 'androidx.lifecycle:lifecycle-compiler:2.3.1'

View File

@@ -118,6 +118,7 @@ class BackupRepository(private val db: MangaDatabase) {
jo.put("created_at", createdAt) jo.put("created_at", createdAt)
jo.put("sort_key", sortKey) jo.put("sort_key", sortKey)
jo.put("title", title) jo.put("title", title)
jo.put("order", order)
return jo return jo
} }

View File

@@ -5,6 +5,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.history.data.HistoryEntity import org.koitharu.kotatsu.history.data.HistoryEntity
@@ -101,7 +102,8 @@ class RestoreRepository(private val db: MangaDatabase) {
categoryId = json.getInt("category_id"), categoryId = json.getInt("category_id"),
createdAt = json.getLong("created_at"), createdAt = json.getLong("created_at"),
sortKey = json.getInt("sort_key"), sortKey = json.getInt("sort_key"),
title = json.getString("title") title = json.getString("title"),
order = json.getStringOrNull("order") ?: SortOrder.NEWEST.name,
) )
private fun parseFavourite(json: JSONObject) = FavouriteEntity( private fun parseFavourite(json: JSONObject) = FavouriteEntity(

View File

@@ -20,6 +20,7 @@ val databaseModule
Migration5To6(), Migration5To6(),
Migration6To7(), Migration6To7(),
Migration7To8(), Migration7To8(),
Migration8To9(),
).addCallback( ).addCallback(
DatabasePrePopulateCallback(androidContext().resources) DatabasePrePopulateCallback(androidContext().resources)
).build() ).build()

View File

@@ -16,7 +16,7 @@ import org.koitharu.kotatsu.history.data.HistoryEntity
MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class, MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class,
FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class, FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class,
TrackEntity::class, TrackLogEntity::class, SuggestionEntity::class TrackEntity::class, TrackLogEntity::class, SuggestionEntity::class
], version = 8 ], version = 9
) )
abstract class MangaDatabase : RoomDatabase() { abstract class MangaDatabase : RoomDatabase() {

View File

@@ -0,0 +1,12 @@
package org.koitharu.kotatsu.core.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import org.koitharu.kotatsu.core.model.SortOrder
class Migration8To9 : Migration(8, 9) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE favourite_categories ADD COLUMN `order` TEXT NOT NULL DEFAULT ${SortOrder.NEWEST.name}")
}
}

View File

@@ -9,5 +9,6 @@ data class FavouriteCategory(
val id: Long, val id: Long,
val title: String, val title: String,
val sortKey: Int, val sortKey: Int,
val createdAt: Date val order: SortOrder,
val createdAt: Date,
) : Parcelable ) : Parcelable

View File

@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.favourites.data
import androidx.room.* import androidx.room.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.koitharu.kotatsu.core.model.FavouriteCategory
@Dao @Dao
abstract class FavouriteCategoriesDao { abstract class FavouriteCategoriesDao {
@@ -13,6 +12,9 @@ abstract class FavouriteCategoriesDao {
@Query("SELECT * FROM favourite_categories ORDER BY sort_key") @Query("SELECT * FROM favourite_categories ORDER BY sort_key")
abstract fun observeAll(): Flow<List<FavouriteCategoryEntity>> abstract fun observeAll(): Flow<List<FavouriteCategoryEntity>>
@Query("SELECT * FROM favourite_categories WHERE category_id = :id")
abstract fun observe(id: Long): Flow<FavouriteCategoryEntity>
@Insert(onConflict = OnConflictStrategy.ABORT) @Insert(onConflict = OnConflictStrategy.ABORT)
abstract suspend fun insert(category: FavouriteCategoryEntity): Long abstract suspend fun insert(category: FavouriteCategoryEntity): Long
@@ -23,10 +25,13 @@ abstract class FavouriteCategoriesDao {
abstract suspend fun delete(id: Long) abstract suspend fun delete(id: Long)
@Query("UPDATE favourite_categories SET title = :title WHERE category_id = :id") @Query("UPDATE favourite_categories SET title = :title WHERE category_id = :id")
abstract suspend fun update(id: Long, title: String) abstract suspend fun updateTitle(id: Long, title: String)
@Query("UPDATE favourite_categories SET `order` = :order WHERE category_id = :id")
abstract suspend fun updateOrder(id: Long, order: String)
@Query("UPDATE favourite_categories SET sort_key = :sortKey WHERE category_id = :id") @Query("UPDATE favourite_categories SET sort_key = :sortKey WHERE category_id = :id")
abstract suspend fun update(id: Long, sortKey: Int) abstract suspend fun updateSortKey(id: Long, sortKey: Int)
@Query("SELECT MAX(sort_key) FROM favourite_categories") @Query("SELECT MAX(sort_key) FROM favourite_categories")
protected abstract suspend fun getMaxSortKey(): Int? protected abstract suspend fun getMaxSortKey(): Int?

View File

@@ -4,6 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.SortOrder
import java.util.* import java.util.*
@Entity(tableName = "favourite_categories") @Entity(tableName = "favourite_categories")
@@ -12,13 +13,15 @@ data class FavouriteCategoryEntity(
@ColumnInfo(name = "category_id") val categoryId: Int, @ColumnInfo(name = "category_id") val categoryId: Int,
@ColumnInfo(name = "created_at") val createdAt: Long, @ColumnInfo(name = "created_at") val createdAt: Long,
@ColumnInfo(name = "sort_key") val sortKey: Int, @ColumnInfo(name = "sort_key") val sortKey: Int,
@ColumnInfo(name = "title") val title: String @ColumnInfo(name = "title") val title: String,
@ColumnInfo(name = "order") val order: String,
) { ) {
fun toFavouriteCategory(id: Long? = null) = FavouriteCategory( fun toFavouriteCategory(id: Long? = null) = FavouriteCategory(
id = id ?: categoryId.toLong(), id = id ?: categoryId.toLong(),
title = title, title = title,
sortKey = sortKey, sortKey = sortKey,
createdAt = Date(createdAt) order = SortOrder.values().find { x -> x.name == order } ?: SortOrder.NEWEST,
createdAt = Date(createdAt),
) )
} }

View File

@@ -1,8 +1,11 @@
package org.koitharu.kotatsu.favourites.data package org.koitharu.kotatsu.favourites.data
import androidx.room.* import androidx.room.*
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.model.SortOrder
@Dao @Dao
abstract class FavouritesDao { abstract class FavouritesDao {
@@ -11,9 +14,13 @@ abstract class FavouritesDao {
@Query("SELECT * FROM favourites GROUP BY manga_id ORDER BY created_at DESC") @Query("SELECT * FROM favourites GROUP BY manga_id ORDER BY created_at DESC")
abstract suspend fun findAll(): List<FavouriteManga> abstract suspend fun findAll(): List<FavouriteManga>
@Transaction fun observeAll(order: SortOrder): Flow<List<FavouriteManga>> {
@Query("SELECT * FROM favourites GROUP BY manga_id ORDER BY created_at DESC") val orderBy = getOrderBy(order)
abstract fun observeAll(): Flow<List<FavouriteManga>> val query = SimpleSQLiteQuery(
"SELECT * FROM favourites LEFT JOIN manga ON favourites.manga_id = manga.manga_id GROUP BY favourites.manga_id ORDER BY $orderBy",
)
return observeAllRaw(query)
}
@Transaction @Transaction
@Query("SELECT * FROM favourites GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit OFFSET :offset") @Query("SELECT * FROM favourites GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit OFFSET :offset")
@@ -23,9 +30,14 @@ abstract class FavouritesDao {
@Query("SELECT * FROM favourites WHERE category_id = :categoryId GROUP BY manga_id ORDER BY created_at DESC") @Query("SELECT * FROM favourites WHERE category_id = :categoryId GROUP BY manga_id ORDER BY created_at DESC")
abstract suspend fun findAll(categoryId: Long): List<FavouriteManga> abstract suspend fun findAll(categoryId: Long): List<FavouriteManga>
@Transaction fun observeAll(categoryId: Long, order: SortOrder): Flow<List<FavouriteManga>> {
@Query("SELECT * FROM favourites WHERE category_id = :categoryId GROUP BY manga_id ORDER BY created_at DESC") val orderBy = getOrderBy(order)
abstract fun observeAll(categoryId: Long): Flow<List<FavouriteManga>> val query = SimpleSQLiteQuery(
"SELECT * FROM favourites LEFT JOIN manga ON favourites.manga_id = manga.manga_id WHERE category_id = ? GROUP BY favourites.manga_id ORDER BY $orderBy",
arrayOf<Any>(categoryId),
)
return observeAllRaw(query)
}
@Transaction @Transaction
@Query("SELECT * FROM favourites WHERE category_id = :categoryId GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit OFFSET :offset") @Query("SELECT * FROM favourites WHERE category_id = :categoryId GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit OFFSET :offset")
@@ -63,4 +75,16 @@ abstract class FavouritesDao {
insert(entity) insert(entity)
} }
} }
@Transaction
@RawQuery(observedEntities = [FavouriteEntity::class])
protected abstract fun observeAllRaw(query: SupportSQLiteQuery): Flow<List<FavouriteManga>>
private fun getOrderBy(sortOrder: SortOrder) = when(sortOrder) {
SortOrder.RATING -> "rating DESC"
SortOrder.NEWEST,
SortOrder.UPDATED -> "created_at DESC"
SortOrder.ALPHABETICAL -> "title ASC"
else -> throw IllegalArgumentException("Sort order $sortOrder is not supported")
}
} }

View File

@@ -3,12 +3,14 @@ package org.koitharu.kotatsu.favourites.domain
import androidx.room.withTransaction import androidx.room.withTransaction
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.utils.ext.mapItems import org.koitharu.kotatsu.utils.ext.mapItems
@@ -21,26 +23,26 @@ class FavouritesRepository(private val db: MangaDatabase) {
return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) }
} }
fun observeAll(): Flow<List<Manga>> { fun observeAll(order: SortOrder): Flow<List<Manga>> {
return db.favouritesDao.observeAll() return db.favouritesDao.observeAll(order)
.mapItems { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } .mapItems { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) }
} }
suspend fun getAllManga(offset: Int): List<Manga> {
val entities = db.favouritesDao.findAll(offset, 20)
return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) }
}
suspend fun getManga(categoryId: Long): List<Manga> { suspend fun getManga(categoryId: Long): List<Manga> {
val entities = db.favouritesDao.findAll(categoryId) val entities = db.favouritesDao.findAll(categoryId)
return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) }
} }
fun observeAll(categoryId: Long): Flow<List<Manga>> { fun observeAll(categoryId: Long, order: SortOrder): Flow<List<Manga>> {
return db.favouritesDao.observeAll(categoryId) return db.favouritesDao.observeAll(categoryId, order)
.mapItems { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } .mapItems { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) }
} }
fun observeAll(categoryId: Long): Flow<List<Manga>> {
return observeOrder(categoryId)
.flatMapLatest { order -> observeAll(categoryId, order) }
}
suspend fun getManga(categoryId: Long, offset: Int): List<Manga> { suspend fun getManga(categoryId: Long, offset: Int): List<Manga> {
val entities = db.favouritesDao.findAll(categoryId, offset, 20) val entities = db.favouritesDao.findAll(categoryId, offset, 20)
return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) }
@@ -77,25 +79,30 @@ class FavouritesRepository(private val db: MangaDatabase) {
title = title, title = title,
createdAt = System.currentTimeMillis(), createdAt = System.currentTimeMillis(),
sortKey = db.favouriteCategoriesDao.getNextSortKey(), sortKey = db.favouriteCategoriesDao.getNextSortKey(),
categoryId = 0 categoryId = 0,
order = SortOrder.UPDATED.name,
) )
val id = db.favouriteCategoriesDao.insert(entity) val id = db.favouriteCategoriesDao.insert(entity)
return entity.toFavouriteCategory(id) return entity.toFavouriteCategory(id)
} }
suspend fun renameCategory(id: Long, title: String) { suspend fun renameCategory(id: Long, title: String) {
db.favouriteCategoriesDao.update(id, title) db.favouriteCategoriesDao.updateTitle(id, title)
} }
suspend fun removeCategory(id: Long) { suspend fun removeCategory(id: Long) {
db.favouriteCategoriesDao.delete(id) db.favouriteCategoriesDao.delete(id)
} }
suspend fun setCategoryOrder(id: Long, order: SortOrder) {
db.favouriteCategoriesDao.updateOrder(id, order.name)
}
suspend fun reorderCategories(orderedIds: List<Long>) { suspend fun reorderCategories(orderedIds: List<Long>) {
val dao = db.favouriteCategoriesDao val dao = db.favouriteCategoriesDao
db.withTransaction { db.withTransaction {
for ((i, id) in orderedIds.withIndex()) { for ((i, id) in orderedIds.withIndex()) {
dao.update(id, i) dao.updateSortKey(id, i)
} }
} }
} }
@@ -117,4 +124,10 @@ class FavouritesRepository(private val db: MangaDatabase) {
suspend fun removeFromFavourites(manga: Manga) { suspend fun removeFromFavourites(manga: Manga) {
db.favouritesDao.delete(manga.id) db.favouritesDao.delete(manga.id)
} }
private fun observeOrder(categoryId: Long): Flow<SortOrder> {
return db.favouriteCategoriesDao.observe(categoryId)
.map { x -> SortOrder.values().find { it.name == x.order } ?: SortOrder.NEWEST }
.distinctUntilChanged()
}
} }

View File

@@ -11,6 +11,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.databinding.FragmentFavouritesBinding import org.koitharu.kotatsu.databinding.FragmentFavouritesBinding
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate
@@ -19,7 +20,6 @@ import org.koitharu.kotatsu.utils.RecycledViewPoolHolder
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.showPopupMenu import org.koitharu.kotatsu.utils.ext.showPopupMenu
import java.util.* import java.util.*
import kotlin.collections.ArrayList
class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(), class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
FavouritesTabLongClickListener, CategoriesEditDelegate.CategoriesEditCallback, FavouritesTabLongClickListener, CategoriesEditDelegate.CategoriesEditCallback,
@@ -100,11 +100,19 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
override fun onTabLongClick(tabView: View, category: FavouriteCategory): Boolean { override fun onTabLongClick(tabView: View, category: FavouriteCategory): Boolean {
val menuRes = if (category.id == 0L) R.menu.popup_category_empty else R.menu.popup_category val menuRes = if (category.id == 0L) R.menu.popup_category_empty else R.menu.popup_category
tabView.showPopupMenu(menuRes) { tabView.showPopupMenu(menuRes, { menu ->
createOrderSubmenu(menu, category)
}) {
when (it.itemId) { when (it.itemId) {
R.id.action_remove -> editDelegate.deleteCategory(category) R.id.action_remove -> editDelegate.deleteCategory(category)
R.id.action_rename -> editDelegate.renameCategory(category) R.id.action_rename -> editDelegate.renameCategory(category)
R.id.action_create -> editDelegate.createCategory() R.id.action_create -> editDelegate.createCategory()
R.id.action_order -> return@showPopupMenu false
else -> {
val order = CategoriesActivity.SORT_ORDERS.getOrNull(it.order)
?: return@showPopupMenu false
viewModel.setCategoryOrder(category.id, order)
}
} }
true true
} }
@@ -125,11 +133,26 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
private fun wrapCategories(categories: List<FavouriteCategory>): List<FavouriteCategory> { private fun wrapCategories(categories: List<FavouriteCategory>): List<FavouriteCategory> {
val data = ArrayList<FavouriteCategory>(categories.size + 1) val data = ArrayList<FavouriteCategory>(categories.size + 1)
data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, Date()) data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, SortOrder.NEWEST, Date())
data += categories data += categories
return data return data
} }
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)
}
companion object { companion object {
fun newInstance() = FavouritesContainerFragment() fun newInstance() = FavouritesContainerFragment()

View File

@@ -36,7 +36,7 @@ class FavouritesPagerAdapter(
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) { override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
val item = differ.currentList[position] val item = differ.currentList[position]
tab.text = item.title tab.text = item.title
tab.view.tag = item tab.view.tag = item.id
tab.view.setOnLongClickListener(this) tab.view.setOnLongClickListener(this)
} }
@@ -45,7 +45,8 @@ class FavouritesPagerAdapter(
} }
override fun onLongClick(v: View): Boolean { override fun onLongClick(v: View): Boolean {
val item = v.tag as? FavouriteCategory ?: return false val itemId = v.tag as? Long ?: return false
val item = differ.currentList.find { x -> x.id == itemId } ?: return false
return longClickListener.onTabLongClick(v, item) return longClickListener.onTabLongClick(v, item)
} }

View File

@@ -5,6 +5,7 @@ import android.content.Intent
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.Menu
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
@@ -20,6 +21,7 @@ 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.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.showPopupMenu import org.koitharu.kotatsu.utils.ext.showPopupMenu
@@ -61,10 +63,17 @@ class CategoriesActivity : BaseActivity<ActivityCategoriesBinding>(),
} }
override fun onItemClick(item: FavouriteCategory, view: View) { override fun onItemClick(item: FavouriteCategory, view: View) {
view.showPopupMenu(R.menu.popup_category) { view.showPopupMenu(R.menu.popup_category, { menu ->
createOrderSubmenu(menu, item)
}) {
when (it.itemId) { when (it.itemId) {
R.id.action_remove -> editDelegate.deleteCategory(item) R.id.action_remove -> editDelegate.deleteCategory(item)
R.id.action_rename -> editDelegate.renameCategory(item) R.id.action_rename -> editDelegate.renameCategory(item)
R.id.action_order -> return@showPopupMenu false
else -> {
val order = SORT_ORDERS.getOrNull(it.order) ?: return@showPopupMenu false
viewModel.setCategoryOrder(item.id, order)
}
} }
true true
} }
@@ -117,6 +126,21 @@ class CategoriesActivity : BaseActivity<ActivityCategoriesBinding>(),
viewModel.createCategory(name) viewModel.createCategory(name)
} }
private fun createOrderSubmenu(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)
}
private inner class ReorderHelperCallback : ItemTouchHelper.SimpleCallback( private inner class ReorderHelperCallback : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.DOWN or ItemTouchHelper.UP, 0 ItemTouchHelper.DOWN or ItemTouchHelper.UP, 0
) { ) {
@@ -145,6 +169,12 @@ class CategoriesActivity : BaseActivity<ActivityCategoriesBinding>(),
companion object { companion object {
val SORT_ORDERS = arrayOf(
SortOrder.ALPHABETICAL,
SortOrder.NEWEST,
SortOrder.RATING,
)
fun newIntent(context: Context) = Intent(context, CategoriesActivity::class.java) fun newIntent(context: Context) = Intent(context, CategoriesActivity::class.java)
} }
} }

View File

@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
class CategoriesAdapter( class CategoriesAdapter(
onItemClickListener: OnListItemClickListener<FavouriteCategory> onItemClickListener: OnListItemClickListener<FavouriteCategory>,
) : AsyncListDifferDelegationAdapter<FavouriteCategory>(DiffCallback()) { ) : AsyncListDifferDelegationAdapter<FavouriteCategory>(DiffCallback()) {
init { init {
@@ -20,12 +20,27 @@ class CategoriesAdapter(
private class DiffCallback : DiffUtil.ItemCallback<FavouriteCategory>() { private class DiffCallback : DiffUtil.ItemCallback<FavouriteCategory>() {
override fun areItemsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean { override fun areItemsTheSame(
oldItem: FavouriteCategory,
newItem: FavouriteCategory,
): Boolean {
return oldItem.id == newItem.id return oldItem.id == newItem.id
} }
override fun areContentsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean { override fun areContentsTheSame(
oldItem: FavouriteCategory,
newItem: FavouriteCategory,
): Boolean {
return oldItem.id == newItem.id && oldItem.title == newItem.title return oldItem.id == newItem.id && oldItem.title == newItem.title
&& oldItem.order == newItem.order
}
override fun getChangePayload(
oldItem: FavouriteCategory,
newItem: FavouriteCategory,
): Any? = when {
oldItem.title == newItem.title && oldItem.order != newItem.order -> newItem.order
else -> super.getChangePayload(oldItem, newItem)
} }
} }
} }

View File

@@ -4,10 +4,10 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import java.util.* import java.util.*
import kotlin.collections.ArrayList
class FavouritesCategoriesViewModel( class FavouritesCategoriesViewModel(
private val repository: FavouritesRepository private val repository: FavouritesRepository
@@ -19,23 +19,29 @@ class FavouritesCategoriesViewModel(
.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default) .asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
fun createCategory(name: String) { fun createCategory(name: String) {
launchJob(Dispatchers.Default) { launchJob {
repository.addCategory(name) repository.addCategory(name)
} }
} }
fun renameCategory(id: Long, name: String) { fun renameCategory(id: Long, name: String) {
launchJob(Dispatchers.Default) { launchJob {
repository.renameCategory(id, name) repository.renameCategory(id, name)
} }
} }
fun deleteCategory(id: Long) { fun deleteCategory(id: Long) {
launchJob(Dispatchers.Default) { launchJob {
repository.removeCategory(id) repository.removeCategory(id)
} }
} }
fun setCategoryOrder(id: Long, order: SortOrder) {
launchJob {
repository.setCategoryOrder(id, order)
}
}
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) {

View File

@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.SortOrder
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
import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.MangaListViewModel
@@ -22,7 +23,11 @@ class FavouritesListViewModel(
) : MangaListViewModel(settings) { ) : MangaListViewModel(settings) {
override val content = combine( override val content = combine(
if (categoryId == 0L) repository.observeAll() else repository.observeAll(categoryId), if (categoryId == 0L) {
repository.observeAll(SortOrder.NEWEST)
} else {
repository.observeAll(categoryId)
},
createListModeFlow() createListModeFlow()
) { list, mode -> ) { list, mode ->
when { when {

View File

@@ -8,6 +8,7 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.retry import kotlinx.coroutines.flow.retry
import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.utils.ext.processLifecycleScope import org.koitharu.kotatsu.utils.ext.processLifecycleScope
@@ -17,7 +18,7 @@ import org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider
class WidgetUpdater(private val context: Context) { class WidgetUpdater(private val context: Context) {
fun subscribeToFavourites(repository: FavouritesRepository) { fun subscribeToFavourites(repository: FavouritesRepository) {
repository.observeAll() repository.observeAll(SortOrder.NEWEST)
.onEach { updateWidget(ShelfWidgetProvider::class.java) } .onEach { updateWidget(ShelfWidgetProvider::class.java) }
.retry { error -> error !is CancellationException } .retry { error -> error !is CancellationException }
.launchIn(processLifecycleScope) .launchIn(processLifecycleScope)

View File

@@ -10,4 +10,17 @@
android:id="@+id/action_rename" android:id="@+id/action_rename"
android:title="@string/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>
</menu> </menu>