Added material library grid

Like the old grid but using a card view + play button
Increased memory size of glide + remove cross fade being on by default
pull/3117/head
Jay 5 years ago
parent 489ef7a5f8
commit a07de130a9

@ -9,6 +9,7 @@ import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.load.engine.cache.LruResourceCache
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.module.AppGlideModule import com.bumptech.glide.module.AppGlideModule
@ -28,8 +29,12 @@ class TachiGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) { override fun applyOptions(context: Context, builder: GlideBuilder) {
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024)) builder.setDiskCache(InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024))
builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565)) builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565))
builder.setDefaultTransitionOptions(Drawable::class.java, val memoryCacheSizeBytes = 1024 * 1024 * 100 // 1000mb
DrawableTransitionOptions.withCrossFade()) builder.setMemoryCache(LruResourceCache(memoryCacheSizeBytes.toLong()))
/* builder.setDefaultTransitionOptions(
Drawable::class.java,
DrawableTransitionOptions.withCrossFade())*/
} }
override fun registerComponents(context: Context, glide: Glide, registry: Registry) { override fun registerComponents(context: Context, glide: Glide, registry: Registry) {

@ -106,7 +106,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
/** /**
* Recycler view with the list of results. * Recycler view with the list of results.
*/ */
private var recycler: androidx.recyclerview.widget.RecyclerView? = null private var recycler: RecyclerView? = null
/** /**
* Subscription for the search view. * Subscription for the search view.
@ -212,9 +212,9 @@ open class BrowseCatalogueController(bundle: Bundle) :
private fun setupRecycler(view: View) { private fun setupRecycler(view: View) {
numColumnsSubscription?.unsubscribe() numColumnsSubscription?.unsubscribe()
var oldPosition = androidx.recyclerview.widget.RecyclerView.NO_POSITION var oldPosition = RecyclerView.NO_POSITION
val oldRecycler = catalogue_view?.getChildAt(1) val oldRecycler = catalogue_view?.getChildAt(1)
if (oldRecycler is androidx.recyclerview.widget.RecyclerView) { if (oldRecycler is RecyclerView) {
oldPosition = (oldRecycler.layoutManager as androidx.recyclerview.widget.LinearLayoutManager).findFirstVisibleItemPosition() oldPosition = (oldRecycler.layoutManager as androidx.recyclerview.widget.LinearLayoutManager).findFirstVisibleItemPosition()
oldRecycler.adapter = null oldRecycler.adapter = null
@ -239,7 +239,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
(layoutManager as androidx.recyclerview.widget.GridLayoutManager).spanSizeLookup = object : androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup() { (layoutManager as androidx.recyclerview.widget.GridLayoutManager).spanSizeLookup = object : androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int { override fun getSpanSize(position: Int): Int {
return when (adapter?.getItemViewType(position)) { return when (adapter?.getItemViewType(position)) {
R.layout.catalogue_grid_item, null -> 1 R.layout.catalogue_mat_grid_item, null -> 1
else -> spanCount else -> spanCount
} }
} }
@ -251,7 +251,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
catalogue_view.addView(recycler, 1) catalogue_view.addView(recycler, 1)
recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
if (oldPosition != androidx.recyclerview.widget.RecyclerView.NO_POSITION) { if (oldPosition != RecyclerView.NO_POSITION) {
recycler.layoutManager?.scrollToPosition(oldPosition) recycler.layoutManager?.scrollToPosition(oldPosition)
} }
this.recycler = recycler this.recycler = recycler

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.catalogue.browse
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
@ -15,6 +16,7 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.android.synthetic.main.catalogue_grid_item.view.* import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
import kotlinx.android.synthetic.main.catalogue_mat_grid_item.view.*
class CatalogueItem(val manga: Manga, private val catalogueAsList: Preference<Boolean>) : class CatalogueItem(val manga: Manga, private val catalogueAsList: Preference<Boolean>) :
AbstractFlexibleItem<CatalogueHolder>() { AbstractFlexibleItem<CatalogueHolder>() {
@ -23,19 +25,19 @@ class CatalogueItem(val manga: Manga, private val catalogueAsList: Preference<Bo
return if (catalogueAsList.getOrDefault()) return if (catalogueAsList.getOrDefault())
R.layout.catalogue_list_item R.layout.catalogue_list_item
else else
R.layout.catalogue_grid_item R.layout.catalogue_mat_grid_item
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): CatalogueHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): CatalogueHolder {
val parent = adapter.recyclerView val parent = adapter.recyclerView
return if (parent is AutofitRecyclerView) { return if (parent is AutofitRecyclerView) {
view.apply { view.apply {
card.layoutParams = FrameLayout.LayoutParams( val coverHeight = (parent.itemWidth / 3 * 4f).toInt()
MATCH_PARENT, parent.itemWidth / 3 * 4) constraint_layout.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
gradient.layoutParams = FrameLayout.LayoutParams( cover_thumbnail.adjustViewBounds = false
MATCH_PARENT, parent.itemWidth / 3 * 4 / 2, Gravity.BOTTOM) cover_thumbnail.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight)
} }
CatalogueGridHolder(view, adapter) CatalogueMatGridHolder(view, adapter)
} else { } else {
CatalogueListHolder(view, adapter) CatalogueListHolder(view, adapter)
} }

@ -0,0 +1,63 @@
package eu.kanade.tachiyomi.ui.catalogue.browse
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.signature.ObjectKey
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.ui.library.LibraryCategoryAdapter
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.widget.StateImageViewTarget
import kotlinx.android.synthetic.main.catalogue_mat_grid_item.*
/**
* Class used to hold the displayed data of a manga in the library, like the cover or the title.
* All the elements from the layout file "item_catalogue_grid" are available in this class.
*
* @param view the inflated view for this holder.
* @param adapter the adapter handling this holder.
* @param listener a listener to react to single tap and long tap events.
* @constructor creates a new library holder.
*/
class CatalogueMatGridHolder(
private val view: View,
private val adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) :
CatalogueHolder(view, adapter) {
/**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* holder with the given manga.
*
* @param manga the manga item to bind.
*/
override fun onSetValues(manga: Manga) {
// Update the title of the manga.
title.text = manga.currentTitle()
subtitle.gone()
bookmark_text.visibility = if (manga.favorite) View.VISIBLE else View.GONE
// Update the cover.
setImage(manga)
}
override fun setImage(manga: Manga) {
if (manga.thumbnail_url == null)
Glide.with(view.context).clear(cover_thumbnail)
else {
GlideApp.with(view.context)
.load(manga)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.centerCrop()
.placeholder(android.R.color.transparent)
.transition(DrawableTransitionOptions.withCrossFade())
.into(StateImageViewTarget(cover_thumbnail, progress))
}
}
}

@ -11,8 +11,9 @@ import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.lang.removeArticles import eu.kanade.tachiyomi.util.lang.removeArticles
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Calendar
import java.util.Date
import java.util.Locale
/** /**
* Adapter storing a list of manga in a certain category. * Adapter storing a list of manga in a certain category.
@ -29,6 +30,12 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
val onItemReleaseListener: CategoryAdapter.OnItemReleaseListener = view val onItemReleaseListener: CategoryAdapter.OnItemReleaseListener = view
/**
* Listener called when an item of the list press start reading.
*/
val libraryListener: LibraryListener = view
/** /**
* Sets a list of manga in the adapter. * Sets a list of manga in the adapter.
* *
@ -133,4 +140,10 @@ class LibraryCategoryAdapter(val view: LibraryCategoryView) :
SimpleDateFormat("yyyy", Locale.getDefault()).format(date) SimpleDateFormat("yyyy", Locale.getDefault()).format(date)
} }
interface LibraryListener {
/**
* Called when an item of the list is released.
*/
fun startReading(position: Int)
}
} }

@ -39,6 +39,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemLongClickListener,
FlexibleAdapter.OnItemMoveListener, FlexibleAdapter.OnItemMoveListener,
LibraryCategoryAdapter.LibraryListener,
CategoryAdapter.OnItemReleaseListener { CategoryAdapter.OnItemReleaseListener {
/** /**
@ -96,6 +97,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
recycler.adapter = adapter recycler.adapter = adapter
swipe_refresh.addView(recycler) swipe_refresh.addView(recycler)
adapter.fastScroller = fast_scroller adapter.fastScroller = fast_scroller
// recycler.addOnScrollListener(adapter.preloader())
if (::category.isInitialized) { if (::category.isInitialized) {
val mangaForCategory = controller.presenter.getMangaInCategory(category.id) val mangaForCategory = controller.presenter.getMangaInCategory(category.id)
@ -332,6 +334,11 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
if (adapter.selectedItemCount == 0) saveDragSort() if (adapter.selectedItemCount == 0) saveDragSort()
} }
override fun startReading(position: Int) {
val manga = adapter.getItem(position)?.manga ?: return
controller.startReading(manga)
}
private fun saveDragSort() { private fun saveDragSort() {
val mangaIds = adapter.currentItems.mapNotNull { it.manga.id } val mangaIds = adapter.currentItems.mapNotNull { it.manga.id }
category.mangaSort = null category.mangaSort = null

@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
@ -50,6 +52,7 @@ import eu.kanade.tachiyomi.ui.migration.MigrationInterface
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
@ -141,6 +144,8 @@ class LibraryController(
private var tabsVisibilitySubscription: Subscription? = null private var tabsVisibilitySubscription: Subscription? = null
private var observeLater:Boolean = false
var snack: Snackbar? = null var snack: Snackbar? = null
var presenter = LibraryPresenter(this) var presenter = LibraryPresenter(this)
@ -165,22 +170,11 @@ class LibraryController(
adapter = LibraryAdapter(this) adapter = LibraryAdapter(this)
library_pager.adapter = adapter library_pager.adapter = adapter
library_pager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { library_pager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
preferences.lastUsedCategory().set(position) preferences.lastUsedCategory().set(position)
activeCategory = position activeCategory = position
}
override fun onPageScrollStateChanged(state: Int) {}
override fun onPageScrolled(
position: Int, positionOffset: Float, positionOffsetPixels: Int
) {
}
})
library_pager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageSelected(position: Int) {
bottom_sheet.lastCategory = adapter?.categories?.getOrNull(position) bottom_sheet.lastCategory = adapter?.categories?.getOrNull(position)
if (preferences.librarySortingMode().getOrDefault() == LibrarySort.DRAG_AND_DROP) bottom_sheet.updateTitle() if (preferences.librarySortingMode().getOrDefault() == LibrarySort.DRAG_AND_DROP) bottom_sheet.updateTitle()
} }
@ -227,6 +221,7 @@ class LibraryController(
val library = presenter.getAllManga() val library = presenter.getAllManga()
if (library != null) onNextLibraryUpdate(presenter.categories, library) if (library != null) onNextLibraryUpdate(presenter.categories, library)
else { else {
library_pager.alpha = 0f
presenter.getLibraryBlocking() presenter.getLibraryBlocking()
} }
} }
@ -242,6 +237,14 @@ class LibraryController(
} }
} }
override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity)
if (observeLater) {
presenter.getLibrary()
observeLater = false
}
}
override fun onDestroy() { override fun onDestroy() {
presenter.onDestroy() presenter.onDestroy()
super.onDestroy() super.onDestroy()
@ -345,6 +348,8 @@ class LibraryController(
} }
else if (!freshStart) { else if (!freshStart) {
justStarted = false justStarted = false
if (library_pager.alpha == 0f)
library_pager.animate().alpha(1f).setDuration(500).start()
} }
} }
@ -666,6 +671,14 @@ class LibraryController(
presenter.moveMangasToCategories(categories, mangas) presenter.moveMangasToCategories(categories, mangas)
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
} }
fun startReading(manga: Manga) {
val activity = activity ?: return
val chapter = presenter.getFirstUnread(manga) ?: return
val intent = ReaderActivity.newIntent(activity, manga, chapter)
observeLater = true
startActivity(intent)
}
} }
object HeightTopWindowInsetsListener : View.OnApplyWindowInsetsListener { object HeightTopWindowInsetsListener : View.OnApplyWindowInsetsListener {

@ -25,7 +25,7 @@ import kotlinx.android.synthetic.main.catalogue_grid_item.*
*/ */
class LibraryGridHolder( class LibraryGridHolder(
private val view: View, private val view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>> adapter: LibraryCategoryAdapter
) : LibraryHolder(view, adapter) { ) : LibraryHolder(view, adapter) {

@ -15,7 +15,7 @@ import eu.davidea.flexibleadapter.items.IFlexible
abstract class LibraryHolder( abstract class LibraryHolder(
view: View, view: View,
val adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>> val adapter: LibraryCategoryAdapter
) : BaseFlexibleViewHolder(view, adapter) { ) : BaseFlexibleViewHolder(view, adapter) {
/** /**

@ -1,10 +1,7 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.Gravity
import android.view.View import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
@ -15,10 +12,10 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.android.synthetic.main.catalogue_grid_item.view.* import kotlinx.android.synthetic.main.catalogue_mat_grid_item.view.*
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.Serializable
class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference<Boolean>) : class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference<Boolean>) :
AbstractFlexibleItem<LibraryHolder>(), IFilterable<String> { AbstractFlexibleItem<LibraryHolder>(), IFilterable<String> {
@ -30,21 +27,21 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
return if (libraryAsList.getOrDefault()) return if (libraryAsList.getOrDefault())
R.layout.catalogue_list_item R.layout.catalogue_list_item
else else
R.layout.catalogue_grid_item R.layout.catalogue_mat_grid_item
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LibraryHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LibraryHolder {
val parent = adapter.recyclerView val parent = adapter.recyclerView
return if (parent is AutofitRecyclerView) { return if (parent is AutofitRecyclerView) {
view.apply { view.apply {
val coverHeight = parent.itemWidth / 3 * 4 val coverHeight = (parent.itemWidth / 3 * 4f).toInt()
card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight) constraint_layout.minHeight = coverHeight
gradient.layoutParams = FrameLayout.LayoutParams(
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM)
} }
LibraryGridHolder(view, adapter) LibraryMatGridHolder(view, adapter as LibraryCategoryAdapter, parent.itemWidth - 22.dpToPx, parent
.spanCount)
} else { } else {
LibraryListHolder(view, adapter) LibraryListHolder(view, adapter as LibraryCategoryAdapter)
} }
} }
@ -52,7 +49,6 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
holder: LibraryHolder, holder: LibraryHolder,
position: Int, position: Int,
payloads: MutableList<Any?>?) { payloads: MutableList<Any?>?) {
holder.onSetValues(this) holder.onSetValues(this)
} }

@ -30,7 +30,7 @@ import kotlinx.android.synthetic.main.catalogue_list_item.unread_text
class LibraryListHolder( class LibraryListHolder(
private val view: View, private val view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>> adapter: LibraryCategoryAdapter
) : LibraryHolder(view, adapter) { ) : LibraryHolder(view, adapter) {
/** /**

@ -0,0 +1,99 @@
package eu.kanade.tachiyomi.ui.library
import android.view.View
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.signature.ObjectKey
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.util.system.getResourceColor
import kotlinx.android.synthetic.main.catalogue_mat_grid_item.*
import kotlinx.android.synthetic.main.catalogue_mat_grid_item.view.*
/**
* Class used to hold the displayed data of a manga in the library, like the cover or the title.
* All the elements from the layout file "item_catalogue_grid" are available in this class.
*
* @param view the inflated view for this holder.
* @param adapter the adapter handling this holder.
* @param listener a listener to react to single tap and long tap events.
* @constructor creates a new library holder.
*/
class LibraryMatGridHolder(
private val view: View,
adapter: LibraryCategoryAdapter,
var width:Int,
var rowCount: Int
) : LibraryHolder(view, adapter) {
/**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* holder with the given manga.
*
* @param item the manga item to bind.
*/
override fun onSetValues(item: LibraryItem) {
// Update the title of the manga.
title.text = item.manga.currentTitle()
// Update the unread count and its visibility.
val unread = item.manga.unread
// Update the subtitle of the manga with artist or the unread count
with(subtitle) {
text = when {
item.manga.unread > 0 -> when (item.unreadType) {
1 -> view.resources.getQuantityString(R.plurals.unread_count, unread, unread)
0 -> view.resources.getString(R.string.new_chapter)
else -> item.manga.originalAuthor()
}
else -> item.manga.originalAuthor()
}
setTextColor(
view.context.getResourceColor(
if (item.manga.unread > 0 && item.unreadType > -1) android.R.attr.colorAccent
else android.R.attr.textColorSecondary
)
)
}
play_button.visibility = if (unread > 0) View.VISIBLE else View.GONE
play_button.setOnClickListener { playButtonClicked() }
// Update the download count and its visibility.
with(download_text) {
visibility = if (item.downloadCount > 0) View.VISIBLE else View.GONE
text = item.downloadCount.toString()
}
// Set local visibility if its local manga
local_text.visibility = if (item.manga.source == LocalSource.ID) View.VISIBLE else View.GONE
// Update the cover.
if (item.manga.thumbnail_url == null) Glide.with(view.context).clear(cover_thumbnail)
else {
val id = item.manga.id ?: return
GlideApp.with(view.context).load(item.manga)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.signature(ObjectKey(MangaImpl.getLastCoverFetch(id).toString()))
.into(cover_thumbnail)
}
}
private fun playButtonClicked() {
adapter.libraryListener.startReading(adapterPosition)
}
override fun onActionStateChanged(position: Int, actionState: Int) {
super.onActionStateChanged(position, actionState)
if (actionState == 2) {
view.card.isDragged = true
}
}
override fun onItemReleased(position: Int) {
super.onItemReleased(position)
view.card.isDragged = false
}
}

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.library
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.models.MangaCategory
@ -16,6 +17,7 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.migration.MigrationFlags import eu.kanade.tachiyomi.ui.migration.MigrationFlags
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.removeArticles import eu.kanade.tachiyomi.util.lang.removeArticles
@ -27,16 +29,20 @@ import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Comp
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import rx.Observable import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.ArrayList import java.util.ArrayList
import java.util.Calendar
import java.util.Collections import java.util.Collections
import java.util.Comparator import java.util.Comparator
import java.util.Date
/** /**
* Class containing library information. * Class containing library information.
@ -78,6 +84,8 @@ class LibraryPresenter(
private var currentMangaMap:LibraryMap? = null private var currentMangaMap:LibraryMap? = null
private var readerSubscription: Subscription? = null
fun isDownloading() = downloadManager.hasQueue() fun isDownloading() = downloadManager.hasQueue()
fun onDestroy() { fun onDestroy() {
@ -684,6 +692,12 @@ class LibraryPresenter(
} }
} }
fun getFirstUnread(manga: Manga): Chapter? {
val chapters = db.getChapters(manga).executeAsBlocking()
return chapters.sortedByDescending { it.source_order }.find { !it.read }
}
private companion object { private companion object {
var currentLibrary:Library? = null var currentLibrary:Library? = null
} }

@ -34,6 +34,7 @@ import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsSingleChoice import com.afollestad.materialdialogs.list.listItemsSingleChoice
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import com.bumptech.glide.signature.ObjectKey import com.bumptech.glide.signature.ObjectKey
@ -333,6 +334,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
.load(manga) .load(manga)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString())) .signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString()))
.transition(DrawableTransitionOptions.withCrossFade())
//.centerCrop() //.centerCrop()
.into(manga_cover) .into(manga_cover)
if (manga_cover_full != null) { if (manga_cover_full != null) {
@ -356,6 +358,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
.load(manga) .load(manga)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString())) .signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString()))
.transition(DrawableTransitionOptions.withCrossFade())
.centerCrop() .centerCrop()
.into(backdrop) .into(backdrop)
} }

@ -119,20 +119,6 @@ class ReaderPresenter(
} }
} }
/**
* Called when the presenter is destroyed. It saves the current progress and cleans up
* references on the currently active chapters.
*/
override fun onDestroy() {
super.onDestroy()
val currentChapters = viewerChaptersRelay.value
if (currentChapters != null) {
currentChapters.unref()
saveChapterProgress(currentChapters.currChapter)
saveChapterHistory(currentChapters.currChapter)
}
}
/** /**
* Called when the presenter instance is being saved. It saves the currently active chapter * Called when the presenter instance is being saved. It saves the currently active chapter
* id and the last page read. * id and the last page read.
@ -152,6 +138,12 @@ class ReaderPresenter(
*/ */
fun onBackPressed() { fun onBackPressed() {
deletePendingChapters() deletePendingChapters()
val currentChapters = viewerChaptersRelay.value
if (currentChapters != null) {
currentChapters.unref()
saveChapterProgress(currentChapters.currChapter)
saveChapterHistory(currentChapters.currChapter)
}
} }
/** /**
@ -364,10 +356,7 @@ class ReaderPresenter(
*/ */
private fun saveChapterHistory(chapter: ReaderChapter) { private fun saveChapterHistory(chapter: ReaderChapter) {
val history = History.create(chapter.chapter).apply { last_read = Date().time } val history = History.create(chapter.chapter).apply { last_read = Date().time }
db.updateHistoryLastRead(history).asRxCompletable() db.updateHistoryLastRead(history).executeAsBlocking()
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
} }
/** /**

@ -9,7 +9,7 @@ import kotlin.math.max
class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
androidx.recyclerview.widget.RecyclerView(context, attrs) { androidx.recyclerview.widget.RecyclerView(context, attrs) {
private val manager = androidx.recyclerview.widget.GridLayoutManager(context, 1) private val manager = GridLayoutManager(context, 1)
private var columnWidth = -1 private var columnWidth = -1

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/selectorColor">
<item android:id="@android:id/mask"
android:top="0dp"
android:bottom="12dp"
android:left="4dp"
android:right="4dp">
<shape android:shape="rectangle">
<corners android:radius="4dp" />
<solid android:color="@color/selectorColor" />
</shape>
</item>
<item
android:top="0dp"
android:bottom="12dp"
android:left="4dp"
android:right="4dp">
<selector>
<item android:state_selected="true">
<shape android:shape="rectangle">
<corners android:radius="4dp" />
<solid android:color="@color/selectorColor" />
</shape>
</item>
<item android:state_activated="true">
<shape android:shape="rectangle">
<corners android:radius="4dp" />
<solid android:color="@color/selectorColor" />
</shape>
</item>
</selector>
</item>
</ripple>

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:background="#FFFF00"
android:color="?android:attr/colorAccent">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="?android:attr/colorAccent" />
<corners android:radius="13dp" />
</shape>
</item>
<item android:id="@android:id/background">
<shape
android:shape="rectangle">
<corners android:radius="16dp"/>
<size
android:height="32dp"
android:width="32dp" />
<solid android:color="#AD212121"/>
<stroke android:width="0.1dp" android:color="#EDEDED" />
</shape>
</item>
</ripple>

@ -3,5 +3,5 @@
<gradient <gradient
android:angle="90" android:angle="90"
android:endColor="@android:color/transparent" android:endColor="@android:color/transparent"
android:startColor="?android:attr/textColorSecondary"/> android:startColor="@color/md_black_1000_54"/>
</shape> </shape>

@ -0,0 +1,187 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:id="@+id/manga_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@drawable/library_item_selector"
android:minHeight="200dp"
android:orientation="vertical">
<com.google.android.material.card.MaterialCardView
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
app:layout_constraintBottom_toTopOf="@+id/title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0">
<ImageView
android:id="@+id/cover_thumbnail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:background="?android:attr/colorBackground"
android:maxHeight="250dp"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:background="?android:attr/colorBackground"
tools:ignore="ContentDescription"
tools:src="@mipmap/ic_launcher" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/badge_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp">
<TextView
android:id="@+id/local_text"
style="@style/TextAppearance.Regular.Caption.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/md_teal_500"
android:paddingStart="3dp"
android:paddingTop="1dp"
android:paddingEnd="3dp"
android:paddingBottom="1dp"
android:text="@string/local_source_badge"
android:textColor="@color/md_white_1000"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/download_text"
style="@style/TextAppearance.Regular.Caption.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="-10dp"
android:layout_marginTop="-10dp"
android:background="@drawable/dialog_rounded_background"
android:backgroundTint="@color/md_red_500"
android:gravity="start|center"
android:paddingStart="14dp"
android:paddingTop="10dp"
android:paddingEnd="5dp"
android:paddingBottom="3dp"
android:textColor="@color/md_white_1000"
android:textSize="13sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/local_text"
app:layout_constraintTop_toTopOf="parent"
tools:text="1" />
<TextView
android:id="@+id/bookmark_text"
style="@style/TextAppearance.Regular.Caption.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="-10dp"
android:layout_marginTop="-10dp"
android:background="@drawable/dialog_rounded_background"
android:backgroundTint="@color/md_blue_A400_87"
android:gravity="start|center"
android:paddingStart="14dp"
android:paddingTop="10dp"
android:paddingEnd="5dp"
android:paddingBottom="3dp"
android:textColor="@color/md_white_1000"
android:textSize="13sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/local_text"
app:layout_constraintTop_toTopOf="parent"
android:text="@string/in_library"
tools:visibility="visible" />
<ImageButton
android:id="@+id/play_button"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="end|bottom"
android:layout_marginEnd="6dp"
android:layout_marginBottom="6dp"
android:background="@drawable/round_play_background"
android:contentDescription="@string/start_reading"
android:src="@drawable/ic_play_arrow_white_24dp"
android:tint="@android:color/white"
android:visibility="gone"
tools:visibility="visible" />
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
style="@style/TextAppearance.Regular.Body1.Light"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="6dp"
android:ellipsize="end"
android:lineSpacingExtra="-4dp"
android:singleLine="true"
android:textColor="?android:attr/textColorPrimary"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Sample name\nsdf" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/subtitle"
style="@style/TextAppearance.Regular.Body1.Light"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="14dp"
android:ellipsize="end"
android:lineSpacingExtra="-4dp"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:singleLine="true"
android:textColor="?android:attr/textColorPrimary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Sample artist" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<eu.kanade.tachiyomi.widget.AutofitRecyclerView <eu.kanade.tachiyomi.widget.AutofitRecyclerView
android:id="@+id/catalogue_recycler"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
style="@style/Theme.Widget.GridView.Catalogue" style="@style/Theme.Widget.GridView.Catalogue"
@ -7,4 +8,4 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:columnWidth="140dp" android:columnWidth="140dp"
android:clipToPadding="false" android:clipToPadding="false"
tools:listitem="@layout/catalogue_grid_item" /> tools:listitem="@layout/catalogue_mat_grid_item" />

@ -33,10 +33,10 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="20dp" android:layout_height="20dp"
android:background="@drawable/shape_gradient_top_shadow" android:background="@drawable/shape_gradient_top_shadow"
android:backgroundTint="@color/md_black_1000_54"
android:paddingBottom="10dp" android:paddingBottom="10dp"
app:layout_anchorGravity="top" app:layout_anchorGravity="top"
app:layout_anchor="@id/bottom_sheet" /> app:layout_anchor="@id/bottom_sheet" />
<!-- Adding bottom sheet after main content --> <!-- Adding bottom sheet after main content -->
<include layout="@layout/filter_bottom_sheet"/> <include layout="@layout/filter_bottom_sheet"/>

@ -49,6 +49,7 @@
<!-- Text Colors --> <!-- Text Colors -->
<color name="md_black_1000_87">#DE000000</color> <color name="md_black_1000_87">#DE000000</color>
<color name="md_black_1000_70">#B3000000</color>
<color name="md_black_1000_54">#8A000000</color> <color name="md_black_1000_54">#8A000000</color>
<color name="md_black_1000_38">#61000000</color> <color name="md_black_1000_38">#61000000</color>
<color name="md_black_1000_12">#1F000000</color> <color name="md_black_1000_12">#1F000000</color>
@ -71,10 +72,13 @@
<color name="md_blue_A200_50">#80448AFF</color> <color name="md_blue_A200_50">#80448AFF</color>
<color name="md_blue_A400">#2979FF</color> <color name="md_blue_A400">#2979FF</color>
<color name="md_blue_A400_87">#DE2979FF</color>
<color name="md_blue_A400_38">#612979FF</color> <color name="md_blue_A400_38">#612979FF</color>
<color name="md_red_500">#F44336</color> <color name="md_red_500">#F44336</color>
<color name="md_red_500_87">#DEF44336</color>
<color name="md_teal_500">#009688</color> <color name="md_teal_500">#009688</color>
</resources> </resources>

@ -409,6 +409,12 @@
<string name="category_already_in_queue">%1$s is already in queue</string> <string name="category_already_in_queue">%1$s is already in queue</string>
<string name="local_source_badge">Local</string> <string name="local_source_badge">Local</string>
<string name="confirm_manga_deletion">Remove from library?</string> <string name="confirm_manga_deletion">Remove from library?</string>
<plurals name="unread_count">
<item quantity="one">New chapter</item>
<item quantity="other">%d Unread</item>
</plurals>
<string name="new_chapter">New</string>
<string name="start_reading">Start Reading</string>
<!-- Catalogue fragment --> <!-- Catalogue fragment -->
<string name="source_search_options">Search filters</string> <string name="source_search_options">Search filters</string>
@ -419,6 +425,7 @@
<string name="action_global_search_hint">Global search…</string> <string name="action_global_search_hint">Global search…</string>
<string name="latest">Latest</string> <string name="latest">Latest</string>
<string name="browse">Browse</string> <string name="browse">Browse</string>
<string name="in_library">In Library</string>
<!-- Manga activity --> <!-- Manga activity -->
<string name="manga_not_in_db">This manga has been removed from the database.</string> <string name="manga_not_in_db">This manga has been removed from the database.</string>

Loading…
Cancel
Save