Merge branch 'master' of https://github.com/nv95/Kotatsu
commit
738d1cb50e
@ -0,0 +1,20 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.FavouriteCategoryEntity
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class FavouriteCategoriesDao {
|
||||||
|
|
||||||
|
@Query("SELECT category_id,title,created_at FROM favourite_categories ORDER BY :orderBy")
|
||||||
|
abstract suspend fun findAll(orderBy: String): List<FavouriteCategoryEntity>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||||
|
abstract suspend fun insert(category: FavouriteCategoryEntity): Long
|
||||||
|
|
||||||
|
@Query("DELETE FROM favourite_categories WHERE category_id = :id")
|
||||||
|
abstract suspend fun delete(id: Long)
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.FavouriteEntity
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.FavouriteManga
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class FavouritesDao {
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM favourites ORDER BY :orderBy LIMIT :limit OFFSET :offset")
|
||||||
|
abstract suspend fun findAll(offset: Int, limit: Int, orderBy: String): List<FavouriteManga>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM favourites WHERE manga_id = :id")
|
||||||
|
abstract suspend fun find(id: Long): FavouriteManga?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
abstract suspend fun add(favourite: FavouriteEntity)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract suspend fun delete(favourite: FavouriteEntity)
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db.entity
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Entity(tableName = "favourite_categories")
|
||||||
|
data class FavouriteCategoryEntity(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ColumnInfo(name = "category_id") val categoryId: Int,
|
||||||
|
@ColumnInfo(name = "created_at") val createdAt: Long,
|
||||||
|
@ColumnInfo(name = "title") val title: String
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun toFavouriteCategory(id: Long? = null) = FavouriteCategory(
|
||||||
|
id = id ?: categoryId.toLong(),
|
||||||
|
title = title,
|
||||||
|
createdAt = Date(createdAt)
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db.entity
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
|
||||||
|
@Entity(tableName = "favourites", primaryKeys = ["manga_id", "category_id"])
|
||||||
|
data class FavouriteEntity(
|
||||||
|
@ColumnInfo(name = "manga_id") val mangaId: Long,
|
||||||
|
@ColumnInfo(name = "category_id") val categoryId: Long,
|
||||||
|
@ColumnInfo(name = "created_at") val createdAt: Long
|
||||||
|
)
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db.entity
|
||||||
|
|
||||||
|
import androidx.room.Embedded
|
||||||
|
import androidx.room.Junction
|
||||||
|
import androidx.room.Relation
|
||||||
|
|
||||||
|
data class FavouriteManga(
|
||||||
|
@Embedded val favourite: FavouriteEntity,
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "manga_id",
|
||||||
|
entityColumn = "manga_id"
|
||||||
|
)
|
||||||
|
val manga: MangaEntity,
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "category_id",
|
||||||
|
entityColumn = "category_id"
|
||||||
|
)
|
||||||
|
val categories: List<FavouriteCategoryEntity>,
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "manga_id",
|
||||||
|
entityColumn = "tag_id",
|
||||||
|
associateBy = Junction(MangaTagsEntity::class)
|
||||||
|
)
|
||||||
|
val tags: List<TagEntity>
|
||||||
|
)
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package org.koitharu.kotatsu.core.model
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class FavouriteCategory(
|
||||||
|
val id: Long,
|
||||||
|
val title: String,
|
||||||
|
val createdAt: Date
|
||||||
|
) : Parcelable
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package org.koitharu.kotatsu.domain.favourites
|
||||||
|
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
import org.koin.core.inject
|
||||||
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.FavouriteCategoryEntity
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||||
|
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||||
|
import org.koitharu.kotatsu.core.model.Manga
|
||||||
|
|
||||||
|
class FavouritesRepository : KoinComponent {
|
||||||
|
|
||||||
|
private val db: MangaDatabase by inject()
|
||||||
|
|
||||||
|
suspend fun getAllManga(offset: Int): List<Manga> {
|
||||||
|
val entities = db.favouritesDao().findAll(offset, 20, "created_at")
|
||||||
|
return entities.map { it.manga.toManga(it.tags.map(TagEntity::toMangaTag).toSet()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getAllCategories(): List<FavouriteCategory> {
|
||||||
|
val entities = db.favouriteCategoriesDao().findAll("created_at")
|
||||||
|
return entities.map { it.toFavouriteCategory() }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getCategories(mangaId: Long): List<FavouriteCategory> {
|
||||||
|
val entities = db.favouritesDao().find(mangaId)?.categories
|
||||||
|
return entities?.map { it.toFavouriteCategory() }.orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addCategory(title: String): FavouriteCategory {
|
||||||
|
val entity = FavouriteCategoryEntity(
|
||||||
|
title = title,
|
||||||
|
createdAt = System.currentTimeMillis(),
|
||||||
|
categoryId = 0
|
||||||
|
)
|
||||||
|
val id = db.favouriteCategoriesDao().insert(entity)
|
||||||
|
return entity.toFavouriteCategory(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun removeCategory(id: Long) {
|
||||||
|
db.favouriteCategoriesDao().delete(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.main.list.favourites
|
||||||
|
|
||||||
|
import android.util.SparseBooleanArray
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Checkable
|
||||||
|
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||||
|
import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter
|
||||||
|
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||||
|
|
||||||
|
class CategoriesAdapter(private val listener: OnCategoryCheckListener) :
|
||||||
|
BaseRecyclerAdapter<FavouriteCategory, Boolean>() {
|
||||||
|
|
||||||
|
private val checkedIds = SparseBooleanArray()
|
||||||
|
|
||||||
|
override fun getExtra(item: FavouriteCategory, position: Int) =
|
||||||
|
checkedIds.get(item.id.toInt(), false)
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup) = CategoryHolder(parent)
|
||||||
|
|
||||||
|
override fun onGetItemId(item: FavouriteCategory) = item.id
|
||||||
|
|
||||||
|
override fun onViewHolderCreated(holder: BaseViewHolder<FavouriteCategory, Boolean>) {
|
||||||
|
super.onViewHolderCreated(holder)
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
if (it !is Checkable) return@setOnClickListener
|
||||||
|
it.toggle()
|
||||||
|
if (it.isChecked) {
|
||||||
|
listener.onCategoryChecked(holder.requireData())
|
||||||
|
} else {
|
||||||
|
listener.onCategoryUnchecked(holder.requireData())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.main.list.favourites
|
||||||
|
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import kotlinx.android.synthetic.main.item_caegory_checkable.*
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||||
|
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||||
|
|
||||||
|
class CategoryHolder(parent: ViewGroup) :
|
||||||
|
BaseViewHolder<FavouriteCategory, Boolean>(parent, R.layout.item_caegory_checkable) {
|
||||||
|
|
||||||
|
override fun onBind(data: FavouriteCategory, extra: Boolean) {
|
||||||
|
checkedTextView.text = data.title
|
||||||
|
checkedTextView.isChecked = extra
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.main.list.favourites
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.ui.common.AlertDialogFragment
|
||||||
|
|
||||||
|
class FavouriteCategoriesDialog() : AlertDialogFragment(R.layout.dialog_favorite_categories) {
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.main.list.favourites
|
||||||
|
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import kotlinx.android.synthetic.main.fragment_list.*
|
||||||
|
import moxy.ktx.moxyPresenter
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.ui.main.list.MangaListFragment
|
||||||
|
import org.koitharu.kotatsu.ui.main.list.MangaListView
|
||||||
|
|
||||||
|
class FavouritesListFragment : MangaListFragment<Unit>(), MangaListView<Unit>{
|
||||||
|
|
||||||
|
private val presenter by moxyPresenter(factory = ::FavouritesListPresenter)
|
||||||
|
|
||||||
|
override fun onRequestMoreItems(offset: Int) {
|
||||||
|
presenter.loadList(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
// inflater.inflate(R.menu.opt_history, menu)
|
||||||
|
super.onCreateOptionsMenu(menu, inflater)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem) = when(item.itemId) {
|
||||||
|
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTitle(): CharSequence? {
|
||||||
|
return getString(R.string.favourites)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUpEmptyListHolder() {
|
||||||
|
textView_holder.setText(R.string.you_have_not_favourites_yet)
|
||||||
|
textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance() = FavouritesListFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.main.list.favourites
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import moxy.InjectViewState
|
||||||
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
|
import org.koitharu.kotatsu.domain.favourites.FavouritesRepository
|
||||||
|
import org.koitharu.kotatsu.ui.common.BasePresenter
|
||||||
|
import org.koitharu.kotatsu.ui.main.list.MangaListView
|
||||||
|
|
||||||
|
@InjectViewState
|
||||||
|
class FavouritesListPresenter : BasePresenter<MangaListView<Unit>>() {
|
||||||
|
|
||||||
|
private lateinit var repository: FavouritesRepository
|
||||||
|
|
||||||
|
override fun onFirstViewAttach() {
|
||||||
|
repository = FavouritesRepository()
|
||||||
|
super.onFirstViewAttach()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadList(offset: Int) {
|
||||||
|
launch {
|
||||||
|
viewState.onLoadingChanged(true)
|
||||||
|
try {
|
||||||
|
val list = withContext(Dispatchers.IO) {
|
||||||
|
repository.getAllManga(offset = offset)
|
||||||
|
}
|
||||||
|
if (offset == 0) {
|
||||||
|
viewState.onListChanged(list)
|
||||||
|
} else {
|
||||||
|
viewState.onListAppended(list)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
viewState.onError(e)
|
||||||
|
} finally {
|
||||||
|
viewState.onLoadingChanged(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.main.list.favourites
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||||
|
|
||||||
|
interface OnCategoryCheckListener {
|
||||||
|
|
||||||
|
fun onCategoryChecked(category: FavouriteCategory)
|
||||||
|
|
||||||
|
fun onCategoryUnchecked(category: FavouriteCategory)
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
<?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:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView_categories"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
tools:listitem="@layout/item_caegory_checkable" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_add"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?listPreferredItemHeightSmall"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:paddingStart="?listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<CheckedTextView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/checkedTextView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?listPreferredItemHeightSmall"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:paddingStart="?listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
tools:checked="true"
|
||||||
|
tools:text="@tools:sample/lorem[4]" />
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_favourite"
|
||||||
|
android:icon="@drawable/ic_favourites"
|
||||||
|
android:title="@string/favourites"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
Loading…
Reference in New Issue