mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-28 17:19:00 +03:00
Merge branch 'upstream'
This commit is contained in:
commit
8f64dca946
26 changed files with 342 additions and 109 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>>) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>>) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,6 +124,12 @@ class SettingsLibraryController : SettingsController() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switchPreference {
|
||||||
|
key = Keys.categorizedDisplay
|
||||||
|
titleRes = R.string.categorized_display_settings
|
||||||
|
defaultValue = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preferenceCategory {
|
preferenceCategory {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"),
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue