mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
Staggered grid layout + Titleless grid option (#1198)
* Most of the work for big toolbar * minor fixes to library * Using accurate offset for library * nav icon fixes * support tabs scrolling properly * fix toolbar snap * Add big icon next to large title * Update MainActivity.kt * Update browse sheet toolbar to be its own * Update recent sheet toolbar to be its own * fix pop from manga details * cleanup of BigAppBarLayout * Use LinearLayoutManagerAccurateOffset for recents * Updates to searchview * fix recents scrolling up on device config change * Move search view up to top on tap * Fix to recent and library searches * Fix multi line big titles * Fix config change resetting recycler scroll * add end padding to big title * More fixes * Fixes to non scrollable appbar controllers * Move swipe refresh circle with view * Fix manga details toolbar * Updates to recents and browse source when config changes * Fixes to global search * Fixes to popping from manga details * clear search when switching controllers + fixes to global and source search * Update search.xml * update backgroundColor * fixes to big appbar background on push * use canShowFloatingToolbar where possible * fix collapse/hide of dl bottom sheet * persist search on config change * Fix library menu on config change * Fix recents toolbar placement while searching * adjust calc of grid layout manager offset * Fixes to range calculation in library * More accurate scrollbar in manga details * use item animator for moving appbar * alpha on main tb * accurate offset for settings * updates to search activity * Fixes to back button and up button * Option to go back to smaller toolbar * Optimization to fast computing offset * moving scrollbar in library as app bar moves * fixes to search title in recents * fix popping into browse after 2 line big titles * Updates to landscape and tablets Tablets now always have a sticky header, just shrinks down Phones in landscape will have a smaller big title * fixes to main toolbar text alpha * Fixes to device config changes * remove updateOnRefresh preference big toolbar now, you can just reach the first refresh button * Fix category hopper * Fix recents in small toolbar mode * clean up and fixes * Fixes to back press + search activity * Fixes to going in and back out of manga details * Optimizations to grid layout calculations * adjustments to library fast scroller margins * Fixes to download queue overflow menu * Fixes to library search when popping * Fixes to d/l sheet on launch * expanding library search * remove includeTabView as parameter * remove sheetIsFullscreen * fixes to bigToolbarHeight * add onDetachedFromWindow to LinearLayoutManagerAccurateOffset * add scrollUpAnyway to moveRecyclerViewUp * snap swipe refresh circle with appbar * Fixes to extension searching * accurate offset use in ExtensionDetailsController * alpha big view adjustments * Fixes to MigrationController * Dont change toolbar menus while scrolling down * staggered grid * titleless grid + plenty of other fixes * fix top scrolls for staggered managers * fix disappearing appbar in some cases * save cover ratios less often * Cleanup GlobalSearchController searchItem * Cleanup SettingsMainController searchItem * Update LibraryController.kt * change onRoot to isControllerVisible and add to controller extensions * lock appbar y when switching tabs * Dont use large toolbar on extremely small devices Basically devices in landscape that cant display nav rails * Fixes to progress view and empty view in landscape new layout for empty view for short devices * keep recycler pos when switching between staggered and regular * move manager offsets to new files * move StaggeredGridLayoutManagerAccurateOffset to new file * Move MangaCoverRatios to another file * setItem -> saveStaggeredState * cleanup * fix indent in manga_grid_item * Use dominant color as a placeholder cover in library * fix for ungrouped library mode * clean up search items in BrowseSourceController * dont show incog icon on main toolbar when search bar is showing * fix browse source having the grid be offset when popping * Fixes to drag and drop for staggered/non * remove mapping for manga not in library * set min height for grid layout when ratio is provided * Fixes to estimated heights of viewholders * rename large toolbar setting to expanded also update setting copy to mention small devices will never show it * BigAppBarLayout -> ExpandedAppBarLayout * save a var for computedRange to ease the calls a bit * fix crash * Fix span being wrong at first on auto fit recycler * Fixes to grid gradient * fixed behind title showing in compact grid * Move color processing to manga fetcher + own scope * Fix staggered not working on compact grid * More fixes to grid gradient * Cleanup BaseToolbar * Refactoring ExpandedAppBarLayout * misc cleanup * use updateAppBarAfterY where needed * Rename ToolbarStates * refactor toolbar height variable names * More refactoring to MainActivity * helper method for setTextColorAlpha * Refactor MangaDetailsController * Refactor Recents * Refactoring settings controllers * Cleanup Browse controller * Remove Float.spToPx its not used * Clean up ControllerExtensions * clean up import in BrowseController * Use keys for the cover preferences * rename methods in MangaFetcher * minor refactor in LibraryController * Add migration method to save covers and ratios of library manga on app update * MangaCoverRatios -> MangaCoverMetadata * Cleanup LibraryItem * Titleless Grid -> Cover-only grid Going to try this as a different key since it seems like weblate still grabs the string even with different kets from upstream * Use constants for library layouts * Use snackbar to show the title of cover only grid when long pressing * move itemanimator to a different method same change was already made in staggered branch * Fixes to browse source toolbar not being expanded * cleanup ControllerExtensions.setAppBarBG * Mainactivity cleanup * Optimizations + fixes to setting menu items in search toolbar * Fixes to first load of library with staggered grid * Fix grid items in migration controller * dismiss about manga title snackbar when selecting again * use measured width for temp span * Fixes to switching between grid/list in browse source with correct offsetting and all * Fix findFirstVisibleItemPosition for offset layoutmanagers * remove unneeded invalidateOptionsMenu calls * More fixes to search toolbar menu in browse source * filter sources controller now uses search toolbar too when using expanded mode sticks back to main toolbar when collapsed (ie like older version of j2k) * Fix compact -> expanded setting change when starting app in compact mode * Fix starting span of autofit recycler * fix how findFirstVisibleItemPosition works in staggered grid * More fixes to BrowseSourceController * Fixes to LibraryController search menu * Filter sources search title is now just "search" because not gonna add a new string for this * Fix statusbar color when popping back to ext sheet * Make adding submenu items recursive * Fix appbar locking up in recents * Fix download/ext sheet in landscape 3 nav devices * Fixing to snapping on compact toolbars * Fix search toolbar menu flashing so much on change * Fix animation time of snapping compact appbar * Fixes to toolbar for tablets * Cleanup * Fix controller navigation when sidebar shows/hides Removing the logic for the sidenav fade out change handling too now In this branch because it made the staggered stuff wonky Fixes to last commit * Reduce decoding fullbitmap for covermetadata when possible * Minor updates to coil setup allow hardware + disable rgb565 on low ram devices * better handling of menu item in search toolbar * minimize use of obtainStyledAttributes of mainActionBarSize * reorder import * some staggered fixes to the appbar in library * use staggered grid pref key directly
This commit is contained in:
parent
d8e944f396
commit
586e3667c6
27 changed files with 632 additions and 120 deletions
|
@ -24,6 +24,7 @@ import eu.kanade.tachiyomi.ui.library.LibraryPresenter
|
|||
import eu.kanade.tachiyomi.ui.recents.RecentsPresenter
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
import eu.kanade.tachiyomi.ui.source.SourcePresenter
|
||||
import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
|
||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
|
||||
import eu.kanade.tachiyomi.util.system.notification
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
@ -78,6 +79,7 @@ open class App : Application(), DefaultLifecycleObserver {
|
|||
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||
|
||||
MangaCoverMetadata.load()
|
||||
preferences.nightMode()
|
||||
.asImmediateFlow { AppCompatDelegate.setDefaultNightMode(it) }
|
||||
.launchIn(ProcessLifecycleOwner.get().lifecycleScope)
|
||||
|
|
|
@ -15,7 +15,9 @@ import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
|||
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryPresenter
|
||||
import eu.kanade.tachiyomi.ui.reader.settings.OrientationType
|
||||
import eu.kanade.tachiyomi.util.system.launchIO
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
|
@ -28,7 +30,7 @@ object Migrations {
|
|||
* @param preferences Preferences of the application.
|
||||
* @return true if a migration is performed, false otherwise.
|
||||
*/
|
||||
fun upgrade(preferences: PreferencesHelper): Boolean {
|
||||
fun upgrade(preferences: PreferencesHelper, scope: CoroutineScope): Boolean {
|
||||
val context = preferences.context
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
prefs.edit {
|
||||
|
@ -184,6 +186,9 @@ object Migrations {
|
|||
}
|
||||
}
|
||||
if (oldVersion < 88) {
|
||||
scope.launchIO {
|
||||
LibraryPresenter.updateRatiosAndColors()
|
||||
}
|
||||
val oldReaderTap = prefs.getBoolean("reader_tap", false)
|
||||
if (!oldReaderTap) {
|
||||
preferences.navigationModePager().set(5)
|
||||
|
|
|
@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.source.SourceManager
|
|||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.reader.settings.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.settings.ReadingModeType
|
||||
import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
|
||||
import tachiyomi.source.model.MangaInfo
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
@ -275,6 +276,13 @@ interface Manga : SManga {
|
|||
id?.let { vibrantCoverColorMap[it] = value }
|
||||
}
|
||||
|
||||
var dominantCoverColors: Pair<Int, Int>?
|
||||
get() = MangaCoverMetadata.getColors(this)
|
||||
set(value) {
|
||||
value ?: return
|
||||
MangaCoverMetadata.addCoverColor(this, value.first, value.second)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
// Generic filter that does not filter anything
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package eu.kanade.tachiyomi.data.image.coil
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import androidx.core.content.getSystemService
|
||||
import coil.Coil
|
||||
import coil.ImageLoader
|
||||
import coil.decode.GifDecoder
|
||||
|
@ -16,8 +19,8 @@ class CoilSetup(context: Context) {
|
|||
val imageLoader = ImageLoader.Builder(context)
|
||||
.availableMemoryPercentage(0.40)
|
||||
.crossfade(true)
|
||||
.allowRgb565(true)
|
||||
.allowHardware(false)
|
||||
.allowRgb565(context.getSystemService<ActivityManager>()!!.isLowRamDevice)
|
||||
.allowHardware(true)
|
||||
.componentRegistry {
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
add(ImageDecoderDecoder(context))
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package eu.kanade.tachiyomi.data.image.coil
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.widget.ImageView
|
||||
import androidx.palette.graphics.Palette
|
||||
|
@ -23,18 +22,6 @@ class LibraryMangaImageTarget(
|
|||
|
||||
private val coverCache: CoverCache by injectLazy()
|
||||
|
||||
override fun onSuccess(result: Drawable) {
|
||||
super.onSuccess(result)
|
||||
if (manga.vibrantCoverColor == null) {
|
||||
val bitmap = (drawable as? BitmapDrawable)?.bitmap ?: return
|
||||
Palette.from(bitmap).generate {
|
||||
if (it == null) return@generate
|
||||
val color = it.getBestColor() ?: return@generate
|
||||
manga.vibrantCoverColor = color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: Drawable?) {
|
||||
super.onError(error)
|
||||
if (manga.favorite) {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package eu.kanade.tachiyomi.data.image.coil
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.palette.graphics.Palette
|
||||
import coil.bitmap.BitmapPool
|
||||
import coil.decode.DataSource
|
||||
import coil.decode.Options
|
||||
|
@ -14,7 +16,12 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
|||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.Call
|
||||
import okhttp3.Request
|
||||
|
@ -40,6 +47,7 @@ class MangaFetcher : Fetcher<Manga> {
|
|||
private val coverCache: CoverCache by injectLazy()
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
private val defaultClient = Injekt.get<NetworkHelper>().client
|
||||
val fileScope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
|
||||
override fun key(data: Manga): String? {
|
||||
if (data.thumbnail_url.isNullOrBlank()) return null
|
||||
|
@ -65,6 +73,7 @@ class MangaFetcher : Fetcher<Manga> {
|
|||
if (!shouldFetchRemotely) {
|
||||
val customCoverFile = coverCache.getCustomCoverFile(manga)
|
||||
if (customCoverFile.exists() && options.parameters.value(realCover) != true) {
|
||||
setRatioAndColorsInScope(manga, customCoverFile)
|
||||
return fileLoader(customCoverFile)
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +82,7 @@ class MangaFetcher : Fetcher<Manga> {
|
|||
if (!manga.favorite) {
|
||||
coverFile.setLastModified(Date().time)
|
||||
}
|
||||
setRatioAndColorsInScope(manga, coverFile)
|
||||
return fileLoader(coverFile)
|
||||
}
|
||||
val (response, body) = awaitGetCall(
|
||||
|
@ -104,9 +114,50 @@ class MangaFetcher : Fetcher<Manga> {
|
|||
coverCache.deleteCachedCovers()
|
||||
}
|
||||
}
|
||||
setRatioAndColorsInScope(manga, coverFile, true)
|
||||
return fileLoader(coverFile)
|
||||
}
|
||||
|
||||
private fun setRatioAndColorsInScope(manga: Manga, ogFile: File? = null, force: Boolean = false) {
|
||||
fileScope.launch {
|
||||
setRatioAndColors(manga, ogFile, force)
|
||||
}
|
||||
}
|
||||
|
||||
fun setRatioAndColors(manga: Manga, ogFile: File? = null, force: Boolean = false) {
|
||||
if (!manga.favorite) {
|
||||
MangaCoverMetadata.remove(manga)
|
||||
}
|
||||
if (manga.vibrantCoverColor != null && !manga.favorite) return
|
||||
val file = ogFile ?: coverCache.getCustomCoverFile(manga).takeIf { it.exists() } ?: coverCache.getCoverFile(manga)
|
||||
// if the file exists and the there was still an error then the file is corrupted
|
||||
if (file.exists()) {
|
||||
val options = BitmapFactory.Options()
|
||||
val hasVibrantColor = if (manga.favorite) manga.vibrantCoverColor != null else true
|
||||
if (manga.dominantCoverColors != null && hasVibrantColor && !force) {
|
||||
options.inJustDecodeBounds = true
|
||||
} else {
|
||||
options.inSampleSize = 4
|
||||
}
|
||||
val bitmap = BitmapFactory.decodeFile(file.path, options) ?: return
|
||||
if (!options.inJustDecodeBounds) {
|
||||
Palette.from(bitmap).generate {
|
||||
if (it == null) return@generate
|
||||
if (manga.favorite) {
|
||||
it.dominantSwatch?.let { swatch ->
|
||||
manga.dominantCoverColors = swatch.rgb to swatch.titleTextColor
|
||||
}
|
||||
}
|
||||
val color = it.getBestColor() ?: return@generate
|
||||
manga.vibrantCoverColor = color
|
||||
}
|
||||
}
|
||||
if (manga.favorite && !(options.outWidth == -1 || options.outHeight == -1)) {
|
||||
MangaCoverMetadata.addCoverRatio(manga, options.outWidth / options.outHeight.toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun awaitGetCall(manga: Manga, onlyCache: Boolean = false, forceNetwork: Boolean): Pair<Response,
|
||||
ResponseBody> {
|
||||
val call = getCall(manga, onlyCache, forceNetwork)
|
||||
|
|
|
@ -253,6 +253,10 @@ object PreferenceKeys {
|
|||
|
||||
const val defaultChapterSortByAscendingOrDescending = "default_chapter_sort_by_ascending_or_descending"
|
||||
|
||||
const val coverRatios = "cover_ratio"
|
||||
|
||||
const val coverColors = "cover_colors"
|
||||
|
||||
const val hideChapterTitles = "hide_chapter_titles"
|
||||
|
||||
const val chaptersDescAsDefault = "chapters_desc_as_default"
|
||||
|
|
|
@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.data.updater.AutoAppUpdaterJob
|
||||
import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||
import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet
|
||||
import eu.kanade.tachiyomi.ui.reader.settings.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.settings.PageLayout
|
||||
|
@ -263,7 +264,7 @@ class PreferencesHelper(val context: Context) {
|
|||
|
||||
fun libraryUpdatePrioritization() = flowPrefs.getInt(Keys.libraryUpdatePrioritization, 0)
|
||||
|
||||
fun libraryLayout() = flowPrefs.getInt(Keys.libraryLayout, 2)
|
||||
fun libraryLayout() = flowPrefs.getInt(Keys.libraryLayout, LibraryItem.LAYOUT_COMFORTABLE_GRID)
|
||||
|
||||
fun gridSize() = flowPrefs.getFloat(Keys.gridSize, 1f)
|
||||
|
||||
|
@ -451,4 +452,10 @@ class PreferencesHelper(val context: Context) {
|
|||
fun chaptersDescAsDefault() = flowPrefs.getBoolean(Keys.chaptersDescAsDefault, true)
|
||||
|
||||
fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.CHAPTER_SORT_DESC)
|
||||
|
||||
fun coverRatios() = flowPrefs.getStringSet(Keys.coverRatios, emptySet())
|
||||
|
||||
fun coverColors() = flowPrefs.getStringSet(Keys.coverColors, emptySet())
|
||||
|
||||
fun useStaggeredGrid() = flowPrefs.getBoolean("use_staggered_grid", false)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.base.controller
|
|||
import android.animation.Animator
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.res.Configuration
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
|
@ -35,9 +34,8 @@ class OneWayFadeChangeHandler : FadeChangeHandler {
|
|||
animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, start, 1f))
|
||||
}
|
||||
|
||||
val hasSideNav = container.context.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
if (from != null && (!isPush || removesFromViewOnPush())) {
|
||||
if (!hasSideNav && fadeOut) {
|
||||
if (fadeOut) {
|
||||
animator.play(ObjectAnimator.ofFloat(from, View.ALPHA, 0f))
|
||||
} else {
|
||||
container.removeView(from)
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
|
@ -19,6 +20,7 @@ import android.view.MotionEvent
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewPropertyAnimator
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
|
@ -37,8 +39,8 @@ import androidx.core.view.updatePadding
|
|||
import androidx.core.view.updatePaddingRelative
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.fredporciuncula.flow.preferences.Preference
|
||||
|
@ -227,6 +229,8 @@ class LibraryController(
|
|||
|
||||
override val mainRecycler: RecyclerView
|
||||
get() = binding.libraryGridRecycler.recycler
|
||||
var staggeredBundle: Parcelable? = null
|
||||
private var staggeredObserver: ViewTreeObserver.OnGlobalLayoutListener? = null
|
||||
|
||||
override fun getTitle(): String? {
|
||||
setSubtitle()
|
||||
|
@ -318,6 +322,18 @@ class LibraryController(
|
|||
updateHopperPosition()
|
||||
}
|
||||
}
|
||||
if (newState != RecyclerView.SCROLL_STATE_IDLE) {
|
||||
removeStaggeredObserver()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeStaggeredObserver() {
|
||||
if (staggeredObserver != null) {
|
||||
binding.libraryGridRecycler.recycler.viewTreeObserver.removeOnGlobalLayoutListener(
|
||||
staggeredObserver
|
||||
)
|
||||
staggeredObserver = null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -517,19 +533,6 @@ class LibraryController(
|
|||
adapter = LibraryCategoryAdapter(this)
|
||||
adapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
|
||||
setRecyclerLayout()
|
||||
binding.libraryGridRecycler.recycler.manager.spanSizeLookup = (
|
||||
object : GridLayoutManager.SpanSizeLookup() {
|
||||
override fun getSpanSize(position: Int): Int {
|
||||
if (libraryLayout == 0) return binding.libraryGridRecycler.recycler.manager.spanCount
|
||||
val item = this@LibraryController.adapter.getItem(position)
|
||||
return if (item is LibraryHeaderItem || item is SearchGlobalItem || (item is LibraryItem && item.manga.isBlank())) {
|
||||
binding.libraryGridRecycler.recycler.manager.spanCount
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
binding.libraryGridRecycler.recycler.setHasFixedSize(true)
|
||||
binding.libraryGridRecycler.recycler.adapter = adapter
|
||||
|
||||
|
@ -798,8 +801,7 @@ class LibraryController(
|
|||
val category = getVisibleHeader() ?: return
|
||||
if (presenter.showAllCategories) {
|
||||
if (!next) {
|
||||
val fPosition =
|
||||
(binding.libraryGridRecycler.recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
||||
val fPosition = binding.libraryGridRecycler.recycler.findFirstVisibleItemPosition()
|
||||
if (fPosition > adapter.currentItems.indexOf(category)) {
|
||||
scrollToHeader(category.category.order)
|
||||
return
|
||||
|
@ -834,7 +836,7 @@ class LibraryController(
|
|||
|
||||
private fun getHeader(firstCompletelyVisible: Boolean = false): LibraryHeaderItem? {
|
||||
val position = if (firstCompletelyVisible) {
|
||||
(binding.libraryGridRecycler.recycler.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
|
||||
binding.libraryGridRecycler.recycler.findFirstCompletelyVisibleItemPosition()
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
|
@ -844,8 +846,7 @@ class LibraryController(
|
|||
is LibraryItem -> return item.header
|
||||
}
|
||||
} else {
|
||||
val fPosition =
|
||||
(binding.libraryGridRecycler.recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
||||
val fPosition = binding.libraryGridRecycler.recycler.findFirstVisibleItemPosition()
|
||||
when (val item = adapter.getItem(fPosition)) {
|
||||
is LibraryHeaderItem -> return item
|
||||
is LibraryItem -> return item.header
|
||||
|
@ -855,8 +856,7 @@ class LibraryController(
|
|||
}
|
||||
|
||||
private fun getVisibleHeader(): LibraryHeaderItem? {
|
||||
val fPosition =
|
||||
(binding.libraryGridRecycler.recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
||||
val fPosition = binding.libraryGridRecycler.recycler.findFirstVisibleItemPosition()
|
||||
when (val item = adapter.getItem(fPosition)) {
|
||||
is LibraryHeaderItem -> return item
|
||||
is LibraryItem -> return item.header
|
||||
|
@ -897,7 +897,8 @@ class LibraryController(
|
|||
bottom = 50.dpToPx + (activityBinding?.bottomNav?.height ?: 0)
|
||||
)
|
||||
}
|
||||
if (libraryLayout == 0) {
|
||||
useStaggered(preferences)
|
||||
if (libraryLayout == LibraryItem.LAYOUT_LIST) {
|
||||
spanCount = 1
|
||||
updatePaddingRelative(
|
||||
start = 0,
|
||||
|
@ -910,6 +911,17 @@ class LibraryController(
|
|||
end = 5.dpToPx
|
||||
)
|
||||
}
|
||||
(manager as? GridLayoutManager)?.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
||||
override fun getSpanSize(position: Int): Int {
|
||||
if (libraryLayout == LibraryItem.LAYOUT_LIST) return managerSpanCount
|
||||
val item = this@LibraryController.adapter.getItem(position)
|
||||
return if (item is LibraryHeaderItem || item is SearchGlobalItem || (item is LibraryItem && item.manga.isBlank())) {
|
||||
managerSpanCount
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -917,7 +929,8 @@ class LibraryController(
|
|||
listOf(
|
||||
preferences.libraryLayout(),
|
||||
preferences.uniformGrid(),
|
||||
preferences.gridSize()
|
||||
preferences.gridSize(),
|
||||
preferences.useStaggeredGrid()
|
||||
).forEach {
|
||||
it.asFlow()
|
||||
.drop(1)
|
||||
|
@ -970,7 +983,12 @@ class LibraryController(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (binding.libraryGridRecycler.recycler.manager is StaggeredGridLayoutManager && staggeredBundle != null) {
|
||||
binding.libraryGridRecycler.recycler.manager.onRestoreInstanceState(staggeredBundle)
|
||||
staggeredBundle = null
|
||||
}
|
||||
} else {
|
||||
saveStaggeredState()
|
||||
updateFilterSheetY()
|
||||
closeTip()
|
||||
if (binding.filterBottomSheet.filterBottomSheet.sheetBehavior.isHidden()) {
|
||||
|
@ -1003,6 +1021,7 @@ class LibraryController(
|
|||
}
|
||||
displaySheet?.dismiss()
|
||||
displaySheet = null
|
||||
saveStaggeredState()
|
||||
super.onDestroyView(view)
|
||||
}
|
||||
|
||||
|
@ -1046,6 +1065,29 @@ class LibraryController(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (binding.libraryGridRecycler.recycler.manager is StaggeredGridLayoutManager && isControllerVisible) {
|
||||
staggeredObserver = ViewTreeObserver.OnGlobalLayoutListener {
|
||||
binding.libraryGridRecycler.recycler.postOnAnimation {
|
||||
if (!isControllerVisible) return@postOnAnimation
|
||||
scrollToHeader(activeC, false)
|
||||
activityBinding?.appBar?.y = 0f
|
||||
activityBinding?.appBar?.updateAppBarAfterY(binding.libraryGridRecycler.recycler)
|
||||
if (activeC > 0) {
|
||||
activityBinding?.appBar?.useSearchToolbarForMenu(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.libraryGridRecycler.recycler.viewTreeObserver.addOnGlobalLayoutListener(staggeredObserver)
|
||||
viewScope.launchUI {
|
||||
delay(500)
|
||||
removeStaggeredObserver()
|
||||
if (!isControllerVisible) return@launchUI
|
||||
if (activeC > 0) {
|
||||
activityBinding?.appBar?.useSearchToolbarForMenu(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isControllerVisible) {
|
||||
activityBinding?.appBar?.lockYPos = false
|
||||
|
@ -1155,7 +1197,10 @@ class LibraryController(
|
|||
}
|
||||
}
|
||||
|
||||
private fun scrollToHeader(pos: Int) {
|
||||
private fun scrollToHeader(pos: Int, removeObserver: Boolean = true) {
|
||||
if (removeObserver) {
|
||||
removeStaggeredObserver()
|
||||
}
|
||||
if (!presenter.showAllCategories) {
|
||||
presenter.switchSection(pos)
|
||||
activeCategory = pos
|
||||
|
@ -1168,7 +1213,7 @@ class LibraryController(
|
|||
val activityBinding = activityBinding ?: return
|
||||
val appbarOffset = if (pos <= 0) 0 else -fullAppBarHeight!! + activityBinding.cardFrame.height
|
||||
val previousHeader = adapter.getItem(adapter.indexOf(pos - 1)) as? LibraryHeaderItem
|
||||
(binding.libraryGridRecycler.recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
|
||||
binding.libraryGridRecycler.recycler.scrollToPositionWithOffset(
|
||||
headerPosition,
|
||||
(
|
||||
when {
|
||||
|
@ -1209,14 +1254,9 @@ class LibraryController(
|
|||
private fun reattachAdapter() {
|
||||
libraryLayout = preferences.libraryLayout().get()
|
||||
setRecyclerLayout()
|
||||
val position =
|
||||
(binding.libraryGridRecycler.recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
||||
val position = binding.libraryGridRecycler.recycler.findFirstVisibleItemPosition()
|
||||
binding.libraryGridRecycler.recycler.adapter = adapter
|
||||
|
||||
(binding.libraryGridRecycler.recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
|
||||
position,
|
||||
0
|
||||
)
|
||||
binding.libraryGridRecycler.recycler.scrollToPositionWithOffset(position, 0)
|
||||
}
|
||||
|
||||
fun search(query: String?): Boolean {
|
||||
|
@ -1333,6 +1373,7 @@ class LibraryController(
|
|||
override fun onItemClick(view: View?, position: Int): Boolean {
|
||||
val item = adapter.getItem(position) as? LibraryItem ?: return false
|
||||
return if (adapter.mode == SelectableAdapter.Mode.MULTI) {
|
||||
snack?.dismiss()
|
||||
lastClickPosition = position
|
||||
toggleSelection(position)
|
||||
false
|
||||
|
@ -1342,11 +1383,15 @@ class LibraryController(
|
|||
}
|
||||
}
|
||||
|
||||
private fun openManga(manga: Manga) = router.pushController(
|
||||
MangaDetailsController(
|
||||
manga
|
||||
).withFadeTransaction()
|
||||
)
|
||||
private fun saveStaggeredState() {
|
||||
if (binding.libraryGridRecycler.recycler.manager is StaggeredGridLayoutManager) {
|
||||
staggeredBundle = binding.libraryGridRecycler.recycler.manager.onSaveInstanceState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openManga(manga: Manga) {
|
||||
router.pushController(MangaDetailsController(manga).withFadeTransaction())
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a manga is long clicked.
|
||||
|
@ -1354,7 +1399,15 @@ class LibraryController(
|
|||
* @param position the position of the element clicked.
|
||||
*/
|
||||
override fun onItemLongClick(position: Int) {
|
||||
if (adapter.getItem(position) !is LibraryItem) return
|
||||
val item = adapter.getItem(position)
|
||||
if (item !is LibraryItem) return
|
||||
snack?.dismiss()
|
||||
if (libraryLayout == LibraryItem.LAYOUT_COVER_ONLY_GRID && actionMode == null) {
|
||||
snack = view?.snack(item.manga.title) {
|
||||
anchorView = activityBinding?.bottomNav
|
||||
view.elevation = 15f.dpToPx
|
||||
}
|
||||
}
|
||||
createActionModeIfNeeded()
|
||||
when {
|
||||
lastClickPosition == -1 -> setSelection(position)
|
||||
|
@ -1409,11 +1462,11 @@ class LibraryController(
|
|||
|
||||
override fun onItemMove(fromPosition: Int, toPosition: Int) {
|
||||
// Because padding a recycler causes it to scroll up we have to scroll it back down... wild
|
||||
if ((
|
||||
adapter.getItem(fromPosition) is LibraryItem &&
|
||||
adapter.getItem(fromPosition) is LibraryItem
|
||||
) ||
|
||||
adapter.getItem(fromPosition) == null
|
||||
val fromItem = adapter.getItem(fromPosition)
|
||||
val toItem = adapter.getItem(toPosition)
|
||||
if (binding.libraryGridRecycler.recycler.layoutManager !is StaggeredGridLayoutManager && (
|
||||
(fromItem is LibraryItem && toItem is LibraryItem) || fromItem == null
|
||||
)
|
||||
) {
|
||||
binding.libraryGridRecycler.recycler.scrollBy(
|
||||
0,
|
||||
|
|
|
@ -3,17 +3,25 @@ package eu.kanade.tachiyomi.ui.library
|
|||
import android.app.Activity
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import coil.clear
|
||||
import coil.size.Precision
|
||||
import coil.size.Scale
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.image.coil.loadManga
|
||||
import eu.kanade.tachiyomi.databinding.MangaGridItemBinding
|
||||
import eu.kanade.tachiyomi.util.lang.highlightText
|
||||
import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.view.backgroundColor
|
||||
import eu.kanade.tachiyomi.util.view.setCards
|
||||
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
||||
|
||||
/**
|
||||
* Class used to hold the displayed data of a manga in the library, like the cover or the title.
|
||||
|
@ -27,9 +35,8 @@ import eu.kanade.tachiyomi.util.view.setCards
|
|||
class LibraryGridHolder(
|
||||
private val view: View,
|
||||
adapter: LibraryCategoryAdapter,
|
||||
var width: Int,
|
||||
compact: Boolean,
|
||||
private var fixedSize: Boolean
|
||||
val fixedSize: Boolean
|
||||
) : LibraryHolder(view, adapter) {
|
||||
|
||||
private val binding = MangaGridItemBinding.bind(view)
|
||||
|
@ -60,6 +67,12 @@ class LibraryGridHolder(
|
|||
setCards(adapter.showOutline, binding.card, binding.unreadDownloadBadge.root)
|
||||
binding.constraintLayout.isVisible = !item.manga.isBlank()
|
||||
binding.title.text = item.manga.title.highlightText(item.filter, color)
|
||||
binding.behindTitle.text = item.manga.title
|
||||
val mangaColor = item.manga.dominantCoverColors
|
||||
binding.coverConstraint.backgroundColor = mangaColor?.first ?: itemView.context.getResourceColor(R.attr.background)
|
||||
binding.behindTitle.setTextColor(
|
||||
mangaColor?.second ?: itemView.context.getResourceColor(R.attr.colorOnBackground)
|
||||
)
|
||||
val authorArtist = if (item.manga.author == item.manga.artist || item.manga.artist.isNullOrBlank()) {
|
||||
item.manga.author?.trim() ?: ""
|
||||
} else {
|
||||
|
@ -109,12 +122,24 @@ class LibraryGridHolder(
|
|||
private fun setCover(manga: Manga) {
|
||||
if ((adapter.recyclerView.context as? Activity)?.isDestroyed == true) return
|
||||
binding.coverThumbnail.loadManga(manga) {
|
||||
if (!fixedSize) {
|
||||
val hasRatio = binding.coverThumbnail.layoutParams.height != ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
if (!fixedSize && !hasRatio) {
|
||||
precision(Precision.INEXACT)
|
||||
scale(Scale.FIT)
|
||||
}
|
||||
listener(
|
||||
onSuccess = { _, _ ->
|
||||
if (!fixedSize && !hasRatio && MangaCoverMetadata.getRatio(manga) != null) {
|
||||
setFreeformCoverRatio(manga)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setFreeformCoverRatio(manga: Manga, parent: AutofitRecyclerView? = null) {
|
||||
binding.setFreeformCoverRatio(manga, parent)
|
||||
}
|
||||
|
||||
private fun playButtonClicked() {
|
||||
adapter.libraryListener.startReading(flexibleAdapterPosition)
|
||||
|
@ -134,3 +159,30 @@ class LibraryGridHolder(
|
|||
binding.unreadDownloadBadge.badgeView.isDragged = false
|
||||
}
|
||||
}
|
||||
|
||||
fun MangaGridItemBinding.setFreeformCoverRatio(manga: Manga?, parent: AutofitRecyclerView? = null) {
|
||||
val ratio = manga?.let { MangaCoverMetadata.getRatio(it) }
|
||||
val itemWidth = parent?.itemWidth ?: root.width
|
||||
if (ratio != null) {
|
||||
coverThumbnail.adjustViewBounds = false
|
||||
coverThumbnail.maxHeight = Int.MAX_VALUE
|
||||
coverThumbnail.minimumHeight = 56.dpToPx
|
||||
constraintLayout.minHeight = 56.dpToPx
|
||||
} else {
|
||||
val coverHeight = (itemWidth / 3f * 4f).toInt()
|
||||
constraintLayout.minHeight = coverHeight / 2
|
||||
coverThumbnail.minimumHeight =
|
||||
(itemWidth / 3f * 3.6f).toInt()
|
||||
coverThumbnail.maxHeight = (itemWidth / 3f * 6f).toInt()
|
||||
coverThumbnail.adjustViewBounds = true
|
||||
}
|
||||
coverThumbnail.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
if (ratio != null) {
|
||||
height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
|
||||
dimensionRatio = "W,1:$ratio"
|
||||
} else {
|
||||
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
dimensionRatio = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.library
|
|||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
|
@ -32,6 +33,8 @@ class LibraryHeaderItem(
|
|||
payloads: MutableList<Any?>?
|
||||
) {
|
||||
holder.bind(this)
|
||||
val layoutParams = holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams
|
||||
layoutParams?.isFullSpan = true
|
||||
}
|
||||
|
||||
val category: Category
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
package eu.kanade.tachiyomi.ui.library
|
||||
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
|
||||
import eu.davidea.flexibleadapter.items.IFilterable
|
||||
|
@ -20,6 +21,7 @@ import eu.kanade.tachiyomi.databinding.MangaGridItemBinding
|
|||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.util.system.contextCompatDrawable
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.view.compatToolTipText
|
||||
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
@ -48,7 +50,7 @@ class LibraryItem(
|
|||
get() = preferences.hideStartReadingButton().get()
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return if (libraryLayout == 0 || manga.isBlank()) {
|
||||
return if (libraryLayout == LAYOUT_LIST || manga.isBlank()) {
|
||||
R.layout.manga_list_item
|
||||
} else {
|
||||
R.layout.manga_grid_item
|
||||
|
@ -60,22 +62,18 @@ class LibraryItem(
|
|||
return if (parent is AutofitRecyclerView) {
|
||||
val libraryLayout = libraryLayout
|
||||
val isFixedSize = uniformSize
|
||||
if (libraryLayout == 0 || manga.isBlank()) {
|
||||
if (libraryLayout == LAYOUT_LIST || manga.isBlank()) {
|
||||
LibraryListHolder(view, adapter as LibraryCategoryAdapter)
|
||||
} else {
|
||||
view.apply {
|
||||
val binding = MangaGridItemBinding.bind(this)
|
||||
val coverHeight = (parent.itemWidth / 3f * 4f).toInt()
|
||||
if (libraryLayout == 1) {
|
||||
binding.gradient.layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
(coverHeight * 0.66f).toInt(),
|
||||
Gravity.BOTTOM
|
||||
)
|
||||
binding.behindTitle.isVisible = libraryLayout == LAYOUT_COVER_ONLY_GRID
|
||||
if (libraryLayout == LAYOUT_COMPACT_GRID) {
|
||||
binding.card.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
bottomMargin = 6.dpToPx
|
||||
}
|
||||
} else if (libraryLayout == 2) {
|
||||
} else if (libraryLayout >= LAYOUT_COMFORTABLE_GRID) {
|
||||
binding.textLayout.isVisible = libraryLayout == LAYOUT_COMFORTABLE_GRID
|
||||
binding.constraintLayout.background = context.contextCompatDrawable(
|
||||
R.drawable.library_comfortable_grid_selector
|
||||
)
|
||||
|
@ -99,23 +97,22 @@ class LibraryItem(
|
|||
binding.constraintLayout.minHeight = 0
|
||||
binding.coverThumbnail.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
binding.coverThumbnail.adjustViewBounds = false
|
||||
binding.coverThumbnail.layoutParams = FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
(parent.itemWidth / 3f * 3.875f).toInt()
|
||||
)
|
||||
} else {
|
||||
binding.constraintLayout.minHeight = coverHeight / 2
|
||||
binding.coverThumbnail.minimumHeight = (parent.itemWidth / 3f * 3.6f).toInt()
|
||||
binding.coverThumbnail.maxHeight = (parent.itemWidth / 3f * 6f).toInt()
|
||||
binding.coverThumbnail.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
|
||||
dimensionRatio = "15:22"
|
||||
}
|
||||
}
|
||||
LibraryGridHolder(
|
||||
}
|
||||
val gridHolder = LibraryGridHolder(
|
||||
view,
|
||||
adapter as LibraryCategoryAdapter,
|
||||
parent.itemWidth,
|
||||
libraryLayout == 1,
|
||||
libraryLayout == LAYOUT_COMPACT_GRID,
|
||||
isFixedSize
|
||||
)
|
||||
if (!isFixedSize) {
|
||||
gridHolder.setFreeformCoverRatio(manga, parent)
|
||||
}
|
||||
gridHolder
|
||||
}
|
||||
} else {
|
||||
LibraryListHolder(view, adapter as LibraryCategoryAdapter)
|
||||
|
@ -128,8 +125,16 @@ class LibraryItem(
|
|||
position: Int,
|
||||
payloads: MutableList<Any?>?
|
||||
) {
|
||||
if (holder is LibraryGridHolder && !holder.fixedSize) {
|
||||
holder.setFreeformCoverRatio(manga, adapter.recyclerView as? AutofitRecyclerView)
|
||||
}
|
||||
holder.onSetValues(this)
|
||||
(holder as? LibraryGridHolder)?.setSelected(adapter.isSelected(position))
|
||||
val layoutParams = holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams
|
||||
layoutParams?.isFullSpan = manga.isBlank()
|
||||
if (libraryLayout == LAYOUT_COVER_ONLY_GRID) {
|
||||
holder.itemView.compatToolTipText = manga.title
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -196,4 +201,11 @@ class LibraryItem(
|
|||
result = 31 * result + (header?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_LIST = 0
|
||||
const val LAYOUT_COMPACT_GRID = 1
|
||||
const val LAYOUT_COMFORTABLE_GRID = 2
|
||||
const val LAYOUT_COVER_ONLY_GRID = 3
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
|||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.image.coil.MangaFetcher
|
||||
import eu.kanade.tachiyomi.data.preference.DelayedLibrarySuggestionsJob
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.minusAssign
|
||||
|
@ -35,6 +36,7 @@ import eu.kanade.tachiyomi.util.chapter.ChapterSort
|
|||
import eu.kanade.tachiyomi.util.lang.capitalizeWords
|
||||
import eu.kanade.tachiyomi.util.lang.chopByWords
|
||||
import eu.kanade.tachiyomi.util.lang.removeArticles
|
||||
import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
|
||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||
import eu.kanade.tachiyomi.util.system.launchIO
|
||||
import eu.kanade.tachiyomi.util.system.withUIContext
|
||||
|
@ -1218,6 +1220,16 @@ class LibraryPresenter(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun updateRatiosAndColors() {
|
||||
val db: DatabaseHelper = Injekt.get()
|
||||
val mangaFetcher = MangaFetcher()
|
||||
val libraryManga = db.getLibraryMangas().executeOnIO()
|
||||
libraryManga.forEach { manga ->
|
||||
mangaFetcher.setRatioAndColors(manga)
|
||||
}
|
||||
MangaCoverMetadata.savePrefs()
|
||||
}
|
||||
|
||||
fun updateCustoms() {
|
||||
val db: DatabaseHelper = Injekt.get()
|
||||
val cc: CoverCache = Injekt.get()
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.view.ViewGroup
|
|||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
|
@ -49,6 +50,8 @@ class SearchGlobalItem : AbstractFlexibleItem<SearchGlobalItem.Holder>() {
|
|||
payloads: MutableList<Any>
|
||||
) {
|
||||
holder.bind(string)
|
||||
val layoutParams = holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams
|
||||
layoutParams?.isFullSpan = true
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
|
|
|
@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.databinding.LibraryDisplayLayoutBinding
|
|||
import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet
|
||||
import eu.kanade.tachiyomi.ui.library.filter.ManageFilterItem
|
||||
import eu.kanade.tachiyomi.util.bindToPreference
|
||||
import eu.kanade.tachiyomi.util.lang.addBetaTag
|
||||
import eu.kanade.tachiyomi.util.lang.withSubtitle
|
||||
import eu.kanade.tachiyomi.util.system.bottomCutoutInset
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
|
@ -31,8 +32,13 @@ class LibraryDisplayView @JvmOverloads constructor(context: Context, attrs: Attr
|
|||
override fun inflateBinding() = LibraryDisplayLayoutBinding.bind(this)
|
||||
override fun initGeneralPreferences() {
|
||||
binding.displayGroup.bindToPreference(preferences.libraryLayout())
|
||||
binding.uniformGrid.bindToPreference(preferences.uniformGrid())
|
||||
binding.uniformGrid.bindToPreference(preferences.uniformGrid()) {
|
||||
binding.staggeredGrid.isEnabled = !it
|
||||
}
|
||||
binding.outlineOnCovers.bindToPreference(preferences.outlineOnCovers())
|
||||
binding.staggeredGrid.text = context.getString(R.string.use_staggered_grid).addBetaTag(context)
|
||||
binding.staggeredGrid.isEnabled = !preferences.uniformGrid().get()
|
||||
binding.staggeredGrid.bindToPreference(preferences.useStaggeredGrid())
|
||||
binding.gridSeekbar.value = ((preferences.gridSize().get() + .5f) * 2f).roundToInt().toFloat()
|
||||
binding.resetGridSize.setOnClickListener {
|
||||
binding.gridSeekbar.value = 3f
|
||||
|
|
|
@ -80,6 +80,7 @@ import eu.kanade.tachiyomi.ui.setting.SettingsController
|
|||
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
|
||||
import eu.kanade.tachiyomi.ui.source.BrowseController
|
||||
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
|
||||
import eu.kanade.tachiyomi.util.manga.MangaShortcutManager
|
||||
import eu.kanade.tachiyomi.util.system.contextCompatDrawable
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
|
@ -385,6 +386,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
|
|||
}
|
||||
|
||||
nav.isVisible = !hideBottomNav
|
||||
updateControllersWithSideNavChanges()
|
||||
binding.bottomView?.visibility = if (hideBottomNav) View.GONE else binding.bottomView?.visibility ?: View.GONE
|
||||
nav.alpha = if (hideBottomNav) 0f else 1f
|
||||
router.addChangeListener(
|
||||
|
@ -438,7 +440,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
|
|||
preferences.incognitoMode().set(false)
|
||||
|
||||
// Show changelog if needed
|
||||
if (Migrations.upgrade(preferences)) {
|
||||
if (Migrations.upgrade(preferences, lifecycleScope)) {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
content.post {
|
||||
whatsNewSheet().show()
|
||||
|
@ -642,7 +644,12 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
|
|||
super.onPause()
|
||||
snackBar?.dismiss()
|
||||
setStartingTab()
|
||||
saveExtras()
|
||||
}
|
||||
|
||||
fun saveExtras() {
|
||||
mangaShortcutManager.updateShortcuts()
|
||||
MangaCoverMetadata.savePrefs()
|
||||
}
|
||||
|
||||
private fun checkForAppUpdates() {
|
||||
|
@ -816,7 +823,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
|
|||
setStartingTab()
|
||||
}
|
||||
SecureActivityDelegate.locked = this !is SearchActivity
|
||||
mangaShortcutManager.updateShortcuts()
|
||||
saveExtras()
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
@ -1051,6 +1058,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
|
|||
nav.visibility = if (!hideBottomNav) View.VISIBLE else nav.visibility
|
||||
if (nav == binding.sideNav) {
|
||||
nav.isVisible = !hideBottomNav
|
||||
updateControllersWithSideNavChanges(from)
|
||||
nav.alpha = 1f
|
||||
} else {
|
||||
animationSet?.cancel()
|
||||
|
@ -1077,6 +1085,27 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateControllersWithSideNavChanges(extraController: Controller? = null) {
|
||||
if (!isBindingInitialized || !this::router.isInitialized) return
|
||||
binding.sideNav?.let { sideNav ->
|
||||
val controllers = (router.backstack.map { it?.controller } + extraController)
|
||||
.filterNotNull()
|
||||
.distinct()
|
||||
val navWidth = sideNav.width.takeIf { it != 0 } ?: 80.dpToPx
|
||||
controllers.forEach { controller ->
|
||||
val isRootController = controller is RootSearchInterface
|
||||
if (controller.view?.layoutParams !is ViewGroup.MarginLayoutParams) return@forEach
|
||||
controller.view?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
marginStart = if (sideNav.isVisible) {
|
||||
if (isRootController) 0 else -navWidth
|
||||
} else {
|
||||
if (isRootController) navWidth else 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showTabBar(show: Boolean, animate: Boolean = true) {
|
||||
tabAnimation?.cancel()
|
||||
if (animate) {
|
||||
|
|
|
@ -16,6 +16,7 @@ import eu.kanade.tachiyomi.databinding.MigrationProcessItemBinding
|
|||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.ui.library.setFreeformCoverRatio
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import eu.kanade.tachiyomi.util.view.setCards
|
||||
|
@ -50,6 +51,9 @@ class MigrationProcessHolder(
|
|||
fun bind(item: MigrationProcessItem) {
|
||||
this.item = item
|
||||
launchUI {
|
||||
binding.migrationMangaCardFrom.setFreeformCoverRatio(item.manga.manga())
|
||||
binding.migrationMangaCardTo.setFreeformCoverRatio(null)
|
||||
|
||||
val manga = item.manga.manga()
|
||||
val source = item.manga.mangaSource()
|
||||
|
||||
|
|
|
@ -75,10 +75,10 @@ class BrowseSourceItem(
|
|||
binding.constraintLayout.minHeight = 0
|
||||
binding.coverThumbnail.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
binding.coverThumbnail.adjustViewBounds = false
|
||||
binding.coverThumbnail.layoutParams = FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
(parent.itemWidth / 3f * 3.7f).toInt()
|
||||
)
|
||||
binding.coverThumbnail.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
|
||||
dimensionRatio = "15:22"
|
||||
}
|
||||
}
|
||||
BrowseSourceGridHolder(view, adapter, listType == 1, outlineOnCovers.get())
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package eu.kanade.tachiyomi.util.manga
|
||||
|
||||
import androidx.annotation.ColorInt
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/** Object that holds info about a covers size ratio + dominant colors */
|
||||
object MangaCoverMetadata {
|
||||
private var coverRatioMap = ConcurrentHashMap<Long, Float>()
|
||||
private var coverColorMap = ConcurrentHashMap<Long, Pair<Int, Int>>()
|
||||
val preferences by injectLazy<PreferencesHelper>()
|
||||
|
||||
fun load() {
|
||||
val ratios = preferences.coverRatios().get()
|
||||
coverRatioMap = ConcurrentHashMap(
|
||||
ratios.mapNotNull {
|
||||
val splits = it.split("|")
|
||||
val id = splits.firstOrNull()?.toLongOrNull()
|
||||
val ratio = splits.lastOrNull()?.toFloatOrNull()
|
||||
if (id != null && ratio != null) {
|
||||
id to ratio
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.toMap()
|
||||
)
|
||||
val colors = preferences.coverColors().get()
|
||||
coverColorMap = ConcurrentHashMap(
|
||||
colors.mapNotNull {
|
||||
val splits = it.split("|")
|
||||
val id = splits.firstOrNull()?.toLongOrNull()
|
||||
val color = splits.getOrNull(1)?.toIntOrNull()
|
||||
val textColor = splits.getOrNull(2)?.toIntOrNull()
|
||||
if (id != null && color != null) {
|
||||
id to (color to (textColor ?: 0))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.toMap()
|
||||
)
|
||||
}
|
||||
|
||||
fun remove(manga: Manga) {
|
||||
val id = manga.id ?: return
|
||||
coverRatioMap.remove(id)
|
||||
coverColorMap.remove(id)
|
||||
}
|
||||
|
||||
fun addCoverRatio(manga: Manga, ratio: Float) {
|
||||
val id = manga.id ?: return
|
||||
coverRatioMap[id] = ratio
|
||||
}
|
||||
|
||||
fun addCoverColor(manga: Manga, @ColorInt color: Int, @ColorInt textColor: Int) {
|
||||
val id = manga.id ?: return
|
||||
coverColorMap[id] = color to textColor
|
||||
}
|
||||
|
||||
fun getColors(manga: Manga): Pair<Int, Int>? {
|
||||
return coverColorMap[manga.id]
|
||||
}
|
||||
|
||||
fun getRatio(manga: Manga): Float? {
|
||||
return coverRatioMap[manga.id]
|
||||
}
|
||||
|
||||
fun savePrefs() {
|
||||
val mapCopy = coverRatioMap.toMap()
|
||||
preferences.coverRatios().set(mapCopy.map { "${it.key}|${it.value}" }.toSet())
|
||||
val mapColorCopy = coverColorMap.toMap()
|
||||
preferences.coverColors().set(mapColorCopy.map { "${it.key}|${it.value.first}|${it.value.second}" }.toSet())
|
||||
}
|
||||
}
|
|
@ -34,6 +34,7 @@ import androidx.core.view.updatePaddingRelative
|
|||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
|
@ -579,6 +580,7 @@ fun Controller.moveRecyclerViewUp(allTheWayUp: Boolean = false, scrollUpAnyway:
|
|||
val appBarOffset = activityBinding.appBar.toolbarDistanceToTop
|
||||
if (allTheWayUp && recycler.computeVerticalScrollOffset() - recycler.paddingTop <= fullAppBarHeight ?: activityBinding.appBar.preLayoutHeight) {
|
||||
(recycler.layoutManager as? LinearLayoutManager)?.scrollToPosition(0)
|
||||
(recycler.layoutManager as? StaggeredGridLayoutManager)?.scrollToPosition(0)
|
||||
recycler.post {
|
||||
activityBinding.appBar.updateAppBarAfterY(recycler)
|
||||
activityBinding.appBar.useSearchToolbarForMenu(false)
|
||||
|
@ -588,6 +590,8 @@ fun Controller.moveRecyclerViewUp(allTheWayUp: Boolean = false, scrollUpAnyway:
|
|||
if (scrollUpAnyway || recycler.computeVerticalScrollOffset() - recycler.paddingTop <= 0 - appBarOffset) {
|
||||
(recycler.layoutManager as? LinearLayoutManager)
|
||||
?.scrollToPositionWithOffset(0, activityBinding.appBar.yNeededForSmallToolbar)
|
||||
(recycler.layoutManager as? StaggeredGridLayoutManager)
|
||||
?.scrollToPositionWithOffset(0, activityBinding.appBar.yNeededForSmallToolbar)
|
||||
recycler.post {
|
||||
activityBinding.appBar.updateAppBarAfterY(recycler)
|
||||
activityBinding.appBar.useSearchToolbarForMenu(recycler.computeVerticalScrollOffset() != 0)
|
||||
|
|
|
@ -77,6 +77,7 @@ import eu.kanade.tachiyomi.util.system.powerManager
|
|||
import eu.kanade.tachiyomi.util.system.pxToDp
|
||||
import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
|
||||
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
||||
import eu.kanade.tachiyomi.widget.StaggeredGridLayoutManagerAccurateOffset
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
|
@ -310,21 +311,25 @@ fun NavigationBarView.getItemView(@IdRes id: Int): NavigationBarItemView? {
|
|||
|
||||
fun RecyclerView.smoothScrollToTop() {
|
||||
val linearLayoutManager = layoutManager as? LinearLayoutManager
|
||||
if (linearLayoutManager != null) {
|
||||
val staggeredLayoutManager = layoutManager as? StaggeredGridLayoutManagerAccurateOffset
|
||||
if (linearLayoutManager != null || staggeredLayoutManager != null) {
|
||||
val smoothScroller: SmoothScroller = object : LinearSmoothScroller(context) {
|
||||
override fun getVerticalSnapPreference(): Int {
|
||||
return SNAP_TO_START
|
||||
}
|
||||
}
|
||||
smoothScroller.targetPosition = 0
|
||||
val firstItemPos = linearLayoutManager.findFirstVisibleItemPosition()
|
||||
val firstItemPos = linearLayoutManager?.findFirstVisibleItemPosition()
|
||||
?: staggeredLayoutManager?.findFirstVisibleItemPosition() ?: 0
|
||||
if (firstItemPos > 15) {
|
||||
scrollToPosition(15)
|
||||
post {
|
||||
linearLayoutManager.startSmoothScroll(smoothScroller)
|
||||
linearLayoutManager?.startSmoothScroll(smoothScroller)
|
||||
staggeredLayoutManager?.startSmoothScroll(smoothScroller)
|
||||
}
|
||||
} else {
|
||||
linearLayoutManager.startSmoothScroll(smoothScroller)
|
||||
linearLayoutManager?.startSmoothScroll(smoothScroller)
|
||||
staggeredLayoutManager?.startSmoothScroll(smoothScroller)
|
||||
}
|
||||
} else {
|
||||
scrollToPosition(0)
|
||||
|
|
|
@ -3,10 +3,19 @@ package eu.kanade.tachiyomi.widget
|
|||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.WindowInsetsCompat.Type.systemBars
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.system.pxToDp
|
||||
import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
@ -14,7 +23,7 @@ import kotlin.math.roundToInt
|
|||
class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
androidx.recyclerview.widget.RecyclerView(context, attrs) {
|
||||
|
||||
val manager = GridLayoutManagerAccurateOffset(context, 1)
|
||||
var manager: LayoutManager = GridLayoutManagerAccurateOffset(context, 1)
|
||||
|
||||
var lastMeasuredWidth = 0
|
||||
var columnWidth = -1f
|
||||
|
@ -29,20 +38,29 @@ class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: Att
|
|||
set(value) {
|
||||
field = value
|
||||
if (value > 0) {
|
||||
manager.spanCount = value
|
||||
managerSpanCount = value
|
||||
}
|
||||
}
|
||||
|
||||
val itemWidth: Int
|
||||
get() {
|
||||
return if (spanCount == 0) measuredWidth / getTempSpan()
|
||||
else measuredWidth / manager.spanCount
|
||||
else measuredWidth / managerSpanCount
|
||||
}
|
||||
|
||||
init {
|
||||
layoutManager = manager
|
||||
}
|
||||
|
||||
var managerSpanCount: Int
|
||||
get() {
|
||||
return (manager as? GridLayoutManager)?.spanCount ?: (manager as StaggeredGridLayoutManager).spanCount
|
||||
}
|
||||
set(value) {
|
||||
(manager as? GridLayoutManager)?.spanCount = value
|
||||
(manager as? StaggeredGridLayoutManager)?.spanCount = value
|
||||
}
|
||||
|
||||
private fun getTempSpan(): Int {
|
||||
if (spanCount == 0 && columnWidth > 0) {
|
||||
val dpWidth = (measuredWidth.pxToDp / 100f).roundToInt()
|
||||
|
@ -54,7 +72,67 @@ class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: Att
|
|||
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
||||
super.onMeasure(widthSpec, heightSpec)
|
||||
setSpan()
|
||||
lastMeasuredWidth = measuredWidth
|
||||
if (width == 0) {
|
||||
spanCount = getTempSpan()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||
super.onLayout(changed, l, t, r, b)
|
||||
setSpan()
|
||||
lastMeasuredWidth = width
|
||||
}
|
||||
|
||||
fun useStaggered(preferences: PreferencesHelper) {
|
||||
useStaggered(
|
||||
preferences.useStaggeredGrid().get() &&
|
||||
!preferences.uniformGrid().get() &&
|
||||
preferences.libraryLayout().get() != LibraryItem.LAYOUT_LIST
|
||||
)
|
||||
}
|
||||
|
||||
private fun useStaggered(use: Boolean) {
|
||||
if (use && manager !is StaggeredGridLayoutManagerAccurateOffset) {
|
||||
manager = StaggeredGridLayoutManagerAccurateOffset(
|
||||
context,
|
||||
null,
|
||||
1,
|
||||
StaggeredGridLayoutManager.VERTICAL
|
||||
)
|
||||
setNewManager()
|
||||
} else if (!use && manager !is GridLayoutManagerAccurateOffset) {
|
||||
manager = GridLayoutManagerAccurateOffset(context, 1)
|
||||
setNewManager()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setNewManager() {
|
||||
val firstPos = findFirstVisibleItemPosition().takeIf { it != NO_POSITION }
|
||||
layoutManager = manager
|
||||
if (firstPos != null) {
|
||||
val insetsTop = rootWindowInsetsCompat?.getInsets(systemBars())?.top ?: 0
|
||||
doOnNextLayout {
|
||||
scrollToPositionWithOffset(firstPos, -paddingTop + 56.dpToPx + insetsTop)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun scrollToPositionWithOffset(position: Int, offset: Int) {
|
||||
layoutManager ?: return
|
||||
return (layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(position, offset)
|
||||
?: (layoutManager as StaggeredGridLayoutManagerAccurateOffset).scrollToPositionWithOffset(position, offset)
|
||||
}
|
||||
|
||||
fun findFirstVisibleItemPosition(): Int {
|
||||
layoutManager ?: return 0
|
||||
return (layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition()
|
||||
?: (layoutManager as StaggeredGridLayoutManagerAccurateOffset).findFirstVisibleItemPosition()
|
||||
}
|
||||
|
||||
fun findFirstCompletelyVisibleItemPosition(): Int {
|
||||
layoutManager ?: return 0
|
||||
return (layoutManager as? LinearLayoutManager)?.findFirstCompletelyVisibleItemPosition()
|
||||
?: (layoutManager as StaggeredGridLayoutManagerAccurateOffset).findFirstCompletelyVisibleItemPosition()
|
||||
}
|
||||
|
||||
fun setGridSize(preferences: PreferencesHelper) {
|
||||
|
@ -85,8 +163,14 @@ class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: Att
|
|||
}
|
||||
|
||||
private fun setSpan(force: Boolean = false) {
|
||||
if ((spanCount == 0 || force || measuredHeight != lastMeasuredWidth) && columnWidth > 0) {
|
||||
val dpWidth = (measuredWidth.pxToDp / 100f).roundToInt()
|
||||
if ((
|
||||
spanCount == 0 || force ||
|
||||
// Add 100dp check to make sure we dont update span for sidenav changes
|
||||
(width != lastMeasuredWidth && abs(width - lastMeasuredWidth) > 100.dpToPx)
|
||||
) &&
|
||||
columnWidth > 0
|
||||
) {
|
||||
val dpWidth = (width.pxToDp / 100f).roundToInt()
|
||||
val count = max(1, (dpWidth / columnWidth).roundToInt())
|
||||
spanCount = count
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package eu.kanade.tachiyomi.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
class StaggeredGridLayoutManagerAccurateOffset(context: Context?, attr: AttributeSet?, spanCount: Int, orientation: Int) :
|
||||
StaggeredGridLayoutManager(context, attr, spanCount, orientation) {
|
||||
|
||||
var rView: RecyclerView? = null
|
||||
|
||||
private val toolbarHeight by lazy {
|
||||
val attrsArray = intArrayOf(R.attr.mainActionBarSize)
|
||||
val array = (context ?: rView?.context)?.obtainStyledAttributes(attrsArray)
|
||||
val height = array?.getDimensionPixelSize(0, 0) ?: 0
|
||||
array?.recycle()
|
||||
height
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow(view: RecyclerView?) {
|
||||
super.onAttachedToWindow(view)
|
||||
rView = view
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow(view: RecyclerView?, recycler: RecyclerView.Recycler?) {
|
||||
super.onDetachedFromWindow(view, recycler)
|
||||
rView = null
|
||||
}
|
||||
|
||||
override fun computeVerticalScrollOffset(state: RecyclerView.State): Int {
|
||||
if (childCount == 0) {
|
||||
return 0
|
||||
}
|
||||
rView ?: return super.computeVerticalScrollOffset(state)
|
||||
val firstChild = (0 until childCount)
|
||||
.mapNotNull { getChildAt(it) }
|
||||
.mapNotNull { pos -> (pos to getPosition(pos)).takeIf { it.second != RecyclerView.NO_POSITION } }
|
||||
.minByOrNull { it.second } ?: return 0
|
||||
val scrolledY: Int = -firstChild.first.y.toInt()
|
||||
return if (firstChild.second == 0) {
|
||||
scrolledY + paddingTop
|
||||
} else {
|
||||
super.computeVerticalScrollOffset(state)
|
||||
}
|
||||
}
|
||||
|
||||
fun findFirstVisibleItemPosition(): Int {
|
||||
return getFirstPos(rView, toolbarHeight)
|
||||
}
|
||||
|
||||
fun findFirstCompletelyVisibleItemPosition(): Int {
|
||||
return getFirstCompletePos(rView, toolbarHeight)
|
||||
}
|
||||
}
|
|
@ -39,6 +39,11 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/comfortable_grid" />
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/cover_only_grid" />
|
||||
</RadioGroup>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
@ -102,6 +107,14 @@
|
|||
android:layout_marginEnd="12dp"
|
||||
android:text="@string/uniform_grid_covers" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/staggered_grid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="@string/use_staggered_grid" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/outline_on_covers"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -31,16 +31,58 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="1.0">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/cover_constraint"
|
||||
android:layout_width="match_parent"
|
||||
android:background="?attr/background"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/behind_title"
|
||||
style="?textAppearanceBodyMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:textColor="?colorOnBackground"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:textAlignment="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="3"
|
||||
app:layout_constraintTop_toTopOf="@id/cover_thumbnail"
|
||||
app:layout_constraintBottom_toBottomOf="@id/cover_thumbnail"
|
||||
app:layout_constraintEnd_toEndOf="@id/cover_thumbnail"
|
||||
app:layout_constraintStart_toStartOf="@id/cover_thumbnail"
|
||||
tools:text="Sample name" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cover_thumbnail"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:alpha="0.5"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
tools:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:background="?attr/background"
|
||||
tools:ignore="ContentDescription"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
tools:src="@mipmap/ic_launcher" />
|
||||
|
||||
<View
|
||||
android:id="@+id/gradient"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="125sp"
|
||||
android:alpha="0.75"
|
||||
android:background="@drawable/gradient_shape"
|
||||
app:layout_constraintStart_toStartOf="@id/cover_thumbnail"
|
||||
app:layout_constraintEnd_toEndOf="@id/cover_thumbnail"
|
||||
app:layout_constraintBottom_toBottomOf="@id/cover_thumbnail"
|
||||
app:layout_constraintVertical_bias="0.0" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/play_layout"
|
||||
android:layout_width="50dp"
|
||||
|
@ -67,14 +109,6 @@
|
|||
|
||||
</FrameLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/gradient"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="150dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:alpha="0.75"
|
||||
android:background="@drawable/gradient_shape" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/compact_title"
|
||||
style="?textAppearanceLabelMedium"
|
||||
|
|
|
@ -174,12 +174,14 @@
|
|||
<string name="can_be_found_in_library_filters">Can also be found by expanding library filters</string>
|
||||
<string name="list">List</string>
|
||||
<string name="comfortable_grid">Comfortable Grid</string>
|
||||
<string name="cover_only_grid">Cover-only grid</string>
|
||||
<string name="compact_grid">Compact Grid</string>
|
||||
<string name="download_badge">Download badges</string>
|
||||
<string name="language_badge">Language badges</string>
|
||||
<string name="hide_start_reading_button">Hide start reading button</string>
|
||||
<string name="badges">Badges</string>
|
||||
<string name="uniform_grid_covers">Uniform grid covers</string>
|
||||
<string name="use_staggered_grid">Use staggered grid</string>
|
||||
<string name="show_outline_around_covers">Show outline around covers</string>
|
||||
<string name="grid_size">Grid size</string>
|
||||
<string name="_per_row">%d per row</string>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue