Comfortable Grid (#3238)

* Comfortable Grid

* Add requested changes

* Add more requested changes
This commit is contained in:
jobobby04 2020-05-25 13:39:14 -04:00 committed by GitHub
parent f05b99ec1f
commit 52e82b3548
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 383 additions and 72 deletions

View File

@ -73,7 +73,7 @@ object PreferenceKeys {
const val lastUsedCategory = "last_used_category" const val lastUsedCategory = "last_used_category"
const val catalogueAsList = "pref_display_catalogue_as_list" const val catalogueDisplayMode = "pref_display_catalogue_display_mode"
const val enabledLanguages = "source_languages" const val enabledLanguages = "source_languages"
@ -131,7 +131,7 @@ object PreferenceKeys {
const val downloadNewCategories = "download_new_categories" const val downloadNewCategories = "download_new_categories"
const val libraryAsList = "pref_display_library_as_list" const val libraryDisplayMode = "pref_display_library_display_mode"
const val lang = "app_language" const val lang = "app_language"

View File

@ -15,4 +15,8 @@ object PreferenceValues {
const val THEME_DARK_DEFAULT = "default" const val THEME_DARK_DEFAULT = "default"
const val THEME_DARK_BLUE = "blue" const val THEME_DARK_BLUE = "blue"
const val THEME_DARK_AMOLED = "amoled" const val THEME_DARK_AMOLED = "amoled"
const val DISPLAY_COMPACT_GRID = 0
const val DISPLAY_LIST = 1
const val DISPLAY_COMFORTABLE_GRID = 2
} }

View File

@ -7,6 +7,7 @@ import androidx.preference.PreferenceManager
import com.tfcporciuncula.flow.FlowSharedPreferences import com.tfcporciuncula.flow.FlowSharedPreferences
import com.tfcporciuncula.flow.Preference import com.tfcporciuncula.flow.Preference
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DISPLAY_COMPACT_GRID
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
@ -138,7 +139,7 @@ class PreferencesHelper(val context: Context) {
fun lastVersionCode() = flowPrefs.getInt("last_version_code", 0) fun lastVersionCode() = flowPrefs.getInt("last_version_code", 0)
fun catalogueAsList() = flowPrefs.getBoolean(Keys.catalogueAsList, false) fun catalogueDisplayMode() = flowPrefs.getInt(Keys.catalogueDisplayMode, DISPLAY_COMPACT_GRID)
fun enabledLanguages() = flowPrefs.getStringSet(Keys.enabledLanguages, setOf("en", Locale.getDefault().language)) fun enabledLanguages() = flowPrefs.getStringSet(Keys.enabledLanguages, setOf("en", Locale.getDefault().language))
@ -184,7 +185,7 @@ class PreferencesHelper(val context: Context) {
fun libraryUpdatePrioritization() = flowPrefs.getInt(Keys.libraryUpdatePrioritization, 0) fun libraryUpdatePrioritization() = flowPrefs.getInt(Keys.libraryUpdatePrioritization, 0)
fun libraryAsList() = flowPrefs.getBoolean(Keys.libraryAsList, false) fun libraryDisplayMode() = flowPrefs.getInt(Keys.libraryDisplayMode, DISPLAY_COMPACT_GRID)
fun downloadBadge() = flowPrefs.getBoolean(Keys.downloadBadge, false) fun downloadBadge() = flowPrefs.getBoolean(Keys.downloadBadge, false)

View File

@ -22,6 +22,9 @@ import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DISPLAY_COMFORTABLE_GRID
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DISPLAY_COMPACT_GRID
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DISPLAY_LIST
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.asImmediateFlow import eu.kanade.tachiyomi.data.preference.asImmediateFlow
import eu.kanade.tachiyomi.databinding.SourceControllerBinding import eu.kanade.tachiyomi.databinding.SourceControllerBinding
@ -187,7 +190,7 @@ open class BrowseSourceController(bundle: Bundle) :
binding.catalogueView.removeView(oldRecycler) binding.catalogueView.removeView(oldRecycler)
} }
val recycler = if (presenter.isListMode) { val recycler = if (preferences.catalogueDisplayMode().get() == DISPLAY_LIST) {
RecyclerView(view.context).apply { RecyclerView(view.context).apply {
id = R.id.recycler id = R.id.recycler
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
@ -205,7 +208,7 @@ open class BrowseSourceController(bundle: Bundle) :
(layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { (layoutManager as GridLayoutManager).spanSizeLookup = object : 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.source_grid_item, null -> 1 R.layout.source_grid_item, R.layout.source_comfortable_grid_item, null -> 1
else -> spanCount else -> spanCount
} }
} }
@ -266,15 +269,13 @@ open class BrowseSourceController(bundle: Bundle) :
} }
) )
// Show next display mode val displayItem = when (preferences.catalogueDisplayMode().get()) {
menu.findItem(R.id.action_display_mode).apply { DISPLAY_COMPACT_GRID -> R.id.action_compact_grid
val icon = if (presenter.isListMode) { DISPLAY_LIST -> R.id.action_list
R.drawable.ic_view_module_24dp DISPLAY_COMFORTABLE_GRID -> R.id.action_comfortable_grid
} else { else -> throw NotImplementedError("Unimplemented display")
R.drawable.ic_view_list_24dp
}
setIcon(icon)
} }
menu.findItem(displayItem).isChecked = true
} }
override fun onPrepareOptionsMenu(menu: Menu) { override fun onPrepareOptionsMenu(menu: Menu) {
@ -290,7 +291,9 @@ open class BrowseSourceController(bundle: Bundle) :
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_search -> expandActionViewFromInteraction = true R.id.action_search -> expandActionViewFromInteraction = true
R.id.action_display_mode -> swapDisplayMode() R.id.action_compact_grid -> setDisplayMode(DISPLAY_COMPACT_GRID)
R.id.action_list -> setDisplayMode(DISPLAY_LIST)
R.id.action_comfortable_grid -> setDisplayMode(DISPLAY_COMFORTABLE_GRID)
R.id.action_open_in_web_view -> openInWebView() R.id.action_open_in_web_view -> openInWebView()
R.id.action_local_source_help -> openLocalSourceHelpGuide() R.id.action_local_source_help -> openLocalSourceHelpGuide()
} }
@ -433,17 +436,19 @@ open class BrowseSourceController(bundle: Bundle) :
} }
/** /**
* Swaps the current display mode. * Sets the current display mode.
*
* @param mode the mode to change to
*/ */
private fun swapDisplayMode() { private fun setDisplayMode(mode: Int) {
val view = view ?: return val view = view ?: return
val adapter = adapter ?: return val adapter = adapter ?: return
presenter.swapDisplayMode() preferences.catalogueDisplayMode().set(mode)
val isListMode = presenter.isListMode presenter.refreshDisplayMode()
activity?.invalidateOptionsMenu() activity?.invalidateOptionsMenu()
setupRecycler(view) setupRecycler(view)
if (!isListMode || !view.context.connectivityManager.isActiveNetworkMetered) { if (mode == DISPLAY_LIST || !view.context.connectivityManager.isActiveNetworkMetered) {
// Initialize mangas if going to grid view or if over wifi when going to list view // Initialize mangas if going to grid view or if over wifi when going to list view
val mangas = (0 until adapter.itemCount).mapNotNull { val mangas = (0 until adapter.itemCount).mapNotNull {
(adapter.getItem(it) as? SourceItem)?.manga (adapter.getItem(it) as? SourceItem)?.manga

View File

@ -87,12 +87,6 @@ open class BrowseSourcePresenter(
*/ */
private val mangaDetailSubject = PublishSubject.create<List<Manga>>() private val mangaDetailSubject = PublishSubject.create<List<Manga>>()
/**
* Whether the view is in list mode or not.
*/
var isListMode: Boolean = false
private set
/** /**
* Subscription for the pager. * Subscription for the pager.
*/ */
@ -119,7 +113,6 @@ open class BrowseSourcePresenter(
query = savedState.getString(::query.name, "") query = savedState.getString(::query.name, "")
} }
isListMode = prefs.catalogueAsList().get()
restartPager() restartPager()
} }
@ -145,7 +138,7 @@ open class BrowseSourcePresenter(
val sourceId = source.id val sourceId = source.id
val catalogueAsList = prefs.catalogueAsList() val catalogueDisplayMode = prefs.catalogueDisplayMode()
// Prepare the pager. // Prepare the pager.
pagerSubscription?.let { remove(it) } pagerSubscription?.let { remove(it) }
@ -153,7 +146,7 @@ open class BrowseSourcePresenter(
.observeOn(Schedulers.io()) .observeOn(Schedulers.io())
.map { pair -> pair.first to pair.second.map { networkToLocalManga(it, sourceId) } } .map { pair -> pair.first to pair.second.map { networkToLocalManga(it, sourceId) } }
.doOnNext { initializeMangas(it.second) } .doOnNext { initializeMangas(it.second) }
.map { pair -> pair.first to pair.second.map { SourceItem(it, catalogueAsList) } } .map { pair -> pair.first to pair.second.map { SourceItem(it, catalogueDisplayMode) } }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeReplay( .subscribeReplay(
{ view, (page, mangas) -> { view, (page, mangas) ->
@ -273,12 +266,9 @@ open class BrowseSourcePresenter(
} }
/** /**
* Changes the active display mode. * Refreshes the active display mode.
*/ */
fun swapDisplayMode() { fun refreshDisplayMode() {
val mode = !isListMode
prefs.catalogueAsList().set(mode)
isListMode = mode
subscribeToMangaInitializer() subscribeToMangaInitializer()
} }

View File

@ -0,0 +1,56 @@
package eu.kanade.tachiyomi.ui.browse.source.browse
import android.view.View
import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
import eu.kanade.tachiyomi.widget.StateImageViewTarget
import kotlinx.android.synthetic.main.source_comfortable_grid_item.card
import kotlinx.android.synthetic.main.source_comfortable_grid_item.progress
import kotlinx.android.synthetic.main.source_comfortable_grid_item.thumbnail
import kotlinx.android.synthetic.main.source_comfortable_grid_item.title
/**
* Class used to hold the displayed data of a manga in the catalogue, like the cover or the title.
* All the elements from the layout file "item_source_grid" are available in this class.
*
* @param view the inflated view for this holder.
* @param adapter the adapter handling this holder.
* @constructor creates a new catalogue holder.
*/
class SourceComfortableGridHolder(private val view: View, private val adapter: FlexibleAdapter<*>) :
SourceGridHolder(view, adapter) {
/**
* Method called from [CatalogueAdapter.onBindViewHolder]. It updates the data for this
* holder with the given manga.
*
* @param manga the manga to bind.
*/
override fun onSetValues(manga: Manga) {
// Set manga title
title.text = manga.title
// Set alpha of thumbnail.
thumbnail.alpha = if (manga.favorite) 0.3f else 1.0f
setImage(manga)
}
override fun setImage(manga: Manga) {
// Setting this via XML doesn't work
card.clipToOutline = true
GlideApp.with(view.context).clear(thumbnail)
if (!manga.thumbnail_url.isNullOrEmpty()) {
GlideApp.with(view.context)
.load(manga.toMangaThumbnail())
.diskCacheStrategy(DiskCacheStrategy.DATA)
.centerCrop()
.placeholder(android.R.color.transparent)
.into(StateImageViewTarget(thumbnail, progress))
}
}
}

View File

@ -20,7 +20,7 @@ import kotlinx.android.synthetic.main.source_grid_item.title
* @param adapter the adapter handling this holder. * @param adapter the adapter handling this holder.
* @constructor creates a new catalogue holder. * @constructor creates a new catalogue holder.
*/ */
class SourceGridHolder(private val view: View, private val adapter: FlexibleAdapter<*>) : open class SourceGridHolder(private val view: View, private val adapter: FlexibleAdapter<*>) :
SourceHolder(view, adapter) { SourceHolder(view, adapter) {
/** /**

View File

@ -4,6 +4,7 @@ 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.widget.FrameLayout import android.widget.FrameLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.tfcporciuncula.flow.Preference import com.tfcporciuncula.flow.Preference
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
@ -11,18 +12,20 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DISPLAY_COMPACT_GRID
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DISPLAY_LIST
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.android.synthetic.main.source_grid_item.view.card import kotlinx.android.synthetic.main.source_grid_item.view.card
import kotlinx.android.synthetic.main.source_grid_item.view.gradient import kotlinx.android.synthetic.main.source_grid_item.view.gradient
class SourceItem(val manga: Manga, private val catalogueAsList: Preference<Boolean>) : class SourceItem(val manga: Manga, private val catalogueDisplayMode: Preference<Int>) :
AbstractFlexibleItem<SourceHolder>() { AbstractFlexibleItem<SourceHolder>() {
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return if (catalogueAsList.get()) { return when (catalogueDisplayMode.get()) {
R.layout.source_list_item DISPLAY_COMPACT_GRID -> R.layout.source_grid_item
} else { DISPLAY_LIST -> R.layout.source_list_item
R.layout.source_grid_item else -> R.layout.source_comfortable_grid_item
} }
} }
@ -32,15 +35,28 @@ class SourceItem(val manga: Manga, private val catalogueAsList: Preference<Boole
): SourceHolder { ): SourceHolder {
val parent = adapter.recyclerView val parent = adapter.recyclerView
return if (parent is AutofitRecyclerView) { return if (parent is AutofitRecyclerView) {
val coverHeight = parent.itemWidth / 3 * 4
if (catalogueDisplayMode.get() == DISPLAY_COMPACT_GRID) {
view.apply { view.apply {
card.layoutParams = FrameLayout.LayoutParams( card.layoutParams = FrameLayout.LayoutParams(
MATCH_PARENT, parent.itemWidth / 3 * 4 MATCH_PARENT, coverHeight
) )
gradient.layoutParams = FrameLayout.LayoutParams( gradient.layoutParams = FrameLayout.LayoutParams(
MATCH_PARENT, parent.itemWidth / 3 * 4 / 2, Gravity.BOTTOM MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM
) )
} }
SourceGridHolder(view, adapter) SourceGridHolder(view, adapter)
} else {
view.apply {
card.layoutParams = ConstraintLayout.LayoutParams(
MATCH_PARENT, coverHeight
)
gradient.layoutParams = FrameLayout.LayoutParams(
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM
)
}
SourceComfortableGridHolder(view, adapter)
}
} else { } else {
SourceListHolder(view, adapter) SourceListHolder(view, adapter)
} }

View File

@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DISPLAY_LIST
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.lang.plusAssign import eu.kanade.tachiyomi.util.lang.plusAssign
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
@ -72,7 +73,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
fun onCreate(controller: LibraryController) { fun onCreate(controller: LibraryController) {
this.controller = controller this.controller = controller
recycler = if (preferences.libraryAsList().get()) { recycler = if (preferences.libraryDisplayMode().get() == DISPLAY_LIST) {
(swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply { (swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply {
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
} }

View File

@ -0,0 +1,68 @@
package eu.kanade.tachiyomi.ui.library
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.view.visibleIf
import kotlinx.android.synthetic.main.source_comfortable_grid_item.card
import kotlinx.android.synthetic.main.source_comfortable_grid_item.download_text
import kotlinx.android.synthetic.main.source_comfortable_grid_item.local_text
import kotlinx.android.synthetic.main.source_comfortable_grid_item.thumbnail
import kotlinx.android.synthetic.main.source_comfortable_grid_item.title
import kotlinx.android.synthetic.main.source_comfortable_grid_item.unread_text
/**
* 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_source_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 LibraryComfortableGridHolder(
private val view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
) : LibraryGridHolder(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.title
// Update the unread count and its visibility.
with(unread_text) {
visibleIf { item.unreadCount > 0 }
text = item.unreadCount.toString()
}
// Update the download count and its visibility.
with(download_text) {
visibleIf { item.downloadCount > 0 }
text = item.downloadCount.toString()
}
// set local visibility if its local manga
local_text.visibleIf { item.manga.isLocal() }
// Setting this via XML doesn't work
card.clipToOutline = true
// Update the cover.
GlideApp.with(view.context).clear(thumbnail)
GlideApp.with(view.context)
.load(item.manga.toMangaThumbnail())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.dontAnimate()
.into(thumbnail)
}
}

View File

@ -23,7 +23,7 @@ import kotlinx.android.synthetic.main.source_grid_item.unread_text
* @param listener a listener to react to single tap and long tap events. * @param listener a listener to react to single tap and long tap events.
* @constructor creates a new library holder. * @constructor creates a new library holder.
*/ */
class LibraryGridHolder( open class LibraryGridHolder(
private val view: View, private val view: View,
private val adapter: FlexibleAdapter<*> private val adapter: FlexibleAdapter<*>
) : LibraryHolder(view, adapter) { ) : LibraryHolder(view, adapter) {

View File

@ -4,6 +4,7 @@ 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.widget.FrameLayout import android.widget.FrameLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.tfcporciuncula.flow.Preference import com.tfcporciuncula.flow.Preference
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
@ -12,6 +13,8 @@ import eu.davidea.flexibleadapter.items.IFilterable
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R 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.PreferenceValues.DISPLAY_COMPACT_GRID
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DISPLAY_LIST
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.android.synthetic.main.source_grid_item.view.card import kotlinx.android.synthetic.main.source_grid_item.view.card
@ -19,7 +22,7 @@ import kotlinx.android.synthetic.main.source_grid_item.view.gradient
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference<Boolean>) : class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Preference<Int>) :
AbstractFlexibleItem<LibraryHolder>(), IFilterable<String> { AbstractFlexibleItem<LibraryHolder>(), IFilterable<String> {
private val sourceManager: SourceManager = Injekt.get() private val sourceManager: SourceManager = Injekt.get()
@ -28,24 +31,36 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
var unreadCount = -1 var unreadCount = -1
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return if (libraryAsList.get()) { return when (libraryDisplayMode.get()) {
R.layout.source_list_item DISPLAY_COMPACT_GRID -> R.layout.source_grid_item
} else { DISPLAY_LIST -> R.layout.source_list_item
R.layout.source_grid_item else -> R.layout.source_comfortable_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 {
val coverHeight = parent.itemWidth / 3 * 4 val coverHeight = parent.itemWidth / 3 * 4
if (libraryDisplayMode.get() == DISPLAY_COMPACT_GRID) {
view.apply {
card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight) card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight)
gradient.layoutParams = FrameLayout.LayoutParams( gradient.layoutParams = FrameLayout.LayoutParams(
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM
) )
} }
LibraryGridHolder(view, adapter) LibraryGridHolder(view, adapter)
} else {
view.apply {
card.layoutParams = ConstraintLayout.LayoutParams(
MATCH_PARENT, coverHeight
)
gradient.layoutParams = FrameLayout.LayoutParams(
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM
)
}
LibraryComfortableGridHolder(view, adapter)
}
} else { } else {
LibraryListHolder(view, adapter) LibraryListHolder(view, adapter)
} }

View File

@ -266,10 +266,10 @@ class LibraryPresenter(
* value. * value.
*/ */
private fun getLibraryMangasObservable(): Observable<LibraryMap> { private fun getLibraryMangasObservable(): Observable<LibraryMap> {
val libraryAsList = preferences.libraryAsList() val libraryDisplayMode = preferences.libraryDisplayMode()
return db.getLibraryMangas().asRxObservable() return db.getLibraryMangas().asRxObservable()
.map { list -> .map { list ->
list.map { LibraryItem(it, libraryAsList) }.groupBy { it.manga.category } list.map { LibraryItem(it, libraryDisplayMode) }.groupBy { it.manga.category }
} }
} }

View File

@ -5,6 +5,9 @@ import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DISPLAY_COMFORTABLE_GRID
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DISPLAY_COMPACT_GRID
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DISPLAY_LIST
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.widget.ExtendedNavigationView import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import eu.kanade.tachiyomi.widget.TabbedBottomSheetDialog import eu.kanade.tachiyomi.widget.TabbedBottomSheetDialog
@ -183,16 +186,18 @@ class LibrarySettingsSheet(
inner class DisplayGroup : Group { inner class DisplayGroup : Group {
private val grid = Item.Radio(R.string.action_display_grid, this) private val grid = Item.Radio(R.string.action_display_grid, this)
private val comfortableGrid = Item.Radio(R.string.action_display_comfortable_grid, this)
private val list = Item.Radio(R.string.action_display_list, this) private val list = Item.Radio(R.string.action_display_list, this)
override val header = null override val header = null
override val items = listOf(grid, list) override val items = listOf(grid, comfortableGrid, list)
override val footer = null override val footer = null
override fun initModels() { override fun initModels() {
val asList = preferences.libraryAsList().get() val mode = preferences.libraryDisplayMode().get()
grid.checked = !asList grid.checked = mode == DISPLAY_COMPACT_GRID
list.checked = asList list.checked = mode == DISPLAY_LIST
comfortableGrid.checked = mode == DISPLAY_COMFORTABLE_GRID
} }
override fun onItemClicked(item: Item) { override fun onItemClicked(item: Item) {
@ -202,7 +207,13 @@ class LibrarySettingsSheet(
item.group.items.forEach { (it as Item.Radio).checked = false } item.group.items.forEach { (it as Item.Radio).checked = false }
item.checked = true item.checked = true
preferences.libraryAsList().set(item == list) preferences.libraryDisplayMode().set(
when (item) {
grid -> DISPLAY_COMPACT_GRID
list -> DISPLAY_LIST
else -> DISPLAY_COMFORTABLE_GRID
}
)
item.group.items.forEach { adapter.notifyItemChanged(it) } item.group.items.forEach { adapter.notifyItemChanged(it) }
} }

View File

@ -0,0 +1,130 @@
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/library_item_selector"
android:padding="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rounded_rectangle">
<FrameLayout
android:id="@+id/card"
android:layout_width="wrap_content"
android:layout_height="220dp"
android:background="@drawable/rounded_rectangle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
tools:ignore="ContentDescription"
tools:src="@mipmap/ic_launcher" />
<View
android:id="@+id/gradient"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@drawable/gradient_shape" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="7dp"
tools:layout_editor_absoluteY="7dp">
<TextView
android:id="@+id/unread_text"
style="@style/TextAppearance.Regular.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:background="@color/colorAccentDark"
android:paddingStart="3dp"
android:paddingTop="1dp"
android:paddingEnd="3dp"
android:paddingBottom="1dp"
android:textColor="@color/md_white_1000"
android:visibility="gone"
app:layout_constraintStart_toEndOf="@+id/download_text"
app:layout_constraintTop_toTopOf="parent"
tools:text="120"
tools:visibility="visible" />
<TextView
android:id="@+id/download_text"
style="@style/TextAppearance.Regular.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:background="@color/md_red_500"
android:paddingStart="3dp"
android:paddingTop="1dp"
android:paddingEnd="3dp"
android:paddingBottom="1dp"
android:textColor="@color/md_white_1000"
android:visibility="gone"
app:layout_constraintStart_toEndOf="@+id/local_text"
app:layout_constraintTop_toTopOf="parent"
tools:text="120"
tools:visibility="visible" />
<TextView
android:id="@+id/local_text"
style="@style/TextAppearance.Regular.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
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_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
<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" />
</FrameLayout>
<TextView
android:id="@+id/title"
style="@style/TextAppearance.Regular.Body1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|start|end"
android:ellipsize="end"
android:fontFamily="@font/ptsans_narrow_bold"
android:lineSpacingExtra="-4dp"
android:maxLines="2"
android:padding="8dp"
android:shadowColor="@color/textColorPrimaryLight"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/card"
tools:text="Sample name" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -10,11 +10,24 @@
app:showAsAction="collapseActionView|ifRoom" /> app:showAsAction="collapseActionView|ifRoom" />
<item <item
android:id="@+id/action_display_mode"
android:icon="@drawable/ic_view_module_24dp" android:icon="@drawable/ic_view_module_24dp"
android:title="@string/action_display_mode" android:title="@string/action_display_mode"
app:iconTint="?attr/colorOnPrimary" app:iconTint="?attr/colorOnPrimary"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom">
<menu>
<group android:checkableBehavior="single">
<item
android:id="@+id/action_compact_grid"
android:title="@string/action_display_grid" />
<item
android:id="@+id/action_comfortable_grid"
android:title="@string/action_display_comfortable_grid" />
<item
android:id="@+id/action_list"
android:title="@string/action_display_list" />
</group>
</menu>
</item>
<item <item
android:id="@+id/action_open_in_web_view" android:id="@+id/action_open_in_web_view"

View File

@ -81,8 +81,9 @@
<string name="action_migrate">Migrate</string> <string name="action_migrate">Migrate</string>
<string name="action_display_mode">Display mode</string> <string name="action_display_mode">Display mode</string>
<string name="action_display">Display</string> <string name="action_display">Display</string>
<string name="action_display_grid">Grid</string> <string name="action_display_grid">Compact grid</string>
<string name="action_display_list">List</string> <string name="action_display_list">List</string>
<string name="action_display_comfortable_grid">Comfortable grid</string>
<string name="action_display_download_badge">Download badges</string> <string name="action_display_download_badge">Download badges</string>
<string name="action_display_unread_badge">Unread badges</string> <string name="action_display_unread_badge">Unread badges</string>
<string name="action_hide">Hide</string> <string name="action_hide">Hide</string>