Show favourites by categories and manager categories order
This commit is contained in:
@@ -60,8 +60,8 @@ androidExtensions {
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6'
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.3.0-rc01'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0-beta01'
|
||||
@@ -91,7 +91,7 @@ dependencies {
|
||||
implementation 'org.jsoup:jsoup:1.13.1'
|
||||
|
||||
implementation 'org.koin:koin-android:2.1.5'
|
||||
implementation 'io.coil-kt:coil:0.10.1'
|
||||
implementation 'io.coil-kt:coil:0.11.0'
|
||||
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
|
||||
implementation 'com.tomclaw.cache:cache:1.0'
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.migrations.Migration1To2
|
||||
import org.koitharu.kotatsu.core.db.migrations.Migration2To3
|
||||
import org.koitharu.kotatsu.core.db.migrations.Migration3To4
|
||||
import org.koitharu.kotatsu.core.db.migrations.Migration4To5
|
||||
import org.koitharu.kotatsu.core.local.CbzFetcher
|
||||
import org.koitharu.kotatsu.core.local.PagesCache
|
||||
import org.koitharu.kotatsu.core.local.cookies.PersistentCookieJar
|
||||
@@ -124,5 +125,5 @@ class KotatsuApp : Application() {
|
||||
applicationContext,
|
||||
MangaDatabase::class.java,
|
||||
"kotatsu-db"
|
||||
).addMigrations(Migration1To2, Migration2To3, Migration3To4)
|
||||
).addMigrations(Migration1To2, Migration2To3, Migration3To4, Migration4To5)
|
||||
}
|
||||
@@ -9,8 +9,8 @@ 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>
|
||||
@Query("SELECT * FROM favourite_categories ORDER BY sort_key")
|
||||
abstract suspend fun findAll(): List<FavouriteCategoryEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||
abstract suspend fun insert(category: FavouriteCategoryEntity): Long
|
||||
@@ -20,4 +20,14 @@ abstract class FavouriteCategoriesDao {
|
||||
|
||||
@Query("UPDATE favourite_categories SET title = :title WHERE category_id = :id")
|
||||
abstract suspend fun update(id: Long, title: String)
|
||||
|
||||
@Query("UPDATE favourite_categories SET sort_key = :sortKey WHERE category_id = :id")
|
||||
abstract suspend fun update(id: Long, sortKey: Int)
|
||||
|
||||
@Query("SELECT MAX(sort_key) FROM favourite_categories")
|
||||
protected abstract suspend fun getMaxSortKey(): Int?
|
||||
|
||||
suspend fun getNextSortKey(): Int {
|
||||
return (getMaxSortKey() ?: 0) + 1
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,18 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
@Dao
|
||||
abstract class FavouritesDao {
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM favourites GROUP BY manga_id ORDER BY created_at")
|
||||
abstract suspend fun findAll(): List<FavouriteManga>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM favourites GROUP BY manga_id ORDER BY created_at LIMIT :limit OFFSET :offset")
|
||||
abstract suspend fun findAll(offset: Int, limit: Int): List<FavouriteManga>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM favourites WHERE category_id = :categoryId GROUP BY manga_id ORDER BY created_at")
|
||||
abstract suspend fun findAll(categoryId: Long): List<FavouriteManga>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM favourites WHERE category_id = :categoryId GROUP BY manga_id ORDER BY created_at LIMIT :limit OFFSET :offset")
|
||||
abstract suspend fun findAll(categoryId: Long, offset: Int, limit: Int): List<FavouriteManga>
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.core.db.entity.*
|
||||
entities = [
|
||||
MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class,
|
||||
FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class, TrackEntity::class
|
||||
], version = 4
|
||||
], version = 5
|
||||
)
|
||||
abstract class MangaDatabase : RoomDatabase() {
|
||||
|
||||
|
||||
@@ -11,12 +11,14 @@ data class FavouriteCategoryEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = "category_id") val categoryId: Int,
|
||||
@ColumnInfo(name = "created_at") val createdAt: Long,
|
||||
@ColumnInfo(name = "sort_key") val sortKey: Int,
|
||||
@ColumnInfo(name = "title") val title: String
|
||||
) {
|
||||
|
||||
fun toFavouriteCategory(id: Long? = null) = FavouriteCategory(
|
||||
id = id ?: categoryId.toLong(),
|
||||
title = title,
|
||||
sortKey = sortKey,
|
||||
createdAt = Date(createdAt)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.koitharu.kotatsu.core.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
object Migration4To5 : Migration(4, 5) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE favourite_categories ADD COLUMN sort_key INTEGER NOT NULL DEFAULT 0")
|
||||
}
|
||||
}
|
||||
@@ -8,5 +8,6 @@ import java.util.*
|
||||
data class FavouriteCategory(
|
||||
val id: Long,
|
||||
val title: String,
|
||||
val sortKey: Int,
|
||||
val createdAt: Date
|
||||
) : Parcelable
|
||||
@@ -18,18 +18,28 @@ class FavouritesRepository : KoinComponent {
|
||||
|
||||
private val db: MangaDatabase by inject()
|
||||
|
||||
suspend fun getAllManga(): List<Manga> {
|
||||
val entities = db.favouritesDao.findAll()
|
||||
return entities.map { it.manga.toManga(it.tags.map(TagEntity::toMangaTag).toSet()) }
|
||||
}
|
||||
|
||||
suspend fun getAllManga(offset: Int): List<Manga> {
|
||||
val entities = db.favouritesDao.findAll(offset, 20)
|
||||
return entities.map { it.manga.toManga(it.tags.map(TagEntity::toMangaTag).toSet()) }
|
||||
}
|
||||
|
||||
suspend fun getManga(categoryId: Long): List<Manga> {
|
||||
val entities = db.favouritesDao.findAll(categoryId)
|
||||
return entities.map { it.manga.toManga(it.tags.map(TagEntity::toMangaTag).toSet()) }
|
||||
}
|
||||
|
||||
suspend fun getManga(categoryId: Long, offset: Int): List<Manga> {
|
||||
val entities = db.favouritesDao.findAll(categoryId, offset, 20)
|
||||
return entities.map { it.manga.toManga(it.tags.map(TagEntity::toMangaTag).toSet()) }
|
||||
}
|
||||
|
||||
suspend fun getAllCategories(): List<FavouriteCategory> {
|
||||
val entities = db.favouriteCategoriesDao.findAll("created_at")
|
||||
val entities = db.favouriteCategoriesDao.findAll()
|
||||
return entities.map { it.toFavouriteCategory() }
|
||||
}
|
||||
|
||||
@@ -42,17 +52,32 @@ class FavouritesRepository : KoinComponent {
|
||||
val entity = FavouriteCategoryEntity(
|
||||
title = title,
|
||||
createdAt = System.currentTimeMillis(),
|
||||
sortKey = db.favouriteCategoriesDao.getNextSortKey(),
|
||||
categoryId = 0
|
||||
)
|
||||
val id = db.favouriteCategoriesDao.insert(entity)
|
||||
notifyCategoriesChanged()
|
||||
return entity.toFavouriteCategory(id)
|
||||
}
|
||||
|
||||
suspend fun renameCategory(id: Long, title: String) {
|
||||
db.favouriteCategoriesDao.update(id, title)
|
||||
notifyCategoriesChanged()
|
||||
}
|
||||
|
||||
suspend fun removeCategory(id: Long) {
|
||||
db.favouriteCategoriesDao.delete(id)
|
||||
notifyCategoriesChanged()
|
||||
}
|
||||
|
||||
suspend fun reorderCategories(orderedIds: List<Long>) {
|
||||
val dao = db.favouriteCategoriesDao
|
||||
db.withTransaction {
|
||||
for ((i, id) in orderedIds.withIndex()) {
|
||||
dao.update(id, i)
|
||||
}
|
||||
}
|
||||
notifyCategoriesChanged()
|
||||
}
|
||||
|
||||
suspend fun addToCategory(manga: Manga, categoryId: Long) {
|
||||
@@ -88,5 +113,11 @@ class FavouritesRepository : KoinComponent {
|
||||
listeners.forEach { x -> x.onFavouritesChanged(mangaId) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun notifyCategoriesChanged() {
|
||||
withContext(Dispatchers.Main) {
|
||||
listeners.forEach { x -> x.onCategoriesChanged() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,6 @@ package org.koitharu.kotatsu.domain.favourites
|
||||
interface OnFavouritesChangeListener {
|
||||
|
||||
fun onFavouritesChanged(mangaId: Long)
|
||||
|
||||
fun onCategoriesChanged()
|
||||
}
|
||||
@@ -157,6 +157,8 @@ class MangaDetailsPresenter private constructor() : BasePresenter<MangaDetailsVi
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCategoriesChanged() = Unit
|
||||
|
||||
override fun onDestroy() {
|
||||
HistoryRepository.unsubscribe(this)
|
||||
FavouritesRepository.unsubscribe(this)
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.domain.MangaProviderFactory
|
||||
import org.koitharu.kotatsu.ui.common.BaseActivity
|
||||
import org.koitharu.kotatsu.ui.main.list.favourites.FavouritesListFragment
|
||||
import org.koitharu.kotatsu.ui.main.list.favourites.FavouritesContainerFragment
|
||||
import org.koitharu.kotatsu.ui.main.list.history.HistoryListFragment
|
||||
import org.koitharu.kotatsu.ui.main.list.local.LocalListFragment
|
||||
import org.koitharu.kotatsu.ui.main.list.remote.RemoteListFragment
|
||||
@@ -107,7 +107,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
setPrimaryFragment(RemoteListFragment.newInstance(source))
|
||||
} else when (item.itemId) {
|
||||
R.id.nav_history -> setPrimaryFragment(HistoryListFragment.newInstance())
|
||||
R.id.nav_favourites -> setPrimaryFragment(FavouritesListFragment.newInstance())
|
||||
R.id.nav_favourites -> setPrimaryFragment(FavouritesContainerFragment.newInstance())
|
||||
R.id.nav_local_storage -> setPrimaryFragment(LocalListFragment.newInstance())
|
||||
R.id.nav_action_settings -> {
|
||||
startActivity(SettingsActivity.newIntent(this))
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.koitharu.kotatsu.ui.main.list.favourites
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import kotlinx.android.synthetic.main.fragment_favourites.*
|
||||
import moxy.ktx.moxyPresenter
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.domain.favourites.FavouritesRepository
|
||||
import org.koitharu.kotatsu.domain.favourites.OnFavouritesChangeListener
|
||||
import org.koitharu.kotatsu.ui.common.BaseFragment
|
||||
import org.koitharu.kotatsu.ui.main.list.favourites.categories.CategoriesActivity
|
||||
import org.koitharu.kotatsu.ui.main.list.favourites.categories.FavouriteCategoriesPresenter
|
||||
import org.koitharu.kotatsu.ui.main.list.favourites.categories.FavouriteCategoriesView
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites), FavouriteCategoriesView,
|
||||
OnFavouritesChangeListener {
|
||||
|
||||
private val presenter by moxyPresenter(factory = ::FavouriteCategoriesPresenter)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val adapter = FavouritesPagerAdapter(this)
|
||||
pager.adapter = adapter
|
||||
TabLayoutMediator(tabs, pager, adapter).attach()
|
||||
FavouritesRepository.subscribe(this)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
FavouritesRepository.unsubscribe(this)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onCategoriesChanged(categories: List<FavouriteCategory>) {
|
||||
val data = ArrayList<FavouriteCategory>(categories.size + 1)
|
||||
data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, Date())
|
||||
data += categories
|
||||
(pager.adapter as? FavouritesPagerAdapter)?.replaceData(data)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.opt_favourites, menu)
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.action_categories -> {
|
||||
context?.let {
|
||||
startActivity(CategoriesActivity.newIntent(it))
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun getTitle(): CharSequence? {
|
||||
return getString(R.string.favourites)
|
||||
}
|
||||
|
||||
override fun onCheckedCategoriesChanged(checkedIds: Set<Int>) = Unit
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Snackbar.make(pager, e.message ?: return, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onFavouritesChanged(mangaId: Long) = Unit
|
||||
|
||||
override fun onCategoriesChanged() {
|
||||
presenter.loadAllCategories()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance() = FavouritesContainerFragment()
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,38 @@
|
||||
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
|
||||
import org.koitharu.kotatsu.ui.main.list.favourites.categories.CategoriesActivity
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
class FavouritesListFragment : MangaListFragment<Unit>(), MangaListView<Unit> {
|
||||
|
||||
private val presenter by moxyPresenter(factory = ::FavouritesListPresenter)
|
||||
|
||||
private val categoryId: Long
|
||||
get() = arguments?.getLong(ARG_CATEGORY_ID) ?: 0L
|
||||
|
||||
override fun onRequestMoreItems(offset: Int) {
|
||||
presenter.loadList(offset)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.opt_favourites, menu)
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.action_categories -> {
|
||||
context?.let {
|
||||
startActivity(CategoriesActivity.newIntent(it))
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun getTitle(): CharSequence? {
|
||||
return getString(R.string.favourites)
|
||||
presenter.loadList(categoryId, offset)
|
||||
}
|
||||
|
||||
override fun setUpEmptyListHolder() {
|
||||
textView_holder.setText(R.string.you_have_not_favourites_yet)
|
||||
textView_holder.setText(if (categoryId == 0L) {
|
||||
R.string.you_have_not_favourites_yet
|
||||
} else {
|
||||
R.string.favourites_category_empty
|
||||
})
|
||||
textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance() = FavouritesListFragment()
|
||||
private const val ARG_CATEGORY_ID = "category_id"
|
||||
|
||||
fun newInstance(categoryId: Long) = FavouritesListFragment().withArgs(1) {
|
||||
putLong(ARG_CATEGORY_ID, categoryId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,12 @@ class FavouritesListPresenter : BasePresenter<MangaListView<Unit>>() {
|
||||
super.onFirstViewAttach()
|
||||
}
|
||||
|
||||
fun loadList(offset: Int) {
|
||||
fun loadList(categoryId: Long, offset: Int) {
|
||||
presenterScope.launch {
|
||||
viewState.onLoadingStateChanged(true)
|
||||
try {
|
||||
val list = withContext(Dispatchers.IO) {
|
||||
repository.getAllManga(offset = offset)
|
||||
repository.getManga(categoryId = categoryId, offset = offset)
|
||||
}
|
||||
if (offset == 0) {
|
||||
viewState.onListChanged(list)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.koitharu.kotatsu.ui.main.list.favourites
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.ui.common.list.AdapterUpdater
|
||||
import org.koitharu.kotatsu.utils.ext.replaceWith
|
||||
|
||||
class FavouritesPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment), TabLayoutMediator.TabConfigurationStrategy {
|
||||
|
||||
private val dataSet = ArrayList<FavouriteCategory>()
|
||||
|
||||
override fun getItemCount() = dataSet.size
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
val item = dataSet[position]
|
||||
return FavouritesListFragment.newInstance(item.id)
|
||||
}
|
||||
|
||||
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
||||
val item = dataSet[position]
|
||||
tab.text = item.title
|
||||
}
|
||||
|
||||
fun replaceData(data: List<FavouriteCategory>) {
|
||||
val updater = AdapterUpdater(dataSet, data, FavouriteCategory::id)
|
||||
dataSet.replaceWith(data)
|
||||
updater(this)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.activity_categories.*
|
||||
@@ -28,6 +29,7 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener<Favourite
|
||||
private val presenter by moxyPresenter(factory = ::FavouriteCategoriesPresenter)
|
||||
|
||||
private lateinit var adapter: CategoriesAdapter
|
||||
private lateinit var reorderHelper: ItemTouchHelper
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -38,6 +40,8 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener<Favourite
|
||||
recyclerView.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
|
||||
recyclerView.adapter = adapter
|
||||
fab_add.setOnClickListener(this)
|
||||
reorderHelper = ItemTouchHelper(ReorderHelperCallback())
|
||||
reorderHelper.attachToRecyclerView(recyclerView)
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
@@ -56,6 +60,11 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener<Favourite
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemLongClick(item: FavouriteCategory, position: Int, view: View): Boolean {
|
||||
reorderHelper.startDrag(recyclerView.findViewHolderForAdapterPosition(position) ?: return false)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCategoriesChanged(categories: List<FavouriteCategory>) {
|
||||
adapter.replaceData(categories)
|
||||
textView_holder.isVisible = categories.isEmpty()
|
||||
@@ -106,6 +115,25 @@ class CategoriesActivity : BaseActivity(), OnRecyclerItemClickListener<Favourite
|
||||
.show()
|
||||
}
|
||||
|
||||
private inner class ReorderHelperCallback : ItemTouchHelper.SimpleCallback(
|
||||
ItemTouchHelper.DOWN or ItemTouchHelper.UP, 0
|
||||
) {
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
val oldPos = viewHolder.bindingAdapterPosition
|
||||
val newPos = target.bindingAdapterPosition
|
||||
adapter.moveItem(oldPos, newPos)
|
||||
presenter.storeCategoriesOrder(adapter.items.map { it.id })
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newIntent(context: Context) = Intent(context, CategoriesActivity::class.java)
|
||||
|
||||
@@ -1,17 +1,40 @@
|
||||
package org.koitharu.kotatsu.ui.main.list.favourites.categories
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.MotionEvent
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.item_category.*
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||
import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
|
||||
import org.koitharu.kotatsu.ui.main.list.favourites.categories.select.CategoryCheckableHolder
|
||||
|
||||
class CategoriesAdapter(onItemClickListener: OnRecyclerItemClickListener<FavouriteCategory>? = null) :
|
||||
BaseRecyclerAdapter<FavouriteCategory, Unit>(onItemClickListener) {
|
||||
class CategoriesAdapter(private val onItemClickListener: OnRecyclerItemClickListener<FavouriteCategory>) :
|
||||
BaseRecyclerAdapter<FavouriteCategory, Unit>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup) = CategoryHolder(parent)
|
||||
|
||||
override fun onGetItemId(item: FavouriteCategory) = item.id
|
||||
|
||||
override fun getExtra(item: FavouriteCategory, position: Int) = Unit
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onViewHolderCreated(holder: BaseViewHolder<FavouriteCategory, Unit>) {
|
||||
holder.imageView_more.setOnClickListener { v ->
|
||||
onItemClickListener.onItemClick(holder.requireData(), holder.bindingAdapterPosition, v)
|
||||
}
|
||||
holder.imageView_handle.setOnTouchListener { v, event ->
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
onItemClickListener.onItemLongClick(holder.requireData(), holder.bindingAdapterPosition, v)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun moveItem(oldPos: Int, newPos: Int) {
|
||||
val item = dataSet.removeAt(oldPos)
|
||||
dataSet.add(newPos, item)
|
||||
notifyItemMoved(oldPos, newPos)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,6 @@ class CategoryHolder(parent: ViewGroup) :
|
||||
BaseViewHolder<FavouriteCategory, Unit>(parent, R.layout.item_category) {
|
||||
|
||||
override fun onBind(data: FavouriteCategory, extra: Unit) {
|
||||
textView.text = data.title
|
||||
textView_title.text = data.title
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package org.koitharu.kotatsu.ui.main.list.favourites.categories
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import moxy.InjectViewState
|
||||
import moxy.presenterScope
|
||||
@@ -14,6 +16,9 @@ import org.koitharu.kotatsu.ui.common.BasePresenter
|
||||
class FavouriteCategoriesPresenter : BasePresenter<FavouriteCategoriesView>() {
|
||||
|
||||
private lateinit var repository: FavouritesRepository
|
||||
private val reorderMutex by lazy {
|
||||
Mutex()
|
||||
}
|
||||
|
||||
override fun onFirstViewAttach() {
|
||||
repository = FavouritesRepository()
|
||||
@@ -28,7 +33,7 @@ class FavouriteCategoriesPresenter : BasePresenter<FavouriteCategoriesView>() {
|
||||
repository.getAllCategories()
|
||||
}
|
||||
viewState.onCategoriesChanged(categories)
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -44,7 +49,7 @@ class FavouriteCategoriesPresenter : BasePresenter<FavouriteCategoriesView>() {
|
||||
repository.getCategories(manga.id)
|
||||
}
|
||||
viewState.onCheckedCategoriesChanged(categories.map { it.id.toInt() }.toSet())
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -61,7 +66,7 @@ class FavouriteCategoriesPresenter : BasePresenter<FavouriteCategoriesView>() {
|
||||
repository.getAllCategories()
|
||||
}
|
||||
viewState.onCategoriesChanged(categories)
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -78,7 +83,7 @@ class FavouriteCategoriesPresenter : BasePresenter<FavouriteCategoriesView>() {
|
||||
repository.getAllCategories()
|
||||
}
|
||||
viewState.onCategoriesChanged(categories)
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -95,7 +100,22 @@ class FavouriteCategoriesPresenter : BasePresenter<FavouriteCategoriesView>() {
|
||||
repository.getAllCategories()
|
||||
}
|
||||
viewState.onCategoriesChanged(categories)
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
viewState.onError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun storeCategoriesOrder(orderedIds: List<Long>) {
|
||||
presenterScope.launch {
|
||||
try {
|
||||
reorderMutex.withLock {
|
||||
repository.reorderCategories(orderedIds)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -110,7 +130,7 @@ class FavouriteCategoriesPresenter : BasePresenter<FavouriteCategoriesView>() {
|
||||
withContext(Dispatchers.IO) {
|
||||
repository.addToCategory(manga,categoryId)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -125,7 +145,7 @@ class FavouriteCategoriesPresenter : BasePresenter<FavouriteCategoriesView>() {
|
||||
withContext(Dispatchers.IO) {
|
||||
repository.removeFromCategory(manga, categoryId)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
@@ -42,6 +42,6 @@ class SourcesSettingsFragment : BaseFragment(R.layout.fragment_settings_sources)
|
||||
|
||||
override fun onItemLongClick(item: MangaSource, position: Int, view: View): Boolean {
|
||||
reorderHelper.startDrag(recyclerView.findViewHolderForAdapterPosition(position) ?: return false)
|
||||
return super.onItemLongClick(item, position, view)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ class WidgetUpdater(private val context: Context) : OnFavouritesChangeListener,
|
||||
updateWidget(RecentWidgetProvider::class.java)
|
||||
}
|
||||
|
||||
override fun onCategoriesChanged() = Unit
|
||||
|
||||
private fun updateWidget(cls: Class<*>) {
|
||||
val intent = Intent(context, cls)
|
||||
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||
|
||||
@@ -80,7 +80,7 @@ class ShelfConfigActivity : BaseActivity(), FavouriteCategoriesView,
|
||||
|
||||
override fun onCategoriesChanged(categories: List<FavouriteCategory>) {
|
||||
val data = ArrayList<FavouriteCategory>(categories.size + 1)
|
||||
data += FavouriteCategory(0L, getString(R.string.favourites), Date())
|
||||
data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, Date())
|
||||
data += categories
|
||||
adapter.replaceData(data)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,13 @@ class ShelfListFactory(private val context: Context, widgetId: Int) : RemoteView
|
||||
override fun onDataSetChanged() {
|
||||
dataSet.clear()
|
||||
val data = runBlocking {
|
||||
FavouritesRepository().getManga(config.categoryId, 0)
|
||||
val category = config.categoryId
|
||||
val repo = FavouritesRepository()
|
||||
if (category == 0L) {
|
||||
repo.getAllManga()
|
||||
} else {
|
||||
repo.getManga(category)
|
||||
}
|
||||
}
|
||||
dataSet.addAll(data)
|
||||
}
|
||||
|
||||
11
app/src/main/res/drawable/ic_more.xml
Normal file
11
app/src/main/res/drawable/ic_more.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" />
|
||||
</vector>
|
||||
@@ -23,6 +23,7 @@
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:tabGravity="center"
|
||||
app:tabMode="scrollable" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
21
app/src/main/res/layout/fragment_favourites.xml
Normal file
21
app/src/main/res/layout/fragment_favourites.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:tabGravity="center"
|
||||
app:tabMode="scrollable"/>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,14 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:listPreferredItemHeight"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="start|center_vertical"
|
||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
tools:text="@tools:sample/lorem[4]" />
|
||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||
android:background="?android:windowBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView_handle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="?listPreferredItemPaddingStart"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_reorder_handle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
tools:text="@tools:sample/lorem[1]" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView_more"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="?listPreferredItemPaddingEnd"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_more" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -132,4 +132,6 @@
|
||||
<string name="other_storage">Другое хранилище</string>
|
||||
<string name="use_ssl">Защищённое соединение (HTTPS)</string>
|
||||
<string name="done">Готово</string>
|
||||
<string name="all_favourites">Всё избранное</string>
|
||||
<string name="favourites_category_empty">В этой категории ничего нет</string>
|
||||
</resources>
|
||||
@@ -133,4 +133,6 @@
|
||||
<string name="other_storage">Other storage</string>
|
||||
<string name="use_ssl">Use secure connection (HTTPS)</string>
|
||||
<string name="done">Done</string>
|
||||
<string name="all_favourites">All favourites</string>
|
||||
<string name="favourites_category_empty">This category is empty</string>
|
||||
</resources>
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Sun May 10 11:33:24 EEST 2020
|
||||
#Sat May 16 07:20:00 EEST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip
|
||||
|
||||
Reference in New Issue
Block a user