#8 Configure sort order for each favourites category
This commit is contained in:
@@ -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'
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ val databaseModule
|
|||||||
Migration5To6(),
|
Migration5To6(),
|
||||||
Migration6To7(),
|
Migration6To7(),
|
||||||
Migration7To8(),
|
Migration7To8(),
|
||||||
|
Migration8To9(),
|
||||||
).addCallback(
|
).addCallback(
|
||||||
DatabasePrePopulateCallback(androidContext().resources)
|
DatabasePrePopulateCallback(androidContext().resources)
|
||||||
).build()
|
).build()
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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?
|
||||||
|
|||||||
@@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user