Merge branch 'upstream'

This commit is contained in:
jmir1 2021-06-05 23:56:25 +02:00
commit 8f64dca946
26 changed files with 342 additions and 109 deletions

View file

@ -12,8 +12,21 @@ interface Category : Serializable {
var flags: Int var flags: Int
private fun setFlags(flag: Int, mask: Int) {
flags = flags and mask.inv() or (flag and mask)
}
var displayMode: Int
get() = flags and MASK
set(mode) = setFlags(mode, MASK)
companion object { companion object {
const val COMPACT_GRID = 0b00000000
const val COMFORTABLE_GRID = 0b00000001
const val LIST = 0b00000010
const val MASK = 0b00000011
fun create(name: String): Category = CategoryImpl().apply { fun create(name: String): Category = CategoryImpl().apply {
this.name = name this.name = name
} }

View file

@ -214,6 +214,8 @@ object PreferenceKeys {
const val defaultCategory = "default_category" const val defaultCategory = "default_category"
const val categorizedDisplay = "categorized_display"
const val skipRead = "skip_read" const val skipRead = "skip_read"
const val skipFiltered = "skip_filtered" const val skipFiltered = "skip_filtered"

View file

@ -330,6 +330,8 @@ class PreferencesHelper(val context: Context) {
fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1) fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1)
fun categorisedDisplaySettings() = flowPrefs.getBoolean(Keys.categorizedDisplay, false)
fun skipRead() = prefs.getBoolean(Keys.skipRead, false) fun skipRead() = prefs.getBoolean(Keys.skipRead, false)
fun skipFiltered() = prefs.getBoolean(Keys.skipFiltered, true) fun skipFiltered() = prefs.getBoolean(Keys.skipFiltered, true)

View file

@ -5,7 +5,6 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dev.chrisbanes.insetter.applyInsetter import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.SelectableAdapter
@ -56,7 +55,7 @@ class AnimelibCategoryView @JvmOverloads constructor(context: Context, attrs: At
/** /**
* Recycler view of the list of anime. * Recycler view of the list of anime.
*/ */
private lateinit var recycler: RecyclerView private lateinit var recycler: AutofitRecyclerView
/** /**
* Adapter to hold the anime in this category. * Adapter to hold the anime in this category.
@ -73,9 +72,11 @@ class AnimelibCategoryView @JvmOverloads constructor(context: Context, attrs: At
fun onCreate(controller: AnimelibController, binding: AnimelibCategoryBinding) { fun onCreate(controller: AnimelibController, binding: AnimelibCategoryBinding) {
this.controller = controller this.controller = controller
recycler = if (preferences.animelibDisplayMode().get() == DisplayMode.LIST) { recycler = if (preferences.libraryDisplayMode().get() == DisplayMode.LIST &&
(binding.swipeRefresh.inflate(R.layout.animelib_list_recycler) as RecyclerView).apply { !preferences.categorisedDisplaySettings().get()
layoutManager = LinearLayoutManager(context) ) {
(binding.swipeRefresh.inflate(R.layout.library_list_recycler) as AutofitRecyclerView).apply {
spanCount = 1
} }
} else { } else {
(binding.swipeRefresh.inflate(R.layout.animelib_grid_recycler) as AutofitRecyclerView).apply { (binding.swipeRefresh.inflate(R.layout.animelib_grid_recycler) as AutofitRecyclerView).apply {
@ -122,6 +123,15 @@ class AnimelibCategoryView @JvmOverloads constructor(context: Context, attrs: At
fun onBind(category: Category) { fun onBind(category: Category) {
this.category = category this.category = category
// If displayMode should be set from category adjust manga count per row
if (preferences.categorisedDisplaySettings().get()) {
recycler.spanCount = if (category.displayMode == Category.LIST || (preferences.animelibDisplayMode().get() == DisplayMode.LIST && category.id == 0)) {
1
} else {
controller.animePerRow
}
}
adapter.mode = if (controller.selectedAnimes.isNotEmpty()) { adapter.mode = if (controller.selectedAnimes.isNotEmpty()) {
SelectableAdapter.Mode.MULTI SelectableAdapter.Mode.MULTI
} else { } else {

View file

@ -262,7 +262,9 @@ class AnimelibController(
} }
fun showSettingsSheet() { fun showSettingsSheet() {
settingsSheet?.show() adapter?.categories?.get(binding.animelibPager.currentItem)?.let { category ->
settingsSheet?.show(category)
}
} }
fun onNextAnimelibUpdate(categories: List<Category>, animeMap: Map<Int, List<AnimelibItem>>) { fun onNextAnimelibUpdate(categories: List<Category>, animeMap: Map<Int, List<AnimelibItem>>) {

View file

@ -21,17 +21,34 @@ import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class AnimelibItem(val anime: AnimelibAnime, private val animelibDisplayMode: Preference<DisplayMode>) : class AnimelibItem(
val anime: AnimelibAnime,
private val shouldSetFromCategory: Preference<Boolean>,
private val defaultLibraryDisplayMode: Preference<DisplayMode>
) :
AbstractFlexibleItem<AnimelibHolder<*>>(), IFilterable<String> { AbstractFlexibleItem<AnimelibHolder<*>>(), IFilterable<String> {
private val sourceManager: AnimeSourceManager = Injekt.get() private val sourceManager: AnimeSourceManager = Injekt.get()
var displayMode: Int = -1
var downloadCount = -1 var downloadCount = -1
var unreadCount = -1 var unreadCount = -1
var isLocal = false var isLocal = false
private fun getDisplayMode(): DisplayMode {
return if (shouldSetFromCategory.get() && anime.category != 0) {
if (displayMode != -1) {
DisplayMode.values()[displayMode]
} else {
DisplayMode.COMPACT_GRID
}
} else {
defaultLibraryDisplayMode.get()
}
}
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return when (animelibDisplayMode.get()) { return when (getDisplayMode()) {
DisplayMode.COMPACT_GRID -> R.layout.source_compact_grid_item DisplayMode.COMPACT_GRID -> R.layout.source_compact_grid_item
DisplayMode.COMFORTABLE_GRID -> R.layout.source_comfortable_grid_item DisplayMode.COMFORTABLE_GRID -> R.layout.source_comfortable_grid_item
DisplayMode.LIST -> R.layout.source_list_item DisplayMode.LIST -> R.layout.source_list_item
@ -39,7 +56,7 @@ class AnimelibItem(val anime: AnimelibAnime, private val animelibDisplayMode: Pr
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): AnimelibHolder<*> { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): AnimelibHolder<*> {
return when (animelibDisplayMode.get()) { return when (getDisplayMode()) {
DisplayMode.COMPACT_GRID -> { DisplayMode.COMPACT_GRID -> {
val binding = SourceCompactGridItemBinding.bind(view) val binding = SourceCompactGridItemBinding.bind(view)
val parent = adapter.recyclerView as AutofitRecyclerView val parent = adapter.recyclerView as AutofitRecyclerView

View file

@ -312,6 +312,13 @@ class AnimelibPresenter(
dbCategories dbCategories
} }
animelibAnime.forEach { (categoryId, animelibAnime) ->
val category = categories.first { category -> category.id == categoryId }
animelibAnime.forEach { libraryItem ->
libraryItem.displayMode = category.displayMode
}
}
this.categories = categories this.categories = categories
Animelib(categories, animelibAnime) Animelib(categories, animelibAnime)
} }
@ -333,10 +340,18 @@ class AnimelibPresenter(
* value. * value.
*/ */
private fun getAnimelibAnimesObservable(): Observable<AnimelibMap> { private fun getAnimelibAnimesObservable(): Observable<AnimelibMap> {
val animelibDisplayMode = preferences.animelibDisplayMode() val defaultLibraryDisplayMode = preferences.libraryDisplayMode()
val shouldSetFromCategory = preferences.categorisedDisplaySettings()
return db.getAnimelibAnimes().asRxObservable() return db.getAnimelibAnimes().asRxObservable()
.map { list -> .map { list ->
list.map { AnimelibItem(it, animelibDisplayMode) }.groupBy { it.anime.category } list.map { animelibAnime ->
// Display mode based on user preference: take it from global library setting or category
AnimelibItem(
animelibAnime,
shouldSetFromCategory,
defaultLibraryDisplayMode
)
}.groupBy { it.anime.category }
} }
} }

View file

@ -5,6 +5,8 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.Router
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
@ -25,6 +27,7 @@ class AnimelibSettingsSheet(
val filters: Filter val filters: Filter
private val sort: Sort private val sort: Sort
private val display: Display private val display: Display
private val db: AnimeDatabaseHelper by injectLazy()
init { init {
filters = Filter(router.activity!!) filters = Filter(router.activity!!)
@ -37,6 +40,16 @@ class AnimelibSettingsSheet(
display.onGroupClicked = onGroupClickListener display.onGroupClicked = onGroupClickListener
} }
/**
* adjusts selected button to match real state.
* @param currentCategory ID of currently shown category
*/
fun show(currentCategory: Category) {
display.currentCategory = currentCategory
display.adjustDisplaySelection()
super.show()
}
override fun getTabViews(): List<View> = listOf( override fun getTabViews(): List<View> = listOf(
filters, filters,
sort, sort,
@ -230,10 +243,33 @@ class AnimelibSettingsSheet(
* Display group, to show the animelib as a list or a grid. * Display group, to show the animelib as a list or a grid.
*/ */
inner class Display @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : inner class Display @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
Settings(context, attrs) { AnimelibSettingsSheet.Settings(context, attrs) {
private val displayGroup: AnimelibSettingsSheet.Display.DisplayGroup
private val badgeGroup: AnimelibSettingsSheet.Display.BadgeGroup
private val tabsGroup: AnimelibSettingsSheet.Display.TabsGroup
init { init {
setGroups(listOf(DisplayGroup(), BadgeGroup(), TabsGroup())) displayGroup = DisplayGroup()
badgeGroup = BadgeGroup()
tabsGroup = TabsGroup()
setGroups(listOf(displayGroup, badgeGroup, tabsGroup))
}
// Refreshes Display Setting selections
fun adjustDisplaySelection() {
val mode = getDisplayModePreference()
displayGroup.setGroupSelections(mode)
displayGroup.items.forEach { adapter.notifyItemChanged(it) }
}
// Gets user preference of currently selected display mode at current category
private fun getDisplayModePreference(): DisplayMode {
return if (preferences.categorisedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
DisplayMode.values()[currentCategory?.displayMode ?: 0]
} else {
preferences.libraryDisplayMode().get()
}
} }
inner class DisplayGroup : Group { inner class DisplayGroup : Group {
@ -247,10 +283,8 @@ class AnimelibSettingsSheet(
override val footer = null override val footer = null
override fun initModels() { override fun initModels() {
val mode = preferences.animelibDisplayMode().get() val mode = getDisplayModePreference()
compactGrid.checked = mode == DisplayMode.COMPACT_GRID setGroupSelections(mode)
comfortableGrid.checked = mode == DisplayMode.COMFORTABLE_GRID
list.checked = mode == DisplayMode.LIST
} }
override fun onItemClicked(item: Item) { override fun onItemClicked(item: Item) {
@ -260,17 +294,43 @@ class AnimelibSettingsSheet(
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.animelibDisplayMode().set( setDisplayModePreference(item)
when (item) {
compactGrid -> DisplayMode.COMPACT_GRID
comfortableGrid -> DisplayMode.COMFORTABLE_GRID
list -> DisplayMode.LIST
else -> throw NotImplementedError("Unknown display mode")
}
)
item.group.items.forEach { adapter.notifyItemChanged(it) } item.group.items.forEach { adapter.notifyItemChanged(it) }
} }
// Sets display group selections based on given mode
fun setGroupSelections(mode: DisplayMode) {
compactGrid.checked = mode == DisplayMode.COMPACT_GRID
comfortableGrid.checked = mode == DisplayMode.COMFORTABLE_GRID
list.checked = mode == DisplayMode.LIST
}
private fun setDisplayModePreference(item: ExtendedNavigationView.Item) {
if (preferences.categorisedDisplaySettings()
.get() && currentCategory != null && currentCategory?.id != 0
) {
val flag = when (item) {
compactGrid -> Category.COMPACT_GRID
comfortableGrid -> Category.COMFORTABLE_GRID
list -> Category.LIST
else -> throw NotImplementedError("Unknown display mode")
}
currentCategory?.displayMode = flag
db.insertCategory(currentCategory!!).executeAsBlocking()
} else {
preferences.libraryDisplayMode().set(
when (item) {
compactGrid -> DisplayMode.COMPACT_GRID
comfortableGrid -> DisplayMode.COMFORTABLE_GRID
list -> DisplayMode.LIST
else -> throw NotImplementedError("Unknown display mode")
}
)
}
}
} }
inner class BadgeGroup : Group { inner class BadgeGroup : Group {
@ -336,6 +396,8 @@ class AnimelibSettingsSheet(
*/ */
var onGroupClicked: (Group) -> Unit = {} var onGroupClicked: (Group) -> Unit = {}
var currentCategory: Category? = null
fun setGroups(groups: List<Group>) { fun setGroups(groups: List<Group>) {
adapter = Adapter(groups.map { it.createItems() }.flatten()) adapter = Adapter(groups.map { it.createItems() }.flatten())
recycler.adapter = adapter recycler.adapter = adapter

View file

@ -5,7 +5,6 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dev.chrisbanes.insetter.applyInsetter import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.SelectableAdapter
@ -56,7 +55,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
/** /**
* Recycler view of the list of manga. * Recycler view of the list of manga.
*/ */
private lateinit var recycler: RecyclerView private lateinit var recycler: AutofitRecyclerView
/** /**
* Adapter to hold the manga in this category. * Adapter to hold the manga in this category.
@ -73,9 +72,11 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
fun onCreate(controller: LibraryController, binding: LibraryCategoryBinding) { fun onCreate(controller: LibraryController, binding: LibraryCategoryBinding) {
this.controller = controller this.controller = controller
recycler = if (preferences.libraryDisplayMode().get() == DisplayMode.LIST) { recycler = if (preferences.libraryDisplayMode().get() == DisplayMode.LIST &&
(binding.swipeRefresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply { !preferences.categorisedDisplaySettings().get()
layoutManager = LinearLayoutManager(context) ) {
(binding.swipeRefresh.inflate(R.layout.library_list_recycler) as AutofitRecyclerView).apply {
spanCount = 1
} }
} else { } else {
(binding.swipeRefresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply { (binding.swipeRefresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply {
@ -122,6 +123,15 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
fun onBind(category: Category) { fun onBind(category: Category) {
this.category = category this.category = category
// If displayMode should be set from category adjust manga count per row
if (preferences.categorisedDisplaySettings().get()) {
recycler.spanCount = if (category.displayMode == Category.LIST || (preferences.libraryDisplayMode().get() == DisplayMode.LIST && category.id == 0)) {
1
} else {
controller.mangaPerRow
}
}
adapter.mode = if (controller.selectedMangas.isNotEmpty()) { adapter.mode = if (controller.selectedMangas.isNotEmpty()) {
SelectableAdapter.Mode.MULTI SelectableAdapter.Mode.MULTI
} else { } else {

View file

@ -262,7 +262,9 @@ class LibraryController(
} }
fun showSettingsSheet() { fun showSettingsSheet() {
settingsSheet?.show() adapter?.categories?.get(binding.libraryPager.currentItem)?.let { category ->
settingsSheet?.show(category)
}
} }
fun onNextLibraryUpdate(categories: List<Category>, mangaMap: Map<Int, List<LibraryItem>>) { fun onNextLibraryUpdate(categories: List<Category>, mangaMap: Map<Int, List<LibraryItem>>) {

View file

@ -21,17 +21,34 @@ import eu.kanade.tachiyomi.widget.AutofitRecyclerView
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 libraryDisplayMode: Preference<DisplayMode>) : class LibraryItem(
val manga: LibraryManga,
private val shouldSetFromCategory: Preference<Boolean>,
private val defaultLibraryDisplayMode: Preference<DisplayMode>
) :
AbstractFlexibleItem<LibraryHolder<*>>(), IFilterable<String> { AbstractFlexibleItem<LibraryHolder<*>>(), IFilterable<String> {
private val sourceManager: SourceManager = Injekt.get() private val sourceManager: SourceManager = Injekt.get()
var displayMode: Int = -1
var downloadCount = -1 var downloadCount = -1
var unreadCount = -1 var unreadCount = -1
var isLocal = false var isLocal = false
private fun getDisplayMode(): DisplayMode {
return if (shouldSetFromCategory.get() && manga.category != 0) {
if (displayMode != -1) {
DisplayMode.values()[displayMode]
} else {
DisplayMode.COMPACT_GRID
}
} else {
defaultLibraryDisplayMode.get()
}
}
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return when (libraryDisplayMode.get()) { return when (getDisplayMode()) {
DisplayMode.COMPACT_GRID -> R.layout.source_compact_grid_item DisplayMode.COMPACT_GRID -> R.layout.source_compact_grid_item
DisplayMode.COMFORTABLE_GRID -> R.layout.source_comfortable_grid_item DisplayMode.COMFORTABLE_GRID -> R.layout.source_comfortable_grid_item
DisplayMode.LIST -> R.layout.source_list_item DisplayMode.LIST -> R.layout.source_list_item
@ -39,7 +56,7 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe
} }
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LibraryHolder<*> { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LibraryHolder<*> {
return when (libraryDisplayMode.get()) { return when (getDisplayMode()) {
DisplayMode.COMPACT_GRID -> { DisplayMode.COMPACT_GRID -> {
val binding = SourceCompactGridItemBinding.bind(view) val binding = SourceCompactGridItemBinding.bind(view)
val parent = adapter.recyclerView as AutofitRecyclerView val parent = adapter.recyclerView as AutofitRecyclerView

View file

@ -312,6 +312,13 @@ class LibraryPresenter(
dbCategories dbCategories
} }
libraryManga.forEach { (categoryId, libraryManga) ->
val category = categories.first { category -> category.id == categoryId }
libraryManga.forEach { libraryItem ->
libraryItem.displayMode = category.displayMode
}
}
this.categories = categories this.categories = categories
Library(categories, libraryManga) Library(categories, libraryManga)
} }
@ -333,10 +340,18 @@ class LibraryPresenter(
* value. * value.
*/ */
private fun getLibraryMangasObservable(): Observable<LibraryMap> { private fun getLibraryMangasObservable(): Observable<LibraryMap> {
val libraryDisplayMode = preferences.libraryDisplayMode() val defaultLibraryDisplayMode = preferences.libraryDisplayMode()
val shouldSetFromCategory = preferences.categorisedDisplaySettings()
return db.getLibraryMangas().asRxObservable() return db.getLibraryMangas().asRxObservable()
.map { list -> .map { list ->
list.map { LibraryItem(it, libraryDisplayMode) }.groupBy { it.manga.category } list.map { libraryManga ->
// Display mode based on user preference: take it from global library setting or category
LibraryItem(
libraryManga,
shouldSetFromCategory,
defaultLibraryDisplayMode
)
}.groupBy { it.manga.category }
} }
} }

View file

@ -5,6 +5,8 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.Router
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
@ -25,6 +27,7 @@ class LibrarySettingsSheet(
val filters: Filter val filters: Filter
private val sort: Sort private val sort: Sort
private val display: Display private val display: Display
private val db: DatabaseHelper by injectLazy()
init { init {
filters = Filter(router.activity!!) filters = Filter(router.activity!!)
@ -37,6 +40,16 @@ class LibrarySettingsSheet(
display.onGroupClicked = onGroupClickListener display.onGroupClicked = onGroupClickListener
} }
/**
* adjusts selected button to match real state.
* @param currentCategory ID of currently shown category
*/
fun show(currentCategory: Category) {
display.currentCategory = currentCategory
display.adjustDisplaySelection()
super.show()
}
override fun getTabViews(): List<View> = listOf( override fun getTabViews(): List<View> = listOf(
filters, filters,
sort, sort,
@ -232,8 +245,31 @@ class LibrarySettingsSheet(
inner class Display @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : inner class Display @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
Settings(context, attrs) { Settings(context, attrs) {
private val displayGroup: DisplayGroup
private val badgeGroup: BadgeGroup
private val tabsGroup: TabsGroup
init { init {
setGroups(listOf(DisplayGroup(), BadgeGroup(), TabsGroup())) displayGroup = DisplayGroup()
badgeGroup = BadgeGroup()
tabsGroup = TabsGroup()
setGroups(listOf(displayGroup, badgeGroup, tabsGroup))
}
// Refreshes Display Setting selections
fun adjustDisplaySelection() {
val mode = getDisplayModePreference()
displayGroup.setGroupSelections(mode)
displayGroup.items.forEach { adapter.notifyItemChanged(it) }
}
// Gets user preference of currently selected display mode at current category
private fun getDisplayModePreference(): DisplayMode {
return if (preferences.categorisedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
DisplayMode.values()[currentCategory?.displayMode ?: 0]
} else {
preferences.libraryDisplayMode().get()
}
} }
inner class DisplayGroup : Group { inner class DisplayGroup : Group {
@ -247,10 +283,8 @@ class LibrarySettingsSheet(
override val footer = null override val footer = null
override fun initModels() { override fun initModels() {
val mode = preferences.libraryDisplayMode().get() val mode = getDisplayModePreference()
compactGrid.checked = mode == DisplayMode.COMPACT_GRID setGroupSelections(mode)
comfortableGrid.checked = mode == DisplayMode.COMFORTABLE_GRID
list.checked = mode == DisplayMode.LIST
} }
override fun onItemClicked(item: Item) { override fun onItemClicked(item: Item) {
@ -260,17 +294,41 @@ 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.libraryDisplayMode().set( setDisplayModePreference(item)
when (item) {
compactGrid -> DisplayMode.COMPACT_GRID
comfortableGrid -> DisplayMode.COMFORTABLE_GRID
list -> DisplayMode.LIST
else -> throw NotImplementedError("Unknown display mode")
}
)
item.group.items.forEach { adapter.notifyItemChanged(it) } item.group.items.forEach { adapter.notifyItemChanged(it) }
} }
// Sets display group selections based on given mode
fun setGroupSelections(mode: DisplayMode) {
compactGrid.checked = mode == DisplayMode.COMPACT_GRID
comfortableGrid.checked = mode == DisplayMode.COMFORTABLE_GRID
list.checked = mode == DisplayMode.LIST
}
private fun setDisplayModePreference(item: Item) {
if (preferences.categorisedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) {
val flag = when (item) {
compactGrid -> Category.COMPACT_GRID
comfortableGrid -> Category.COMFORTABLE_GRID
list -> Category.LIST
else -> throw NotImplementedError("Unknown display mode")
}
currentCategory?.displayMode = flag
db.insertCategory(currentCategory!!).executeAsBlocking()
} else {
preferences.libraryDisplayMode().set(
when (item) {
compactGrid -> DisplayMode.COMPACT_GRID
comfortableGrid -> DisplayMode.COMFORTABLE_GRID
list -> DisplayMode.LIST
else -> throw NotImplementedError("Unknown display mode")
}
)
}
}
} }
inner class BadgeGroup : Group { inner class BadgeGroup : Group {
@ -336,6 +394,8 @@ class LibrarySettingsSheet(
*/ */
var onGroupClicked: (Group) -> Unit = {} var onGroupClicked: (Group) -> Unit = {}
var currentCategory: Category? = null
fun setGroups(groups: List<Group>) { fun setGroups(groups: List<Group>) {
adapter = Adapter(groups.map { it.createItems() }.flatten()) adapter = Adapter(groups.map { it.createItems() }.flatten())
recycler.adapter = adapter recycler.adapter = adapter

View file

@ -88,6 +88,9 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
} }
} }
private const val ENABLED_BUTTON_IMAGE_ALPHA = 255
private const val DISABLED_BUTTON_IMAGE_ALPHA = 64
} }
private val preferences: PreferencesHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy()
@ -422,8 +425,6 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
presenter.setMangaOrientationType(newOrientation.flagValue) presenter.setMangaOrientationType(newOrientation.flagValue)
updateOrientationShortcut(newOrientation.flagValue)
menuToggleToast?.cancel() menuToggleToast?.cancel()
menuToggleToast = toast(newOrientation.stringRes) menuToggleToast = toast(newOrientation.stringRes)
} }
@ -589,13 +590,28 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
/** /**
* Called from the presenter whenever a new [viewerChapters] have been set. It delegates the * Called from the presenter whenever a new [viewerChapters] have been set. It delegates the
* method to the current viewer, but also set the subtitle on the toolbar. * method to the current viewer, but also set the subtitle on the toolbar, and
* hides or disables the reader prev/next buttons if there's a prev or next chapter
*/ */
fun setChapters(viewerChapters: ViewerChapters) { fun setChapters(viewerChapters: ViewerChapters) {
binding.pleaseWait.isVisible = false binding.pleaseWait.isVisible = false
viewer?.setChapters(viewerChapters) viewer?.setChapters(viewerChapters)
binding.toolbar.subtitle = viewerChapters.currChapter.chapter.name binding.toolbar.subtitle = viewerChapters.currChapter.chapter.name
val leftChapterObject = if (viewer is R2LPagerViewer) viewerChapters.nextChapter else viewerChapters.prevChapter
val rightChapterObject = if (viewer is R2LPagerViewer) viewerChapters.prevChapter else viewerChapters.nextChapter
if (leftChapterObject == null && rightChapterObject == null) {
binding.leftChapter.isVisible = false
binding.rightChapter.isVisible = false
} else {
binding.leftChapter.isEnabled = leftChapterObject != null
binding.leftChapter.imageAlpha = if (leftChapterObject != null) ENABLED_BUTTON_IMAGE_ALPHA else DISABLED_BUTTON_IMAGE_ALPHA
binding.rightChapter.isEnabled = rightChapterObject != null
binding.rightChapter.imageAlpha = if (rightChapterObject != null) ENABLED_BUTTON_IMAGE_ALPHA else DISABLED_BUTTON_IMAGE_ALPHA
}
// Invalidate menu to show proper chapter bookmark state // Invalidate menu to show proper chapter bookmark state
invalidateOptionsMenu() invalidateOptionsMenu()
} }
@ -792,6 +808,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
if (newOrientation.flag != requestedOrientation) { if (newOrientation.flag != requestedOrientation) {
requestedOrientation = newOrientation.flag requestedOrientation = newOrientation.flag
} }
updateOrientationShortcut(presenter.getMangaOrientationType(resolveDefault = false))
} }
/** /**

View file

@ -527,10 +527,11 @@ class ReaderPresenter(
/** /**
* Returns the orientation type used by this manga or the default one. * Returns the orientation type used by this manga or the default one.
*/ */
fun getMangaOrientationType(): Int { fun getMangaOrientationType(resolveDefault: Boolean = true): Int {
val default = preferences.defaultOrientationType() val default = preferences.defaultOrientationType()
return when (manga?.orientationType) { val orientation = OrientationType.fromPreference(manga?.orientationType)
OrientationType.DEFAULT.flagValue -> default return when {
resolveDefault && orientation == OrientationType.DEFAULT -> default
else -> manga?.orientationType ?: default else -> manga?.orientationType ?: default
} }
} }

View file

@ -124,6 +124,12 @@ class SettingsLibraryController : SettingsController() {
true true
} }
} }
switchPreference {
key = Keys.categorizedDisplay
titleRes = R.string.categorized_display_settings
defaultValue = false
}
} }
preferenceCategory { preferenceCategory {

View file

@ -17,6 +17,7 @@ import com.google.android.exoplayer2.source.MediaSourceFactory
import com.google.android.exoplayer2.source.TrackGroupArray import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.trackselection.TrackSelectionArray import com.google.android.exoplayer2.trackselection.TrackSelectionArray
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
@ -261,7 +262,7 @@ class WatcherActivity : AppCompatActivity() {
uri = links[nextQuality].url uri = links[nextQuality].url
mediaItem = MediaItem.Builder() mediaItem = MediaItem.Builder()
.setUri(uri) .setUri(uri)
.setMimeType(MimeTypes.VIDEO_MP4) .setMimeType(getMime(uri))
.build() .build()
exoPlayer.setMediaItem(mediaItem, resumeAt) exoPlayer.setMediaItem(mediaItem, resumeAt)
exoPlayer.prepare() exoPlayer.prepare()

View file

@ -271,8 +271,8 @@ fun Context.createFileInCacheDir(name: String): File {
} }
/** /**
* We consider anything with a width of >= 720dp as a tablet, i.e. with layouts in layout-w720dp. * We consider anything with a width of >= 720dp as a tablet, i.e. with layouts in layout-sw720dp.
*/ */
fun Context.isTablet(): Boolean { fun Context.isTablet(): Boolean {
return (resources.displayMetrics.widthPixels / resources.displayMetrics.density) >= 720 return resources.configuration.smallestScreenWidthDp >= 720
} }

View file

@ -18,6 +18,7 @@ import androidx.core.graphics.green
import androidx.core.graphics.red import androidx.core.graphics.red
import tachiyomi.decoder.Format import tachiyomi.decoder.Format
import tachiyomi.decoder.ImageDecoder import tachiyomi.decoder.ImageDecoder
import tachiyomi.decoder.ImageType
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
@ -41,30 +42,15 @@ object ImageUtil {
fun findImageType(stream: InputStream): ImageType? { fun findImageType(stream: InputStream): ImageType? {
try { try {
val bytes = ByteArray(8) return when (getImageType(stream)?.format) {
// TODO: image-decoder library currently doesn't actually detect AVIF yet
val length = if (stream.markSupported()) { Format.Avif -> ImageType.AVIF
stream.mark(bytes.size) Format.Gif -> ImageType.GIF
stream.read(bytes, 0, bytes.size).also { stream.reset() } Format.Heif -> ImageType.HEIF
} else { Format.Jpeg -> ImageType.JPEG
stream.read(bytes, 0, bytes.size) Format.Png -> ImageType.PNG
} Format.Webp -> ImageType.WEBP
else -> null
if (length == -1) {
return null
}
if (bytes.compareWith(charByteArrayOf(0xFF, 0xD8, 0xFF))) {
return ImageType.JPG
}
if (bytes.compareWith(charByteArrayOf(0x89, 0x50, 0x4E, 0x47))) {
return ImageType.PNG
}
if (bytes.compareWith("GIF8".toByteArray())) {
return ImageType.GIF
}
if (bytes.compareWith("RIFF".toByteArray())) {
return ImageType.WEBP
} }
} catch (e: Exception) { } catch (e: Exception) {
} }
@ -73,20 +59,7 @@ object ImageUtil {
fun isAnimatedAndSupported(stream: InputStream): Boolean { fun isAnimatedAndSupported(stream: InputStream): Boolean {
try { try {
val bytes = ByteArray(32) val type = getImageType(stream) ?: return false
val length = if (stream.markSupported()) {
stream.mark(bytes.size)
stream.read(bytes, 0, bytes.size).also { stream.reset() }
} else {
stream.read(bytes, 0, bytes.size)
}
if (length == -1) {
return false
}
val type = ImageDecoder.findType(bytes) ?: return false
return when (type.format) { return when (type.format) {
Format.Gif -> true Format.Gif -> true
// Coil supports animated WebP on Android 9.0+ // Coil supports animated WebP on Android 9.0+
@ -99,23 +72,30 @@ object ImageUtil {
return false return false
} }
private fun ByteArray.compareWith(magic: ByteArray): Boolean { private fun getImageType(stream: InputStream): tachiyomi.decoder.ImageType? {
return magic.indices.none { this[it] != magic[it] } val bytes = ByteArray(32)
}
private fun charByteArrayOf(vararg bytes: Int): ByteArray { val length = if (stream.markSupported()) {
return ByteArray(bytes.size).apply { stream.mark(bytes.size)
for (i in bytes.indices) { stream.read(bytes, 0, bytes.size).also { stream.reset() }
set(i, bytes[i].toByte()) } else {
} stream.read(bytes, 0, bytes.size)
} }
if (length == -1) {
return null
}
return ImageDecoder.findType(bytes)
} }
enum class ImageType(val mime: String, val extension: String) { enum class ImageType(val mime: String, val extension: String) {
JPG("image/jpeg", "jpg"), AVIF("image/avif", "avif"),
PNG("image/png", "png"),
GIF("image/gif", "gif"), GIF("image/gif", "gif"),
WEBP("image/webp", "webp") HEIF("image/heif", "heif"),
JPEG("image/jpeg", "jpg"),
PNG("image/png", "png"),
WEBP("image/webp", "webp"),
} }
/** /**

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" <eu.kanade.tachiyomi.widget.AutofitRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/animelib_list" android:id="@+id/animelib_list"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" <eu.kanade.tachiyomi.widget.AutofitRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/library_list" android:id="@+id/library_list"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -237,6 +237,7 @@
<string name="default_category">Default category</string> <string name="default_category">Default category</string>
<string name="default_category_summary">Always ask</string> <string name="default_category_summary">Always ask</string>
<string name="categorized_display_settings">Per-category display settings</string>
<plurals name="num_categories"> <plurals name="num_categories">
<item quantity="one">%d category</item> <item quantity="one">%d category</item>
<item quantity="other">%d categories</item> <item quantity="other">%d categories</item>