Expanded toolbar (#1197)

* 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

* fix disappearing appbar in some cases

* 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

* move manager offsets to new files

* 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 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

* Cleanup BaseToolbar

* Refactoring ExpandedAppBarLayout

* misc cleanup

* 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

* 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 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

* 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

* better handling of menu item in search toolbar

* minimize use of obtainStyledAttributes of mainActionBarSize
This commit is contained in:
Jays2Kings 2022-04-20 21:14:11 -04:00 committed by GitHub
parent f372a4b81f
commit 92b98cef5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 2333 additions and 750 deletions

View file

@ -205,8 +205,6 @@ object PreferenceKeys {
const val refreshCoversToo = "refresh_covers_too"
const val updateOnRefresh = "update_on_refresh"
const val showDLsInRecents = "show_dls_in_recents"
const val showRemHistoryInRecents = "show_rem_history_in_recents"
const val showReadInAllRecents = "show_read_in_all_recents"
@ -237,6 +235,8 @@ object PreferenceKeys {
const val themeMangaDetails = "theme_manga_details"
const val useLargeToolbar = "use_large_toolbar"
const val incognitoMode = "incognito_mode"
const val sideNavMode = "side_nav_mode"

View file

@ -337,8 +337,6 @@ class PreferencesHelper(val context: Context) {
fun refreshCoversToo() = flowPrefs.getBoolean(Keys.refreshCoversToo, true)
fun updateOnRefresh() = flowPrefs.getInt(Keys.updateOnRefresh, -1)
fun extensionUpdatesCount() = flowPrefs.getInt("ext_updates_count", 0)
fun recentsViewType() = flowPrefs.getInt("recents_view_type", 0)
@ -410,6 +408,8 @@ class PreferencesHelper(val context: Context) {
fun themeMangaDetails() = prefs.getBoolean(Keys.themeMangaDetails, true)
fun useLargeToolbar() = prefs.getBoolean(Keys.useLargeToolbar, true)
fun dohProvider() = prefs.getInt(Keys.dohProvider, -1)
fun showSeriesInShortcuts() = prefs.getBoolean(Keys.showSeriesInShortcuts, true)

View file

@ -8,6 +8,7 @@ import androidx.core.view.isVisible
import com.bluelinelabs.conductor.Router
import com.google.android.material.appbar.MaterialToolbar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.main.SearchActivity
open class BaseToolbar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
@ -17,6 +18,10 @@ open class BaseToolbar @JvmOverloads constructor(context: Context, attrs: Attrib
val onRoot: Boolean
get() = router?.backstackSize ?: 1 <= 1 && context !is SearchActivity
val canShowIncogOnMain: Boolean
get() = router?.backstack?.lastOrNull()?.controller !is FloatingSearchInterface ||
this !is CenteredToolbar
lateinit var toolbarTitle: TextView
protected set
private val defStyleRes = com.google.android.material.R.style.Widget_Material3_Toolbar
@ -72,7 +77,7 @@ open class BaseToolbar @JvmOverloads constructor(context: Context, attrs: Attrib
@DrawableRes
private fun getIncogRes(): Int {
return when {
incognito -> R.drawable.ic_incognito_circle_24dp
incognito && canShowIncogOnMain -> R.drawable.ic_incognito_circle_24dp
else -> 0
}
}
@ -80,7 +85,7 @@ open class BaseToolbar @JvmOverloads constructor(context: Context, attrs: Attrib
@DrawableRes
private fun getDropdownRes(): Int {
return when {
incognito && onRoot -> R.drawable.ic_blank_28dp
incognito && onRoot && canShowIncogOnMain -> R.drawable.ic_blank_28dp
else -> 0
}
}

View file

@ -0,0 +1,375 @@
package eu.kanade.tachiyomi.ui.base
import android.content.Context
import android.content.res.Configuration
import android.util.AttributeSet
import android.view.View
import android.view.ViewPropertyAnimator
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.core.math.MathUtils
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.marginTop
import androidx.core.view.updateLayoutParams
import androidx.core.widget.TextViewCompat
import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.Controller
import com.google.android.material.appbar.AppBarLayout
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.isTablet
import eu.kanade.tachiyomi.util.view.backgroundColor
import eu.kanade.tachiyomi.util.view.setTextColorAlpha
import uy.kohesive.injekt.injectLazy
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
class ExpandedAppBarLayout@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
AppBarLayout(context, attrs) {
var cardToolbar: FloatingToolbar? = null
var cardFrame: FrameLayout? = null
var mainToolbar: CenteredToolbar? = null
var bigTitleView: TextView? = null
val preferences: PreferencesHelper by injectLazy()
var bigView: View? = null
var imageView: ImageView? = null
private var tabsFrameLayout: FrameLayout? = null
var mainActivity: MainActivity? = null
private var isExtraSmall = false
val useLargeToolbar: Boolean
get() = preferences.useLargeToolbar() && !isExtraSmall
/** Defines how the toolbar layout should be */
private var toolbarMode = ToolbarState.EXPANDED
set(value) {
field = value
if (value == ToolbarState.SEARCH_ONLY) {
mainToolbar?.isGone = true
} else if (value == ToolbarState.COMPACT) {
mainToolbar?.alpha = 1f
mainToolbar?.isVisible = true
}
if (value != ToolbarState.EXPANDED) {
mainToolbar?.translationY = 0f
y = 0f
}
}
var useTabsInPreLayout = false
var yAnimator: ViewPropertyAnimator? = null
/**
* used to ignore updates to y
*
* use only on controller.onViewCreated that asynchronously loads the first set of items
* and make false once the recycler has items
*/
var lockYPos = false
/** A value used to determine the offset needed for a recycler to land just under the smaller toolbar */
val toolbarDistanceToTop: Int
get() {
val tabHeight = if (tabsFrameLayout?.isVisible == true) 48.dpToPx else 0
return paddingTop - (mainToolbar?.height ?: 0) - tabHeight
}
/** A value used to determine the offset needed for a appbar's y to show only the smaller toolbar */
val yNeededForSmallToolbar: Int
get() {
val tabHeight = if (tabsFrameLayout?.isVisible == true) 48.dpToPx else 0
return -preLayoutHeight + (mainToolbar?.height ?: 0) + tabHeight
}
val attrToolbarHeight: Int = let {
val attrsArray = intArrayOf(R.attr.mainActionBarSize)
val array = it.context.obtainStyledAttributes(attrsArray)
val height = array.getDimensionPixelSize(0, 0)
array.recycle()
height
}
val preLayoutHeight: Int
get() = getEstimatedLayout(
cardFrame?.isVisible == true && toolbarMode == ToolbarState.EXPANDED,
useTabsInPreLayout,
toolbarMode == ToolbarState.EXPANDED
)
/** Small toolbar height + top system insets, same size as a collapsed appbar */
private val compactAppBarHeight: Float
get() {
val appBarHeight = if (mainToolbar?.height ?: 0 > 0) {
mainToolbar?.height ?: 0
} else {
attrToolbarHeight
}
return (appBarHeight + paddingTop).toFloat()
}
/** Used to restrain how far up the app bar can go up. Tablets stop at the smaller toolbar */
private val minTabletHeight: Int
get() {
val tabHeight = if (tabsFrameLayout?.isVisible == true) 48.dpToPx else 0
return if (context.isTablet()) {
(mainToolbar?.height ?: 0) + paddingTop + tabHeight
} else {
0
}
}
enum class ToolbarState {
EXPANDED,
COMPACT,
SEARCH_ONLY,
}
fun setToolbarModeBy(controller: Controller?, useSmall: Boolean? = null) {
toolbarMode = if (useSmall ?: !useLargeToolbar) {
when {
controller is FloatingSearchInterface && controller.showFloatingBar() -> {
ToolbarState.SEARCH_ONLY
}
else -> ToolbarState.COMPACT
}
} else {
when (controller) {
is SmallToolbarInterface -> {
if (controller is FloatingSearchInterface && controller.showFloatingBar()) {
ToolbarState.SEARCH_ONLY
} else {
ToolbarState.COMPACT
}
}
else -> ToolbarState.EXPANDED
}
}
}
fun hideBigView(useSmall: Boolean, force: Boolean? = null, setTitleAlpha: Boolean = true) {
val useSmallAnyway = force ?: (useSmall || !useLargeToolbar)
bigView?.isGone = useSmallAnyway
if (useSmallAnyway) {
mainToolbar?.backgroundColor = null
if (!setTitleAlpha) return
mainToolbar?.toolbarTitle?.setTextColorAlpha(255)
}
}
override fun onFinishInflate() {
super.onFinishInflate()
bigTitleView = findViewById(R.id.big_title)
cardToolbar = findViewById(R.id.card_toolbar)
mainToolbar = findViewById(R.id.toolbar)
bigView = findViewById(R.id.big_toolbar)
cardFrame = findViewById(R.id.card_frame)
tabsFrameLayout = findViewById(R.id.tabs_frame_layout)
imageView = findViewById(R.id.big_icon)
shrinkAppBarIfNeeded(resources.configuration)
}
fun setTitle(title: CharSequence?, setBigTitle: Boolean) {
if (setBigTitle) {
bigTitleView?.text = title
}
mainToolbar?.title = title
}
override fun setTranslationY(translationY: Float) {
if (lockYPos) return
val realHeight = (preLayoutHeight + paddingTop).toFloat()
val newY = MathUtils.clamp(translationY, -realHeight + minTabletHeight, 0f)
super.setTranslationY(newY)
}
fun getEstimatedLayout(includeSearchToolbar: Boolean, includeTabs: Boolean, includeLargeToolbar: Boolean): Int {
val hasLargeToolbar = includeLargeToolbar && useLargeToolbar
val appBarHeight = attrToolbarHeight * (if (includeSearchToolbar && hasLargeToolbar) 2 else 1)
val widthMeasureSpec = MeasureSpec.makeMeasureSpec(resources.displayMetrics.widthPixels, MeasureSpec.AT_MOST)
val heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
bigTitleView?.measure(widthMeasureSpec, heightMeasureSpec)
val textHeight = max(bigTitleView?.height ?: 0, bigTitleView?.measuredHeight ?: 0) +
(bigTitleView?.marginTop?.plus(bigView?.paddingBottom ?: 0) ?: 0)
return appBarHeight + (if (hasLargeToolbar) textHeight else 0) +
if (includeTabs) 48.dpToPx else 0
}
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
shrinkAppBarIfNeeded(newConfig)
}
private fun shrinkAppBarIfNeeded(config: Configuration?) {
config ?: return
isExtraSmall = false
if (config.screenHeightDp < 600) {
val bigTitleView = bigTitleView ?: return
isExtraSmall = config.screenWidthDp < 720
if (isExtraSmall) {
setToolbarModeBy(null, true)
return
}
val attrs = intArrayOf(R.attr.textAppearanceHeadlineMedium)
val ta = context.obtainStyledAttributes(attrs)
val resId = ta.getResourceId(0, 0)
ta.recycle()
TextViewCompat.setTextAppearance(bigTitleView, resId)
bigTitleView.setTextColor(context.getResourceColor(R.attr.actionBarTintColor))
bigTitleView.updateLayoutParams<MarginLayoutParams> {
topMargin = 12.dpToPx
}
imageView?.updateLayoutParams<MarginLayoutParams> {
height = 48.dpToPx
width = 48.dpToPx
}
}
}
fun updateAppBarAfterY(recyclerView: RecyclerView?, cancelAnim: Boolean = true) {
if (cancelAnim) {
yAnimator?.cancel()
}
if (lockYPos) return
val offset = recyclerView?.computeVerticalScrollOffset() ?: 0
val bigHeight = bigView?.height ?: 0
val realHeight = preLayoutHeight + paddingTop
val tabHeight = if (tabsFrameLayout?.isVisible == true) 48.dpToPx else 0
val shortH = if (toolbarMode != ToolbarState.EXPANDED) 0f else compactAppBarHeight
val smallHeight = -realHeight + shortH + tabHeight
val newY = when {
toolbarMode != ToolbarState.EXPANDED -> {
translationY
}
offset < realHeight - shortH - tabHeight -> {
-offset.toFloat()
}
else -> {
MathUtils.clamp(
translationY,
-realHeight.toFloat() + top + minTabletHeight,
max(
smallHeight,
if (offset > realHeight - shortH - tabHeight) smallHeight else min(
-offset.toFloat(),
0f
)
) + top.toFloat()
)
}
}
translationY = newY
mainToolbar?.let { mainToolbar ->
mainToolbar.translationY = when {
toolbarMode != ToolbarState.EXPANDED -> 0f
-newY <= bigHeight -> max(-newY, 0f)
else -> bigHeight.toFloat()
}
}
if (toolbarMode != ToolbarState.EXPANDED) {
useSearchToolbarForMenu(offset > realHeight - shortH - tabHeight)
return
}
val alpha =
(bigHeight + newY * 2) / (bigHeight) + 0.45f // (realHeight.toFloat() + newY * 5) / realHeight.toFloat() + .33f
bigView?.alpha = MathUtils.clamp(if (alpha.isNaN()) 1f else alpha, 0f, 1f)
val toolbarTextView = mainToolbar?.toolbarTitle ?: return
toolbarTextView.setTextColorAlpha(
(
MathUtils.clamp(
(1 - ((if (alpha.isNaN()) 1f else alpha) + 0.95f)) * 2, 0f, 1f
) * 255
).roundToInt()
)
val mainToolbar = mainToolbar ?: return
mainToolbar.alpha = MathUtils.clamp(
(mainToolbar.bottom + mainToolbar.translationY + y - paddingTop) / mainToolbar.height,
0f,
1f
)
val mainActivity = mainActivity ?: return
val useSearchToolbar = mainToolbar.alpha <= 0.025f
val idle = RecyclerView.SCROLL_STATE_IDLE
if (if (useSearchToolbar) -y >= height || recyclerView?.scrollState ?: idle <= idle || context.isTablet()
else mainActivity.currentToolbar == cardToolbar
) {
useSearchToolbarForMenu(useSearchToolbar)
}
}
fun snapAppBarY(recyclerView: RecyclerView, callback: (() -> Unit)?): Float {
yAnimator?.cancel()
val halfWay = compactAppBarHeight / 2
val shortAnimationDuration = resources?.getInteger(
if (toolbarMode != ToolbarState.EXPANDED) android.R.integer.config_shortAnimTime
else android.R.integer.config_longAnimTime
) ?: 0
val realHeight = preLayoutHeight + paddingTop
val closerToTop = abs(y) > realHeight - halfWay
val atTop = !recyclerView.canScrollVertically(-1)
val shortH = if (toolbarMode != ToolbarState.EXPANDED) 0f else compactAppBarHeight
val lastY = if (closerToTop && !atTop) {
-height.toFloat()
} else {
shortH
}
val onFirstItem = recyclerView.computeVerticalScrollOffset() < realHeight - shortH
return if (!onFirstItem) {
yAnimator = animate().y(lastY)
.setDuration(shortAnimationDuration.toLong())
yAnimator?.setUpdateListener {
updateAppBarAfterY(recyclerView, false)
callback?.invoke()
}
yAnimator?.start()
useSearchToolbarForMenu(true)
lastY
} else {
useSearchToolbarForMenu(mainToolbar?.alpha ?: 0f <= 0f)
y
}
}
fun useSearchToolbarForMenu(showCardTB: Boolean) {
val mainActivity = mainActivity ?: return
if (lockYPos) return
if ((showCardTB || toolbarMode == ToolbarState.SEARCH_ONLY) && cardFrame?.isVisible == true) {
if (mainActivity.currentToolbar != cardToolbar) {
mainActivity.setFloatingToolbar(true, showSearchAnyway = true)
} else {
mainActivity.setSearchTBMenuIfInvalid()
}
if (mainActivity.currentToolbar == cardToolbar) {
if (toolbarMode == ToolbarState.EXPANDED) {
mainToolbar?.isInvisible = true
}
mainToolbar?.backgroundColor = null
cardFrame?.backgroundColor = null
}
} else {
if (mainActivity.currentToolbar != mainToolbar) {
mainActivity.setFloatingToolbar(false, showSearchAnyway = true)
}
if (toolbarMode == ToolbarState.EXPANDED) {
mainToolbar?.isInvisible = false
}
if (tabsFrameLayout?.isVisible == false) {
cardFrame?.backgroundColor = mainActivity.getResourceColor(R.attr.colorSurface)
} else {
cardFrame?.backgroundColor = null
}
}
}
}
interface SmallToolbarInterface

View file

@ -3,9 +3,11 @@ package eu.kanade.tachiyomi.ui.base
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.MenuItem
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.widget.SearchView
import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
@ -25,6 +27,40 @@ class FloatingToolbar @JvmOverloads constructor(context: Context, attrs: Attribu
private val defStyleRes = com.google.android.material.R.style.Widget_Material3_Toolbar
private val subtitleTextAppearance: Int
val isSearchExpanded: Boolean
get() {
return searchItem?.isActionViewExpanded == true
}
var searchQueryHint: CharSequence?
get() {
val searchView = searchItem?.actionView as? SearchView ?: return null
return searchView.queryHint
}
set(value) {
setQueryHint(value)
}
fun setQueryHint(query: CharSequence?, collapseSearch: Boolean = true) {
val searchView = searchItem?.actionView as? SearchView ?: return
val oldV = searchView.queryHint
searchView.queryHint = query
if (oldV != query && collapseSearch) {
searchView.setQuery("", false)
searchItem?.collapseActionView()
}
}
val searchView: SearchView?
get() {
return searchItem?.actionView as? SearchView
}
val searchItem: MenuItem?
get() {
return menu.findItem(R.id.action_search)
}
init {
val a = context.obtainStyledAttributes(
attrs,
@ -52,6 +88,7 @@ class FloatingToolbar @JvmOverloads constructor(context: Context, attrs: Attribu
collapseIcon = context.contextCompatDrawable(R.drawable.ic_arrow_back_24dp)?.apply {
setTint(actionColorAlpha)
}
inflateMenu(R.menu.search)
}
override fun setSubtitle(resId: Int) {

View file

@ -32,6 +32,9 @@ class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: Attr
updateScrollListener()
}
val isFastScrolling: Boolean
get() = handle.isSelected
// Overriding to force a distance moved before scrolling
override fun onTouchEvent(event: MotionEvent): Boolean {
if (recyclerView.computeVerticalScrollRange() <= recyclerView.computeVerticalScrollExtent()) {

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.ui.base.controller
import android.app.Activity
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
@ -9,11 +10,15 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.forEach
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.isControllerVisible
import eu.kanade.tachiyomi.util.view.removeQueryListener
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
@ -75,13 +80,21 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
super.onChangeStarted(handler, type)
}
val onRoot: Boolean
get() = router.backstack.lastOrNull()?.controller == this
open fun getTitle(): String? {
return null
}
open fun getSearchTitle(): String? {
return null
}
open fun getBigIcon(): Drawable? {
return null
}
open val mainRecycler: RecyclerView?
get() = null
override fun onActivityPaused(activity: Activity) {
super.onActivityPaused(activity)
removeQueryListener()
@ -96,8 +109,16 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
parentController = parentController.parentController
}
if (router.backstack.lastOrNull()?.controller == this) {
(activity as? AppCompatActivity)?.supportActionBar?.title = getTitle()
if (isControllerVisible) {
(activity as? AppCompatActivity)?.title = getTitle()
(activity as? MainActivity)?.searchTitle = getSearchTitle()
val icon = getBigIcon()
activityBinding?.bigIcon?.isVisible = icon != null
if (icon != null) {
activityBinding?.bigIcon?.setImageDrawable(getBigIcon())
} else {
activityBinding?.bigIcon?.setImageDrawable(getBigIcon())
}
}
}
@ -133,6 +154,9 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
}
}
open fun onActionViewExpand(item: MenuItem?) { }
open fun onActionViewCollapse(item: MenuItem?) { }
fun hideItemsIfExpanded(searchItem: MenuItem?, menu: Menu?, isExpanded: Boolean = false) {
menu ?: return
searchItem ?: return

View file

@ -10,6 +10,7 @@ import com.google.android.material.snackbar.Snackbar
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.CategoriesControllerBinding
import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.category.CategoryPresenter.Companion.CREATE_CATEGORY_ORDER
import eu.kanade.tachiyomi.ui.main.MainActivity
@ -25,6 +26,7 @@ class CategoryController(bundle: Bundle? = null) :
BaseController<CategoriesControllerBinding>(bundle),
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemMoveListener,
SmallToolbarInterface,
CategoryAdapter.CategoryItemListener {
/**

View file

@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.databinding.SetCategoriesSheetBinding
import eu.kanade.tachiyomi.ui.category.ManageCategoryDialog
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
import eu.kanade.tachiyomi.util.view.checkHeightThen
@ -218,9 +219,7 @@ class SetCategoriesSheet(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val attrsArray = intArrayOf(R.attr.mainActionBarSize)
val array = context.obtainStyledAttributes(attrsArray)
val headerHeight = array.getDimensionPixelSize(0, 0)
val headerHeight = (activity as? MainActivity)?.toolbarHeight ?: 0
binding.buttonLayout.updatePaddingRelative(
bottom = activity.window.decorView.rootWindowInsetsCompat
?.getInsets(systemBars())?.bottom ?: 0
@ -229,7 +228,6 @@ class SetCategoriesSheet(
binding.buttonLayout.updateLayoutParams<ConstraintLayout.LayoutParams> {
height = headerHeight + binding.buttonLayout.paddingBottom
}
array.recycle()
binding.cancelButton.setOnClickListener { dismiss() }
binding.newCategoryButton.setOnClickListener {

View file

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.download
import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import android.view.Menu
import android.view.MenuItem
import android.widget.LinearLayout
import androidx.core.view.WindowInsetsCompat.Type.systemBars
@ -15,6 +14,7 @@ import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.databinding.DownloadBottomSheetBinding
import eu.kanade.tachiyomi.ui.extension.ExtensionDividerItemDecoration
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.recents.RecentsController
import eu.kanade.tachiyomi.util.view.collapse
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsetsCompat
@ -69,10 +69,7 @@ class DownloadBottomSheet @JvmOverloads constructor(
this.controller = controller
updateDLTitle()
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
val array = context.obtainStyledAttributes(attrsArray)
val headerHeight = array.getDimensionPixelSize(0, 0)
array.recycle()
val headerHeight = (activity as? MainActivity)?.toolbarHeight ?: 0
binding.recyclerLayout.doOnApplyWindowInsetsCompat { v, windowInsets, _ ->
v.updateLayoutParams<MarginLayoutParams> {
topMargin = windowInsets.getInsets(systemBars()).top +
@ -108,6 +105,7 @@ class DownloadBottomSheet @JvmOverloads constructor(
presenter.getItems()
onQueueStatusChange(!presenter.downloadManager.isPaused())
binding.downloadFab.isInvisible = presenter.downloadQueue.isEmpty()
prepareMenu()
}
private fun updateDLTitle() {
@ -130,7 +128,7 @@ class DownloadBottomSheet @JvmOverloads constructor(
binding.downloadFab.isInvisible = presenter.downloadQueue.isEmpty()
updateFab()
if (oldRunning != running) {
activity?.invalidateOptionsMenu()
prepareMenu()
// Check if download queue is empty and update information accordingly.
setInformationView()
@ -143,7 +141,7 @@ class DownloadBottomSheet @JvmOverloads constructor(
* @param downloads the downloads from the queue.
*/
fun onNextDownloads(downloads: List<DownloadItem>) {
activity?.invalidateOptionsMenu()
prepareMenu()
setInformationView()
adapter?.updateDataSet(downloads)
setBottomSheet()
@ -193,7 +191,8 @@ class DownloadBottomSheet @JvmOverloads constructor(
}
}
fun prepareMenu(menu: Menu) {
private fun prepareMenu() {
val menu = binding.sheetToolbar.menu
updateFab()
// Set clear button visibility.
menu.findItem(R.id.clear_queue)?.isVisible = !presenter.downloadQueue.isEmpty()

View file

@ -25,6 +25,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -72,7 +73,7 @@ class ExtensionBottomPresenter(
extensionManager.availableExtensions
)
)
withContext(Dispatchers.Main) { bottomSheet.setExtensions(extensions) }
withContext(Dispatchers.Main) { bottomSheet.setExtensions(extensions, false) }
}
val migrationJob = async {
val favs = db.getFavoriteMangas().executeOnIO()
@ -97,6 +98,7 @@ class ExtensionBottomPresenter(
}
presenterScope.launch {
extensionManager.downloadRelay
.drop(3)
.collect {
if (it.first.startsWith("Finished")) {
firstLoad = true
@ -151,7 +153,7 @@ class ExtensionBottomPresenter(
extensionManager.availableExtensions
)
)
withContext(Dispatchers.Main) { bottomSheet.setExtensions(extensions) }
withContext(Dispatchers.Main) { bottomSheet.setExtensions(extensions, false) }
}
}

View file

@ -42,6 +42,7 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.view.openInBrowser
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.widget.LinearLayoutManagerAccurateOffset
import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -109,8 +110,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
manager.setPreferences(screen)
binding.extensionPrefsRecycler.layoutManager =
androidx.recyclerview.widget.LinearLayoutManager(context)
binding.extensionPrefsRecycler.layoutManager = LinearLayoutManagerAccurateOffset(context)
val concatAdapterConfig = ConcatAdapter.Config.Builder()
.setStableIdMode(ConcatAdapter.Config.StableIdMode.ISOLATED_STABLE_IDS)
.build()

View file

@ -67,10 +67,10 @@ class LibraryCategoryAdapter(val controller: LibraryController) :
}
private fun setItemsPerCategoryMap() {
itemsPerCategory = headerItems.map { header ->
itemsPerCategory = headerItems.associate { header ->
(header as LibraryHeaderItem).catId to
controller.presenter.getItemCountInCategories(header.catId)
}.toMap()
}
}
/**

View file

@ -6,7 +6,6 @@ import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.os.Build
import android.os.Bundle
@ -21,10 +20,8 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewPropertyAnimator
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.GestureDetectorCompat
import androidx.core.view.ViewCompat
@ -34,7 +31,9 @@ import androidx.core.view.WindowInsetsCompat.Type.ime
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.marginTop
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.core.view.updatePaddingRelative
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
@ -94,10 +93,13 @@ import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.collapse
import eu.kanade.tachiyomi.util.view.expand
import eu.kanade.tachiyomi.util.view.fullAppBarHeight
import eu.kanade.tachiyomi.util.view.getItemView
import eu.kanade.tachiyomi.util.view.hide
import eu.kanade.tachiyomi.util.view.isControllerVisible
import eu.kanade.tachiyomi.util.view.isExpanded
import eu.kanade.tachiyomi.util.view.isHidden
import eu.kanade.tachiyomi.util.view.moveRecyclerViewUp
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.setStyle
@ -222,7 +224,15 @@ class LibraryController(
) + 55f.dpToPx
}
override val mainRecycler: RecyclerView
get() = binding.libraryGridRecycler.recycler
override fun getTitle(): String? {
setSubtitle()
return view?.context?.getString(R.string.library)
}
override fun getSearchTitle(): String? {
setSubtitle()
return searchTitle(
if (preferences.showLibrarySearchSuggestions().get() &&
@ -270,6 +280,9 @@ class LibraryController(
if (!preferences.hideBottomNavOnScroll().get() || activityBinding?.bottomNav == null) {
updateFilterSheetY()
}
if (!binding.fastScroller.isFastScrolling) {
updateSmallerViewsTopMargins()
}
binding.roundedCategoryHopper.upCategory.alpha = if (isAtTop()) 0.25f else 1f
binding.roundedCategoryHopper.downCategory.alpha = if (isAtBottom()) 0.25f else 1f
}
@ -390,7 +403,7 @@ class LibraryController(
}
private fun setSubtitle() {
if (!singleCategory && presenter.showAllCategories &&
if (isBindingInitialized && !singleCategory && presenter.showAllCategories &&
!binding.headerTitle.text.isNullOrBlank() && !binding.recyclerCover.isClickable
) {
activityBinding?.cardToolbar?.subtitle = binding.headerTitle.text.toString()
@ -551,11 +564,9 @@ class LibraryController(
swipeRefreshLayout = binding.swipeRefresh,
afterInsets = { insets ->
binding.categoryRecycler.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = binding.libraryGridRecycler.recycler.paddingTop
}
binding.fastScroller.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = binding.libraryGridRecycler.recycler.paddingTop
topMargin = insets.getInsets(systemBars()).top + (activityBinding?.cardToolbar?.height ?: 0) + 12.dpToPx
}
updateSmallerViewsTopMargins()
binding.headerCard.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = insets.getInsets(systemBars()).top + 4.dpToPx
}
@ -583,11 +594,42 @@ class LibraryController(
if (presenter.libraryItems.isNotEmpty()) {
presenter.restoreLibrary()
if (justStarted) {
val activityBinding = activityBinding ?: return
val bigToolbarHeight = fullAppBarHeight ?: return
if (lastUsedCategory > 0) {
activityBinding.appBar.y =
-bigToolbarHeight + activityBinding.cardFrame.height.toFloat()
activityBinding.appBar.useSearchToolbarForMenu(true)
}
activityBinding.appBar.lockYPos = true
}
} else {
binding.recyclerLayout.alpha = 0f
}
}
private fun updateSmallerViewsTopMargins() {
val activityBinding = activityBinding ?: return
val bigToolbarHeight = fullAppBarHeight ?: return
val value = max(
0,
bigToolbarHeight + activityBinding.appBar.y.roundToInt()
) + activityBinding.appBar.paddingTop
if (value != binding.fastScroller.marginTop) {
binding.fastScroller.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = value
}
binding.emptyView.updatePadding(
top = bigToolbarHeight + activityBinding.appBar.paddingTop,
bottom = binding.libraryGridRecycler.recycler.paddingBottom
)
binding.progress.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = (bigToolbarHeight + activityBinding.appBar.paddingTop) / 2
}
}
}
private fun setSwipeRefresh() = with(binding.swipeRefresh) {
setOnRefreshListener {
isRefreshing = false
@ -598,48 +640,7 @@ class LibraryController(
updateLibrary(it)
}
}
presenter.allCategories.size <= 1 || presenter.groupType > BY_DEFAULT -> {
updateLibrary()
}
preferences.updateOnRefresh().get() == -1 -> {
var selected = 0
activity!!.materialAlertDialog()
.setTitle(R.string.what_should_update)
.setNegativeButton(android.R.string.cancel, null)
.setSingleChoiceItems(
arrayOf(
context.getString(
R.string.top_category,
presenter.allCategories.first().name
),
context.getString(
R.string.categories_in_global_update
)
),
-1
) { dialog, index ->
selected = index
(dialog as? AlertDialog)?.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled = true
}
.setPositiveButton(
R.string.update
) { _, _ ->
preferences.updateOnRefresh().set(selected)
when (selected) {
0 -> updateLibrary(presenter.allCategories.first())
else -> updateLibrary()
}
}
.show().apply {
getButton(DialogInterface.BUTTON_POSITIVE).isEnabled = false
}
}
else -> {
when (preferences.updateOnRefresh().get()) {
0 -> updateLibrary(presenter.allCategories.first())
else -> updateLibrary()
}
}
else -> updateLibrary()
}
}
}
@ -796,7 +797,7 @@ class LibraryController(
if (!next) {
val fPosition =
(binding.libraryGridRecycler.recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
if (fPosition != adapter.currentItems.indexOf(category)) {
if (fPosition > adapter.currentItems.indexOf(category)) {
scrollToHeader(category.category.order)
return
}
@ -955,12 +956,11 @@ class LibraryController(
activityBinding?.cardToolbar?.setOnLongClickListener {
val suggestion = preferences.librarySearchSuggestion().get()
if (suggestion.isNotBlank()) {
val searchItem =
activityBinding?.cardToolbar?.menu?.findItem(R.id.action_search)
val searchView = searchItem?.actionView as? SearchView
val searchItem = activityBinding?.cardToolbar?.searchItem
val searchView = activityBinding?.cardToolbar?.searchView
?: return@setOnLongClickListener false
searchItem.expandActionView()
searchView.setQuery(suggestion, false)
searchItem?.expandActionView()
searchView.setQuery(suggestion.removeSuffix(""), false)
true
} else {
false
@ -993,6 +993,7 @@ class LibraryController(
override fun onDestroyView(view: View) {
LibraryUpdateService.removeListener(this)
activityBinding?.appBar?.lockYPos = false
destroyActionModeIfNeeded()
if (isBindingInitialized) {
binding.libraryGridRecycler.recycler.removeOnScrollListener(scrollListener)
@ -1031,8 +1032,19 @@ class LibraryController(
binding.recyclerLayout.animate().alpha(1f).setDuration(500).start()
}
if (justStarted && freshStart) {
val activeC = activeCategory
scrollToHeader(activeCategory)
binding.libraryGridRecycler.recycler.post {
if (isControllerVisible) {
activityBinding?.appBar?.y = 0f
activityBinding?.appBar?.updateAppBarAfterY(binding.libraryGridRecycler.recycler)
if (activeC > 0) {
activityBinding?.appBar?.useSearchToolbarForMenu(true)
}
}
}
}
activityBinding?.appBar?.lockYPos = false
binding.libraryGridRecycler.recycler.post {
elevateAppBar(binding.libraryGridRecycler.recycler.canScrollVertically(-1))
setActiveCategory()
@ -1056,18 +1068,16 @@ class LibraryController(
binding.libraryGridRecycler.recycler.scrollToPosition(0)
shouldScrollToTop = false
}
if (onRoot) {
listOf(activityBinding?.toolbar, binding.headerTitle).forEach {
it?.setOnClickListener {
val recycler = binding.libraryGridRecycler.recycler
if (!singleCategory) {
showCategories(recycler.translationY == 0f)
}
}
if (!hasMovedHopper && isAnimatingHopper == null) {
showSlideAnimation()
if (isControllerVisible) {
binding.headerTitle.setOnClickListener {
val recycler = binding.libraryGridRecycler.recycler
if (!singleCategory) {
showCategories(recycler.translationY == 0f)
}
}
if (!hasMovedHopper && isAnimatingHopper == null) {
showSlideAnimation()
}
setSubtitle()
showMiniBar()
}
@ -1106,13 +1116,12 @@ class LibraryController(
binding.recyclerCover.isClickable = show
binding.recyclerCover.isFocusable = show
if (closeSearch) {
activityBinding?.cardToolbar?.menu?.findItem(R.id.action_search)?.collapseActionView()
activityBinding?.cardToolbar?.searchItem?.collapseActionView()
}
val full = binding.categoryRecycler.height.toFloat() + binding.libraryGridRecycler.recycler.paddingTop
val full = binding.categoryRecycler.height.toFloat() + binding.categoryRecycler.marginTop
val translateY = if (show) full else 0f
binding.libraryGridRecycler.recycler.animate().translationY(translateY).apply {
setUpdateListener {
activityBinding?.appBar?.y = 0f
updateHopperY()
}
}.start()
@ -1127,7 +1136,6 @@ class LibraryController(
binding.categoryRecycler.scrollToCategory(activeCategory)
}
binding.fastScroller.hideScrollbar()
activityBinding?.appBar?.y = 0f
elevateAppBar(false)
binding.filterBottomSheet.filterBottomSheet.sheetBehavior?.hide()
} else {
@ -1146,13 +1154,9 @@ class LibraryController(
}
val headerPosition = adapter.indexOf(pos)
if (headerPosition > -1) {
val appbar = activityBinding?.appBar
val activityBinding = activityBinding ?: return
binding.libraryGridRecycler.recycler.suppressLayout(true)
val appbarOffset = if (appbar?.y ?: 0f > -20) 0 else (
appbar?.y?.plus(
view?.rootWindowInsetsCompat?.getInsets(systemBars())?.top ?: 0
) ?: 0f
).roundToInt() + 30.dpToPx
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(
headerPosition,
@ -1170,11 +1174,14 @@ class LibraryController(
activeCategory = pos
preferences.lastUsedCategory().set(pos)
binding.libraryGridRecycler.recycler.suppressLayout(false)
binding.libraryGridRecycler.recycler.post {
activityBinding.appBar.y = 0f
activityBinding.appBar.updateAppBarAfterY(binding.libraryGridRecycler.recycler)
}
}
}
private fun onRefresh() {
activity?.invalidateOptionsMenu()
showCategories(false)
presenter.getLibrary()
destroyActionModeIfNeeded()
@ -1184,7 +1191,6 @@ class LibraryController(
* Called when a filter is changed.
*/
private fun onFilterChanged() {
activity?.invalidateOptionsMenu()
presenter.requestFilterUpdate()
destroyActionModeIfNeeded()
}
@ -1222,6 +1228,7 @@ class LibraryController(
viewScope.launchUI {
adapter.performFilterAsync()
}
moveRecyclerViewUp()
return true
}
@ -1610,8 +1617,6 @@ class LibraryController(
}
}
override fun sheetIsFullscreen(): Boolean = false
override fun handleSheetBack(): Boolean {
if (binding.recyclerCover.isClickable) {
showCategories(false)
@ -1629,41 +1634,47 @@ class LibraryController(
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.library, menu)
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchView.queryHint = resources?.getString(R.string.library_search_hint)
val searchItem = activityBinding?.cardToolbar?.searchItem
val searchView = activityBinding?.cardToolbar?.searchView
activityBinding?.cardToolbar?.setQueryHint(resources?.getString(R.string.library_search_hint), query.isEmpty())
searchItem.collapseActionView()
if (query.isNotEmpty()) {
searchItem.expandActionView()
searchView.setQuery(query, true)
searchView.clearFocus()
if (activityBinding?.cardToolbar?.isSearchExpanded != true) {
searchItem?.expandActionView()
searchView?.setQuery(query, true)
searchView?.clearFocus()
} else {
searchView?.setQuery(query, false)
}
search(query)
} else if (activityBinding?.cardToolbar?.isSearchExpanded == true) {
searchItem?.collapseActionView()
}
setOnQueryTextChangeListener(searchView) {
setOnQueryTextChangeListener(activityBinding?.cardToolbar?.searchView) {
if (!it.isNullOrEmpty() && binding.recyclerCover.isClickable) {
showCategories(false)
}
search(it)
}
searchItem.fixExpand(
onExpand = {
if (!binding.recyclerCover.isClickable && query.isBlank() &&
!singleCategory && presenter.showAllCategories
) {
showCategories(true)
}
invalidateMenuOnExpand()
},
onCollapse = {
if (binding.recyclerCover.isClickable) {
showCategories(false)
}
true
}
override fun onActionViewExpand(item: MenuItem?) {
if (!binding.recyclerCover.isClickable && query.isBlank() &&
!singleCategory && presenter.showAllCategories
) {
showCategories(true)
binding.libraryGridRecycler.recycler.post {
activityBinding?.appBar?.y = (activityBinding?.appBar?.yNeededForSmallToolbar ?: 0).toFloat()
activityBinding?.appBar?.updateAppBarAfterY(binding.libraryGridRecycler.recycler)
}
)
hideItemsIfExpanded(searchItem, menu)
}
}
override fun onActionViewCollapse(item: MenuItem?) {
if (binding.recyclerCover.isClickable) {
showCategories(false)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {

View file

@ -1160,10 +1160,10 @@ class LibraryPresenter(
sourceManager.get(
distinctSources.randomOrNull(random)?.source ?: 0L
)?.name
randomSource?.chopByWords(15)
randomSource?.chopByWords(30)
}
randomTitle -> {
libraryManga.randomOrNull(random)?.title?.chopByWords(15)
libraryManga.randomOrNull(random)?.title?.chopByWords(30)
}
in randomTags -> {
val tags = recentManga.map {

View file

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.main
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Dialog
import android.app.assist.AssistContent
import android.content.Context
@ -21,6 +22,9 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.annotation.IdRes
import androidx.appcompat.view.menu.ActionMenuItemView
import androidx.appcompat.view.menu.MenuItemImpl
import androidx.appcompat.widget.ActionMenuView
import androidx.appcompat.widget.Toolbar
import androidx.core.graphics.ColorUtils
import androidx.core.net.toUri
@ -29,6 +33,8 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.children
import androidx.core.view.forEach
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
@ -60,6 +66,7 @@ import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.MaterialMenuSheet
import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -75,6 +82,7 @@ import eu.kanade.tachiyomi.ui.source.BrowseController
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.util.manga.MangaShortcutManager
import eu.kanade.tachiyomi.util.system.contextCompatDrawable
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.hasSideNavBar
import eu.kanade.tachiyomi.util.system.isBottomTappable
@ -87,7 +95,9 @@ import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.backgroundColor
import eu.kanade.tachiyomi.util.view.blurBehindWindow
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsetsCompat
import eu.kanade.tachiyomi.util.view.findChild
import eu.kanade.tachiyomi.util.view.getItemView
import eu.kanade.tachiyomi.util.view.moveRecyclerViewUp
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.withFadeInTransaction
import eu.kanade.tachiyomi.util.view.withFadeTransaction
@ -108,9 +118,8 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
protected lateinit var router: Router
private val searchDrawable by lazy { contextCompatDrawable(R.drawable.ic_search_24dp) }
protected val searchDrawable by lazy { contextCompatDrawable(R.drawable.ic_search_24dp) }
protected val backDrawable by lazy { contextCompatDrawable(R.drawable.ic_arrow_back_24dp) }
private val dismissDrawable by lazy { contextCompatDrawable(R.drawable.ic_close_24dp) }
private var gestureDetector: GestureDetectorCompat? = null
private var snackBar: Snackbar? = null
@ -131,6 +140,15 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
var currentToolbar: Toolbar? = null
var ogWidth: Int = Int.MAX_VALUE
private val actionButtonSize: Pair<Int, Int> by lazy {
val attrs = intArrayOf(android.R.attr.minWidth, android.R.attr.minHeight)
val ta = obtainStyledAttributes(androidx.appcompat.R.style.Widget_AppCompat_ActionButton, attrs)
val dimenW = ta.getDimensionPixelSize(0, 0.dpToPx)
val dimenH = ta.getDimensionPixelSize(1, 0.dpToPx)
ta.recycle()
dimenW to dimenH
}
fun setUndoSnackBar(snackBar: Snackbar?, extraViewToCheck: View? = null) {
this.snackBar = snackBar
canDismissSnackBar = false
@ -151,6 +169,14 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
val toolbarHeight: Int
get() = max(binding.toolbar.height, binding.cardFrame.height)
fun bigToolbarHeight(includeSearchToolbar: Boolean, includeTabs: Boolean, includeLargeToolbar: Boolean): Int {
return if (!includeLargeToolbar || !binding.appBar.useLargeToolbar) {
toolbarHeight + if (includeTabs) 48.dpToPx else 0
} else {
binding.appBar.getEstimatedLayout(includeSearchToolbar, includeTabs, includeLargeToolbar)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -205,9 +231,11 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
val content: ViewGroup = binding.mainContent
DownloadService.addListener(this)
WindowCompat.setDecorFitsSystemWindows(window, false)
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayShowCustomEnabled(true)
setNavBarColor(content.rootWindowInsetsCompat)
binding.appBar.mainActivity = this
nav.isVisible = false
content.doOnApplyWindowInsetsCompat { v, insets, _ ->
setNavBarColor(insets)
@ -302,23 +330,52 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
}
binding.toolbar.setNavigationOnClickListener {
val rootSearchController = router.backstack.lastOrNull()?.controller
if (rootSearchController is RootSearchInterface) {
rootSearchController.expandSearch()
} else onBackPressed()
onBackPressed()
}
binding.cardToolbar.setNavigationOnClickListener {
val rootSearchController = router.backstack.lastOrNull()?.controller
if (rootSearchController is RootSearchInterface) {
rootSearchController.expandSearch()
if ((
rootSearchController is RootSearchInterface ||
(currentToolbar != binding.cardToolbar && binding.appBar.useLargeToolbar)
) &&
rootSearchController !is SmallToolbarInterface
) {
binding.cardToolbar.menu.findItem(R.id.action_search)?.expandActionView()
} else onBackPressed()
}
binding.cardToolbar.searchItem?.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
val controller = router.backstack.lastOrNull()?.controller
controller?.moveRecyclerViewUp()
(controller as? BaseController<*>)?.onActionViewExpand(item)
(controller as? SettingsController)?.onActionViewExpand(item)
binding.cardToolbar.menu.forEach { it.isVisible = false }
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
val controller = router.backstack.lastOrNull()?.controller
setupSearchTBMenu(binding.toolbar.menu, true)
(controller as? BaseController<*>)?.onActionViewCollapse(item)
(controller as? SettingsController)?.onActionViewCollapse(item)
return true
}
})
binding.appBar.alpha = 1f
binding.cardToolbar.setOnClickListener {
binding.cardToolbar.menu.findItem(R.id.action_search)?.expandActionView()
}
binding.cardToolbar.setOnMenuItemClickListener {
if (router.backstack.lastOrNull()?.controller?.onOptionsItemSelected(it) == true) {
return@setOnMenuItemClickListener true
} else return@setOnMenuItemClickListener onOptionsItemSelected(it)
}
nav.isVisible = !hideBottomNav
binding.bottomView?.visibility = if (hideBottomNav) View.GONE else binding.bottomView?.visibility ?: View.GONE
nav.alpha = if (hideBottomNav) 0f else 1f
@ -332,7 +389,8 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
handler: ControllerChangeHandler
) {
syncActivityViewWithController(to, from, isPush)
binding.appBar.y = 0f
binding.appBar.isVisible = true
binding.appBar.alpha = 1f
if (!isPush || router.backstackSize == 1) {
nav.translationY = 0f
}
@ -346,7 +404,6 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
container: ViewGroup,
handler: ControllerChangeHandler
) {
binding.appBar.y = 0f
nav.translationY = 0f
showDLQueueTutorial()
if (router.backstackSize == 1) {
@ -363,7 +420,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
syncActivityViewWithController(router.backstack.lastOrNull()?.controller)
val navIcon = if (router.backstackSize > 1) backDrawable else searchDrawable
val navIcon = if (router.backstackSize > 1) backDrawable else null
binding.toolbar.navigationIcon = navIcon
(router.backstack.lastOrNull()?.controller as? BaseController<*>)?.setTitle()
(router.backstack.lastOrNull()?.controller as? SettingsController)?.setTitle()
@ -403,38 +460,57 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
setFloatingToolbar(canShowFloatingToolbar(router.backstack.lastOrNull()?.controller), changeBG = false)
}
open fun setFloatingToolbar(show: Boolean, solidBG: Boolean = false, changeBG: Boolean = true) {
val oldTB = currentToolbar
currentToolbar = if (show) {
override fun onTitleChanged(title: CharSequence?, color: Int) {
super.onTitleChanged(title, color)
binding.cardToolbar.title = searchTitle
val onExpandedController = if (this::router.isInitialized) router.backstack.lastOrNull()?.controller !is SmallToolbarInterface else false
binding.appBar.setTitle(title, onExpandedController)
}
var searchTitle: String?
get() {
return try {
(router.backstack.lastOrNull()?.controller as? BaseController<*>)?.getSearchTitle()
?: (router.backstack.lastOrNull()?.controller as? SettingsController)?.getSearchTitle()
} catch (_: Exception) {
binding.cardToolbar.title?.toString()
}
}
set(title) {
binding.cardToolbar.title = title
}
open fun setFloatingToolbar(show: Boolean, solidBG: Boolean = false, changeBG: Boolean = true, showSearchAnyway: Boolean = false) {
val controller = if (this::router.isInitialized) router.backstack.lastOrNull()?.controller else null
val useLargeTB = binding.appBar.useLargeToolbar
val onSearchController = canShowFloatingToolbar(controller)
val onSmallerController = controller is SmallToolbarInterface || !useLargeTB
currentToolbar = if (show && ((showSearchAnyway && onSearchController) || onSmallerController)) {
binding.cardToolbar
} else {
binding.toolbar
}
if (oldTB != currentToolbar) {
setSupportActionBar(currentToolbar)
}
binding.toolbar.isVisible = !show
binding.cardFrame.isVisible = show
binding.toolbar.isVisible = !(onSmallerController && onSearchController)
binding.cardFrame.isVisible = (show || showSearchAnyway) && onSearchController
val bgColor = binding.appBar.backgroundColor ?: Color.TRANSPARENT
if (changeBG && (if (solidBG) bgColor == Color.TRANSPARENT else false)) {
binding.appBar.setBackgroundColor(
if (show && !solidBG) Color.TRANSPARENT else getResourceColor(R.attr.colorSurface)
)
}
currentToolbar?.setNavigationOnClickListener {
val rootSearchController = router.backstack.lastOrNull()?.controller
if (rootSearchController is RootSearchInterface) {
rootSearchController.expandSearch()
} else onBackPressed()
setupSearchTBMenu(binding.toolbar.menu)
if (currentToolbar != binding.cardToolbar) {
binding.cardToolbar.menu?.children?.toList()?.forEach {
it.isVisible = false
}
}
if (oldTB != currentToolbar) {
invalidateOptionsMenu()
val onRoot = !this::router.isInitialized || router.backstackSize == 1
if (!useLargeTB) {
binding.cardToolbar.navigationIcon = if (onRoot) searchDrawable else backDrawable
} else if (showSearchAnyway) {
binding.cardToolbar.navigationIcon = if (!show || onRoot) searchDrawable else backDrawable
}
}
fun setDismissIcon(enabled: Boolean) {
binding.cardToolbar.navigationIcon = if (enabled) dismissDrawable else searchDrawable
binding.toolbar.navigationIcon = if (enabled) dismissDrawable else searchDrawable
binding.cardToolbar.title = searchTitle
}
private fun setNavBarColor(insets: WindowInsetsCompat?) {
@ -494,6 +570,15 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
super.onSupportActionModeFinished(mode)
}
fun setStatusBarColorTransparent(show: Boolean) {
window?.statusBarColor = if (show) {
ColorUtils.setAlphaComponent(window?.statusBarColor ?: Color.TRANSPARENT, 0)
} else {
val color = getResourceColor(android.R.attr.statusBarColor)
ColorUtils.setAlphaComponent(window?.statusBarColor ?: color, Color.alpha(color))
}
}
private fun setExtensionsBadge() {
val updates = preferences.extensionUpdatesCount().get()
if (updates > 0) {
@ -695,12 +780,21 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
overflowDialog = null
DownloadService.removeListener(this)
if (isBindingInitialized) {
binding.appBar.mainActivity = null
binding.toolbar.setNavigationOnClickListener(null)
binding.cardToolbar.setNavigationOnClickListener(null)
}
}
override fun onBackPressed() {
if (binding.cardToolbar.isSearchExpanded && binding.cardFrame.isVisible) {
binding.cardToolbar.searchItem?.collapseActionView()
return
}
backPress()
}
open fun backPress() {
val sheetController = router.backstack.lastOrNull()?.controller as? BottomSheetController
if (if (router.backstackSize == 1) !(sheetController?.handleSheetBack() ?: false)
else !router.handleBack()
@ -759,12 +853,118 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
router.setRoot(controller.withFadeInTransaction().tag(id.toString()))
}
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
val searchItem = menu?.findItem(R.id.action_search)
if (currentToolbar == binding.cardToolbar) {
override fun onPreparePanel(featureId: Int, view: View?, menu: Menu): Boolean {
val prepare = super.onPreparePanel(featureId, view, menu)
if (canShowFloatingToolbar(router.backstack.lastOrNull()?.controller)) {
val searchItem = menu.findItem(R.id.action_search)
searchItem?.isVisible = false
}
return super.onPrepareOptionsMenu(menu)
setupSearchTBMenu(menu)
return prepare
}
fun setSearchTBMenuIfInvalid() = setupSearchTBMenu(binding.toolbar.menu)
private fun setupSearchTBMenu(menu: Menu?, showAnyway: Boolean = false) {
val toolbar = binding.cardToolbar
val currentItemsId = toolbar.menu.children.toList().map { it.itemId }
val newMenuIds = menu?.children?.toList()?.map { it.itemId }.orEmpty()
menu?.children?.toList()?.let { menuItems ->
val searchActive = toolbar.isSearchExpanded
menuItems.forEachIndexed { index, oldMenuItem ->
if (oldMenuItem.itemId == R.id.action_search) return@forEachIndexed
val isVisible = oldMenuItem.isVisible &&
(currentToolbar == toolbar || !binding.appBar.useLargeToolbar) && (!searchActive || showAnyway)
addOrUpdateMenuItem(oldMenuItem, toolbar.menu, isVisible, currentItemsId, index)
}
}
toolbar.menu.children.toList().forEach {
if (it.itemId != R.id.action_search && !newMenuIds.contains(it.itemId)) {
toolbar.menu.removeItem(it.itemId)
}
}
// Done because sometimes ActionMenuItemViews have a width/height of 0 and never update
val actionMenuView = toolbar.findChild<ActionMenuView>()
if (binding.appBar.isVisible && toolbar.isVisible &&
toolbar.width > 0 && actionMenuView?.children?.any { it.width == 0 } == true
) {
actionMenuView.children.forEach {
if (it !is ActionMenuItemView) return@forEach
it.updateLayoutParams<ViewGroup.LayoutParams> {
width = actionButtonSize.first
height = actionButtonSize.second
}
}
actionMenuView.requestLayout()
}
val controller = if (this::router.isInitialized) router.backstack.lastOrNull()?.controller else null
if (canShowFloatingToolbar(controller)) {
binding.toolbar.menu.removeItem(R.id.action_search)
}
}
private fun addOrUpdateMenuItem(oldMenuItem: MenuItem, menu: Menu, isVisible: Boolean, currentItemsId: List<Int>, index: Int) {
if (currentItemsId.contains(oldMenuItem.itemId)) {
val newItem = menu.findItem(oldMenuItem.itemId) ?: return
if (newItem.icon != oldMenuItem.icon) {
newItem.icon = oldMenuItem.icon
}
if (newItem.isVisible != isVisible) {
newItem.isVisible = isVisible
}
updateSubMenu(oldMenuItem, newItem)
return
}
val menuItem = if (oldMenuItem.hasSubMenu()) {
menu.addSubMenu(
oldMenuItem.groupId,
oldMenuItem.itemId,
index,
oldMenuItem.title
).item
} else {
menu.add(
oldMenuItem.groupId,
oldMenuItem.itemId,
index,
oldMenuItem.title
)
}
menuItem.isVisible = isVisible
menuItem.actionView = oldMenuItem.actionView
menuItem.icon = oldMenuItem.icon
menuItem.isChecked = oldMenuItem.isChecked
updateSubMenu(oldMenuItem, menuItem)
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
@SuppressLint("RestrictedApi")
private fun updateSubMenu(oldMenuItem: MenuItem, menuItem: MenuItem) {
if (oldMenuItem.hasSubMenu()) {
val oldSubMenu = oldMenuItem.subMenu
val newMenuIds = oldSubMenu.children.toList().map { it.itemId }
val currentItemsId = menuItem.subMenu.children.toList().map { it.itemId }
var isExclusiveCheckable = false
var isCheckable = false
oldSubMenu.children.toList().forEachIndexed { index, oldSubMenuItem ->
val isSubVisible = oldSubMenuItem.isVisible
addOrUpdateMenuItem(oldSubMenuItem, menuItem.subMenu, isSubVisible, currentItemsId, index)
if (!isExclusiveCheckable) {
isExclusiveCheckable = (oldSubMenuItem as? MenuItemImpl)?.isExclusiveCheckable ?: false
}
if (!isCheckable) {
isCheckable = oldSubMenuItem.isCheckable
}
}
menuItem.subMenu.setGroupCheckable(oldSubMenu.children.first().groupId, isCheckable, isExclusiveCheckable)
menuItem.subMenu.children.toList().forEach {
if (!newMenuIds.contains(it.itemId)) {
menuItem.subMenu.removeItem(it.itemId)
}
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -836,8 +1036,8 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
setFloatingToolbar(canShowFloatingToolbar(to))
val onRoot = router.backstackSize == 1
val navIcon = if (onRoot) searchDrawable else backDrawable
binding.toolbar.navigationIcon = navIcon
binding.cardToolbar.navigationIcon = navIcon
binding.toolbar.navigationIcon = if (onRoot) null else backDrawable
binding.cardToolbar.navigationIcon = if (binding.appBar.useLargeToolbar) searchDrawable else navIcon
binding.cardToolbar.subtitle = null
nav.visibility = if (!hideBottomNav) View.VISIBLE else nav.visibility
@ -1023,6 +1223,8 @@ interface RootSearchInterface {
}
}
interface TabbedInterface
interface FloatingSearchInterface {
fun searchTitle(title: String?): String? {
if (this is Controller) {
@ -1039,5 +1241,4 @@ interface BottomSheetController {
fun hideSheet()
fun toggleSheet()
fun handleSheetBack(): Boolean
fun sheetIsFullscreen(): Boolean
}

View file

@ -9,8 +9,10 @@ import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
@ -38,9 +40,17 @@ class SearchActivity : MainActivity() {
}
override fun onBackPressed() {
if (binding.cardToolbar.isSearchExpanded && binding.cardFrame.isVisible) {
binding.cardToolbar.searchItem?.collapseActionView()
return
}
backPress()
}
override fun backPress() {
if (router.backstack.size <= 1 || !router.handleBack()) {
SecureActivityDelegate.locked = true
super.onBackPressed()
super.backPress()
}
}
@ -56,9 +66,19 @@ class SearchActivity : MainActivity() {
}
}
override fun setFloatingToolbar(show: Boolean, solidBG: Boolean, changeBG: Boolean) {
super.setFloatingToolbar(show, solidBG, changeBG)
currentToolbar?.setNavigationOnClickListener { popToRoot() }
override fun setFloatingToolbar(show: Boolean, solidBG: Boolean, changeBG: Boolean, showSearchAnyway: Boolean) {
super.setFloatingToolbar(show, solidBG, changeBG, showSearchAnyway)
binding.toolbar.setNavigationOnClickListener { popToRoot() }
binding.cardToolbar.setNavigationOnClickListener {
val rootSearchController = router.backstack.lastOrNull()?.controller
if ((
rootSearchController is RootSearchInterface ||
(currentToolbar != binding.cardToolbar && binding.appBar.useLargeToolbar)
) && rootSearchController !is SmallToolbarInterface
) {
binding.cardToolbar.menu.findItem(R.id.action_search)?.expandActionView()
} else popToRoot()
}
}
private fun intentShouldGoBack() =
@ -74,7 +94,7 @@ class SearchActivity : MainActivity() {
return
}
setFloatingToolbar(canShowFloatingToolbar(to))
binding.cardToolbar.navigationIcon = backDrawable
binding.cardToolbar.navigationIcon = if (binding.appBar.useLargeToolbar) searchDrawable else backDrawable
binding.toolbar.navigationIcon = backDrawable
nav.isVisible = false

View file

@ -58,6 +58,7 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.MaterialMenuSheet
import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
@ -97,15 +98,16 @@ import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
import eu.kanade.tachiyomi.util.system.setCustomTitleAndMessage
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsetsCompat
import eu.kanade.tachiyomi.util.view.getText
import eu.kanade.tachiyomi.util.view.requestFilePermissionsSafe
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.setStyle
import eu.kanade.tachiyomi.util.view.setTextColorAlpha
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.toolbarHeight
import eu.kanade.tachiyomi.util.view.withFadeTransaction
import eu.kanade.tachiyomi.widget.LinearLayoutManagerAccurateOffset
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -121,6 +123,7 @@ class MangaDetailsController :
FlexibleAdapter.OnItemLongClickListener,
ActionMode.Callback,
MangaDetailsAdapter.MangaDetailsInterface,
SmallToolbarInterface,
FlexibleAdapter.OnItemMoveListener {
constructor(
@ -353,7 +356,6 @@ class MangaDetailsController :
presenter.onDestroy()
adapter = null
trackingBottomSheet = null
updateToolbarTitleAlpha(1f)
super.onDestroyView(view)
}
@ -363,46 +365,39 @@ class MangaDetailsController :
binding.recycler.adapter = adapter
adapter?.isSwipeEnabled = true
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.layoutManager = LinearLayoutManagerAccurateOffset(view.context)
binding.recycler.addItemDecoration(
MangaDetailsDivider(view.context)
)
binding.recycler.setHasFixedSize(true)
val attrsArray = intArrayOf(R.attr.mainActionBarSize)
val array = view.context.obtainStyledAttributes(attrsArray)
val appbarHeight = array.getDimensionPixelSize(0, 0)
array.recycle()
val appbarHeight = activityBinding?.appBar?.attrToolbarHeight ?: 0
val offset = 10.dpToPx
binding.swipeRefresh.setDistanceToTriggerSync(70.dpToPx)
if (isTablet) {
val tHeight = toolbarHeight.takeIf { it ?: 0 > 0 } ?: appbarHeight
headerHeight = tHeight +
(view.rootWindowInsetsCompat?.getInsets(systemBars())?.top ?: 0)
val insetsCompat = view.rootWindowInsetsCompat ?: activityBinding?.root?.rootWindowInsetsCompat
headerHeight = tHeight + (insetsCompat?.getInsets(systemBars())?.top ?: 0)
binding.recycler.updatePaddingRelative(top = headerHeight + 4.dpToPx)
binding.recycler.doOnApplyWindowInsetsCompat { _, insets, _ ->
setInsets(insets, appbarHeight, offset)
}
} else {
scrollViewWith(
binding.recycler,
padBottom = true,
customPadding = true,
afterInsets = { insets ->
setInsets(insets, appbarHeight, offset)
},
liftOnScroll = {
colorToolbar(it)
}
)
}
scrollViewWith(
binding.recycler,
padBottom = true,
customPadding = true,
swipeRefreshLayout = binding.swipeRefresh,
afterInsets = { insets ->
setInsets(insets, appbarHeight, offset)
},
liftOnScroll = {
colorToolbar(it)
}
)
binding.recycler.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (!isTablet) {
updateToolbarTitleAlpha(isScrollingDown = dy > 0)
updateToolbarTitleAlpha(isScrollingDown = dy > 0 && !binding.root.context.isTablet())
val atTop = !recyclerView.canScrollVertically(-1)
val tY = getHeader()?.binding?.backdrop?.translationY ?: 0f
getHeader()?.binding?.backdrop?.translationY = max(0f, tY + dy * 0.25f)
@ -562,6 +557,8 @@ class MangaDetailsController :
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (type.isEnter) {
activityBinding?.appBar?.y = 0f
activityBinding?.appBar?.updateAppBarAfterY(binding.recycler)
updateToolbarTitleAlpha(0f)
setStatusBarAndToolbar()
} else {
@ -1118,25 +1115,20 @@ class MangaDetailsController :
) return
val scrolledList = binding.recycler
val toolbarTextView = activityBinding?.toolbar?.toolbarTitle ?: return
toolbarTextView.setTextColor(
ColorUtils.setAlphaComponent(
toolbarTextView.currentTextColor,
(
when {
// Specific alpha provided
alpha != null -> alpha
val tbAlpha = when {
isTablet -> 0f
// Specific alpha provided
alpha != null -> alpha
// First item isn't in view, full opacity
((scrolledList.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() > 0) -> 1f
((scrolledList.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() == 0) -> 0f
// First item isn't in view, full opacity
((scrolledList.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() > 0) -> 1f
((scrolledList.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() == 0) -> 0f
// Based on scroll amount when first item is in view
else -> (scrolledList.computeVerticalScrollOffset() - (20.dpToPx))
.coerceIn(0, 255) / 255f
} * 255
).roundToInt()
)
)
// Based on scroll amount when first item is in view
else -> (scrolledList.computeVerticalScrollOffset() - (20.dpToPx))
.coerceIn(0, 255) / 255f
}
toolbarTextView.setTextColorAlpha((tbAlpha * 255).roundToInt())
}
private fun downloadChapters(choice: Int) {

View file

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.migration
import android.view.LayoutInflater
import android.view.View
import androidx.core.view.doOnNextLayout
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
@ -13,7 +14,9 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.util.system.await
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.widget.LinearLayoutManagerAccurateOffset
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import rx.schedulers.Schedulers
@ -45,8 +48,7 @@ class MigrationController :
scrollViewWith(binding.migrationRecycler, padBottom = true)
adapter = FlexibleAdapter(null, this)
binding.migrationRecycler.layoutManager =
androidx.recyclerview.widget.LinearLayoutManager(view.context)
binding.migrationRecycler.layoutManager = LinearLayoutManagerAccurateOffset(view.context)
binding.migrationRecycler.adapter = adapter
}
@ -83,10 +85,9 @@ class MigrationController :
binding.migrationRecycler.adapter = adapter
}
adapter?.updateDataSet(state.mangaForSource, true)
/*if (switching) launchUI {
binding.migrationRecycler.alpha = 0f
binding.migrationRecycler.animate().alpha(1f).setStartDelay(100).setDuration(200).start()
}*/
activityBinding?.appBar?.doOnNextLayout {
binding.migrationRecycler.requestApplyInsets()
}
}
}

View file

@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.PreMigrationControllerBinding
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig
@ -33,8 +34,8 @@ import uy.kohesive.injekt.injectLazy
class PreMigrationController(bundle: Bundle? = null) :
BaseController<PreMigrationControllerBinding>(bundle),
FlexibleAdapter
.OnItemClickListener,
FlexibleAdapter.OnItemClickListener,
SmallToolbarInterface,
StartMigrationListener {
private val sourceManager: SourceManager by injectLazy()
private val prefs: PreferencesHelper by injectLazy()

View file

@ -1,19 +1,22 @@
package eu.kanade.tachiyomi.ui.recents
import android.app.Activity
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.RoundedCorner
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.core.graphics.ColorUtils
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.core.view.updatePaddingRelative
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
@ -42,6 +45,7 @@ import eu.kanade.tachiyomi.ui.main.BottomSheetController
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.main.RootSearchInterface
import eu.kanade.tachiyomi.ui.main.TabbedInterface
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.recents.options.TabbedRecentsOptionsSheet
@ -58,19 +62,24 @@ import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.collapse
import eu.kanade.tachiyomi.util.view.compatToolTipText
import eu.kanade.tachiyomi.util.view.expand
import eu.kanade.tachiyomi.util.view.fullAppBarHeight
import eu.kanade.tachiyomi.util.view.hide
import eu.kanade.tachiyomi.util.view.isCollapsed
import eu.kanade.tachiyomi.util.view.isControllerVisible
import eu.kanade.tachiyomi.util.view.isExpanded
import eu.kanade.tachiyomi.util.view.isHidden
import eu.kanade.tachiyomi.util.view.moveRecyclerViewUp
import eu.kanade.tachiyomi.util.view.requestFilePermissionsSafe
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.setStyle
import eu.kanade.tachiyomi.util.view.smoothScrollToTop
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.updateGradiantBGRadius
import eu.kanade.tachiyomi.util.view.withFadeTransaction
import eu.kanade.tachiyomi.widget.LinearLayoutManagerAccurateOffset
import java.util.Locale
import kotlin.math.max
import kotlin.math.roundToInt
/**
* Fragment that shows recently read manga.
@ -84,6 +93,7 @@ class RecentsController(bundle: Bundle? = null) :
FlexibleAdapter.OnItemLongClickListener,
FlexibleAdapter.OnItemMoveListener,
FlexibleAdapter.EndlessScrollListener,
TabbedInterface,
RootSearchInterface,
FloatingSearchInterface,
BottomSheetController,
@ -106,16 +116,24 @@ class RecentsController(bundle: Bundle? = null) :
private var lastChapterId: Long? = null
private var showingDownloads = false
var headerHeight = 0
private var ogRadius = 0f
private var deviceRadius = 0f
private var query = ""
set(value) {
field = value
presenter.query = value
}
override val mainRecycler: RecyclerView
get() = binding.recycler
override fun getTitle(): String? {
return if (showingDownloads) {
resources?.getString(R.string.download_queue)
} else searchTitle(
return view?.context?.getString(R.string.recents)
}
override fun getSearchTitle(): String? {
return searchTitle(
view?.context?.getString(
when (presenter.viewType) {
RecentsPresenter.VIEW_TYPE_ONLY_HISTORY -> R.string.history
@ -137,10 +155,11 @@ class RecentsController(bundle: Bundle? = null) :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
// Initialize adapter
val isReturning = this::adapter.isInitialized
adapter = RecentMangaAdapter(this)
adapter.setPreferenceFlows()
binding.recycler.adapter = adapter
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.layoutManager = LinearLayoutManagerAccurateOffset(view.context)
binding.recycler.setHasFixedSize(true)
binding.recycler.recycledViewPool.setMaxRecycledViews(0, 0)
binding.recycler.addItemDecoration(
@ -150,28 +169,36 @@ class RecentsController(bundle: Bundle? = null) :
adapter.itemTouchHelperCallback.setSwipeFlags(
if (view.resources.isLTR) ItemTouchHelper.LEFT else ItemTouchHelper.RIGHT
)
val attrsArray = intArrayOf(R.attr.mainActionBarSize)
val array = view.context.obtainStyledAttributes(attrsArray)
val appBarHeight = array.getDimensionPixelSize(0, 0)
array.recycle()
binding.swipeRefresh.setStyle()
scrollViewWith(
binding.recycler,
swipeRefreshLayout = binding.swipeRefresh,
includeTabView = true,
afterInsets = {
val appBarHeight = activityBinding?.appBar?.attrToolbarHeight ?: 0
headerHeight = it.getInsets(systemBars()).top + appBarHeight + 48.dpToPx
binding.recycler.updatePaddingRelative(
bottom = activityBinding?.bottomNav?.height ?: it.getInsets(systemBars()).bottom
)
binding.recentsEmptyView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = headerHeight
bottomMargin =
activityBinding?.bottomNav?.height ?: it.getInsets(systemBars()).bottom
binding.downloadBottomSheet.sheetLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
height = appBarHeight + it.getInsets(systemBars()).top
}
val bigToolbarHeight = fullAppBarHeight ?: 0
binding.recentsEmptyView.updatePadding(
top = bigToolbarHeight + it.getInsets(systemBars()).top,
bottom = activityBinding?.bottomNav?.height ?: it.getInsets(systemBars()).bottom
)
binding.progress.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = (bigToolbarHeight + it.getInsets(systemBars()).top) / 2
}
if (activityBinding?.bottomNav == null) {
setBottomPadding()
}
deviceRadius = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
it.toWindowInsets()?.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT)?.radius?.toFloat() ?: ogRadius
} else {
ogRadius
}
},
onBottomNavUpdate = {
setBottomPadding()
@ -179,6 +206,10 @@ class RecentsController(bundle: Bundle? = null) :
)
viewScope.launchUI {
if (!isReturning && adapter.itemCount == 0) {
activityBinding?.appBar?.y = 0f
activityBinding?.appBar?.lockYPos = true
}
val height =
activityBinding?.bottomNav?.height ?: view.rootWindowInsetsCompat?.getInsets(
systemBars()
@ -188,21 +219,30 @@ class RecentsController(bundle: Bundle? = null) :
bottom = height
)
val isExpanded = binding.downloadBottomSheet.root.sheetBehavior.isExpanded()
activityBinding?.tabsFrameLayout?.isVisible = !isExpanded
if (isExpanded) {
(activity as? MainActivity)?.showTabBar(show = false, animate = false)
setRecentsAppBarBG(1f)
}
binding.downloadBottomSheet.dlRecycler.alpha = isExpanded.toInt().toFloat()
binding.downloadBottomSheet.sheetLayout.backgroundTintList = ColorStateList.valueOf(
ColorUtils.blendARGB(
view.context.getResourceColor(R.attr.colorPrimaryVariant),
view.context.getResourceColor(R.attr.background),
isExpanded.toInt().toFloat()
)
)
binding.downloadBottomSheet.root.backgroundTintList =
binding.downloadBottomSheet.sheetLayout.backgroundTintList
binding.downloadBottomSheet.titleText.alpha = (!isExpanded).toInt().toFloat()
binding.downloadBottomSheet.sheetToolbar.alpha = isExpanded.toInt().toFloat()
if (binding.downloadBottomSheet.root.sheetBehavior.isCollapsed()) {
if (hasQueue()) {
binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.isHideable =
false
} else {
binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.isHideable =
true
binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.state =
BottomSheetBehavior.STATE_HIDDEN
}
} else if (binding.downloadBottomSheet.root.sheetBehavior.isHidden()) {
if (!hasQueue()) {
binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.skipCollapsed =
true
} else {
binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.skipCollapsed =
false
binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.state =
BottomSheetBehavior.STATE_COLLAPSED
}
}
updateTitleAndMenu()
}
@ -223,58 +263,37 @@ class RecentsController(bundle: Bundle? = null) :
// Doing some fun math to hide the tab bar just as the title text of the
// dl sheet is under the toolbar
val cap = height * (1 / 12600f) + 479f / 700
val elValue = max(
max(0f, (progress - cap)) / (1 - cap),
if (binding.recycler.canScrollVertically(-1)) 1f else 0f
).coerceIn(0f, 1f)
setRecentsAppBarBG(elValue)
binding.downloadBottomSheet.sheetLayout.alpha = 1 - max(0f, progress / cap)
binding.downloadBottomSheet.titleText.alpha = 1 - max(0f, progress / cap)
binding.downloadBottomSheet.sheetToolbar.alpha = max(0f, progress / cap)
binding.downloadBottomSheet.pill.alpha = binding.downloadBottomSheet.titleText.alpha * 0.25f
binding.downloadBottomSheet.dlRecycler.alpha = progress * 10
binding.downloadBottomSheet.sheetLayout.backgroundTintList =
ColorStateList.valueOf(
ColorUtils.blendARGB(
view.context.getResourceColor(R.attr.colorPrimaryVariant),
view.context.getResourceColor(R.attr.background),
(progress * 2f).coerceIn(0f, 1f)
)
)
val oldShow = showingDownloads
showingDownloads = progress > 0.92f
if (router.backstack.lastOrNull()?.controller != this@RecentsController) {
if (!isControllerVisible) {
return
}
binding.downloadBottomSheet.root.backgroundTintList =
binding.downloadBottomSheet.sheetLayout.backgroundTintList
activityBinding?.appBar?.y = max(
activityBinding!!.appBar.y,
-headerHeight * (1 - progress)
)
activityBinding?.tabsFrameLayout?.let { tabs ->
tabs.alpha = 1 - max(0f, progress / cap)
if (tabs.alpha <= 0 && tabs.isVisible) {
tabs.isVisible = false
} else if (tabs.alpha > 0 && !tabs.isVisible) {
tabs.isVisible = true
}
if (isControllerVisible) {
activityBinding?.appBar?.alpha = (1 - progress * 3) + 0.5f
}
binding.downloadBottomSheet.root.updateGradiantBGRadius(
ogRadius,
deviceRadius,
progress,
binding.downloadBottomSheet.sheetLayout
)
if (oldShow != showingDownloads) {
updateTitleAndMenu()
activity?.invalidateOptionsMenu()
}
}
override fun onStateChanged(p0: View, state: Int) {
if (this@RecentsController.view == null) return
if (state == BottomSheetBehavior.STATE_EXPANDED) activityBinding?.appBar?.y = 0f
if (state == BottomSheetBehavior.STATE_EXPANDED || state == BottomSheetBehavior.STATE_COLLAPSED) {
binding.downloadBottomSheet.sheetLayout.alpha =
if (state == BottomSheetBehavior.STATE_COLLAPSED) 1f else 0f
showingDownloads = state == BottomSheetBehavior.STATE_EXPANDED
updateTitleAndMenu()
activity?.invalidateOptionsMenu()
}
if (router.backstack.lastOrNull()?.controller == this@RecentsController) {
if (isControllerVisible) {
activityBinding?.tabsFrameLayout?.isVisible =
state != BottomSheetBehavior.STATE_EXPANDED
}
@ -349,7 +368,8 @@ class RecentsController(bundle: Bundle? = null) :
LibraryUpdateService.start(view.context)
}
}
ogRadius = view.resources.getDimension(R.dimen.rounded_radius)
setSheetToolbar()
if (showingDownloads) {
binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.expand()
}
@ -359,27 +379,28 @@ class RecentsController(bundle: Bundle? = null) :
binding.downloadBottomSheet.root.sheetBehavior?.isGestureInsetBottomIgnored = true
}
fun updateTitleAndMenu() {
if (router.backstack.lastOrNull()?.controller == this) {
val activity = (activity as? MainActivity) ?: return
activity.setFloatingToolbar(!showingDownloads, true)
if (showingDownloads) {
setRecentsAppBarBG(1f)
private fun setSheetToolbar() {
binding.downloadBottomSheet.sheetToolbar.title = view?.context?.getString(R.string.download_queue)
binding.downloadBottomSheet.sheetToolbar.overflowIcon?.setTint(view?.context?.getResourceColor(R.attr.actionBarTintColor) ?: Color.BLACK)
binding.downloadBottomSheet.sheetToolbar.setOnMenuItemClickListener { item ->
return@setOnMenuItemClickListener binding.downloadBottomSheet.dlBottomSheet.onOptionsItemSelected(item)
}
binding.downloadBottomSheet.sheetToolbar.setNavigationOnClickListener {
if (binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.isHideable == true) {
binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.hide()
} else {
binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.collapse()
}
setTitle()
}
}
fun setRecentsAppBarBG(value: Float) {
val context = view?.context ?: return
val color = ColorUtils.blendARGB(
context.getResourceColor(R.attr.colorSurface),
context.getResourceColor(R.attr.colorPrimaryVariant),
value
)
activityBinding?.appBar?.setBackgroundColor(color)
activity?.window?.statusBarColor =
ColorUtils.setAlphaComponent(color, (0.87f * 255).roundToInt())
fun updateTitleAndMenu() {
if (isControllerVisible) {
val activity = (activity as? MainActivity) ?: return
activityBinding?.appBar?.isInvisible = showingDownloads
(activity as? MainActivity)?.setStatusBarColorTransparent(showingDownloads)
setTitle()
}
}
private fun setBottomPadding() {
@ -454,11 +475,11 @@ class RecentsController(bundle: Bundle? = null) :
setBottomPadding()
binding.downloadBottomSheet.dlBottomSheet.update()
if (BuildConfig.DEBUG && query.isBlank()) {
if (BuildConfig.DEBUG && query.isBlank() && isControllerVisible) {
val searchItem =
(activity as? MainActivity)?.binding?.cardToolbar?.menu?.findItem(R.id.action_search)
val searchView = searchItem?.actionView as? SearchView ?: return
if (router.backstack.lastOrNull()?.controller != this) return
if (!isControllerVisible) return
setOnQueryTextChangeListener(searchView) {
if (query != it) {
query = it ?: return@setOnQueryTextChangeListener false
@ -496,6 +517,9 @@ class RecentsController(bundle: Bundle? = null) :
adapter.removeAllScrollableHeaders()
adapter.updateItems(recents)
adapter.onLoadMoreComplete(null)
if (isControllerVisible) {
activityBinding?.appBar?.lockYPos = false
}
if (!hasNewItems || presenter.viewType == RecentsPresenter.VIEW_TYPE_GROUP_ALL ||
recents.isEmpty()
) {
@ -517,8 +541,14 @@ class RecentsController(bundle: Bundle? = null) :
} else {
binding.recentsEmptyView.hide()
}
val isSearchExpanded = activityBinding?.cardToolbar?.isSearchExpanded == true
if (shouldMoveToTop) {
binding.recycler.scrollToPosition(0)
if (isSearchExpanded) {
moveRecyclerViewUp(scrollUpAnyway = true)
} else {
(binding.recycler.layoutManager as? LinearLayoutManager)
?.scrollToPositionWithOffset(0, 0)
}
}
if (lastChapterId != null) {
refreshItem(lastChapterId ?: 0L)
@ -675,38 +705,25 @@ class RecentsController(bundle: Bundle? = null) :
override fun isSearching() = query.isNotEmpty()
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
if (onRoot) (activity as? MainActivity)?.setDismissIcon(showingDownloads)
if (showingDownloads) {
inflater.inflate(R.menu.download_queue, menu)
} else {
inflater.inflate(R.menu.recents, menu)
inflater.inflate(R.menu.recents, menu)
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchView.queryHint = view?.context?.getString(R.string.search_recents)
searchItem.collapseActionView()
if (isSearching()) {
searchItem.expandActionView()
searchView.setQuery(query, true)
searchView.clearFocus()
}
setOnQueryTextChangeListener(searchView) {
if (query != it) {
query = it ?: return@setOnQueryTextChangeListener false
// loadNoMore()
resetProgressItem()
refresh()
}
true
}
searchItem.fixExpandInvalidate()
hideItemsIfExpanded(searchItem, menu)
val searchItem = activityBinding?.cardToolbar?.searchItem
val searchView = activityBinding?.cardToolbar?.searchView
activityBinding?.cardToolbar?.setQueryHint(view?.context?.getString(R.string.search_recents), !isSearching())
if (isSearching()) {
searchItem?.expandActionView()
searchView?.setQuery(query, true)
searchView?.clearFocus()
}
setOnQueryTextChangeListener(activityBinding?.cardToolbar?.searchView) {
if (query != it) {
query = it ?: return@setOnQueryTextChangeListener false
// loadNoMore()
resetProgressItem()
refresh()
}
true
}
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
if (showingDownloads) binding.downloadBottomSheet.dlBottomSheet.prepareMenu(menu)
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
@ -786,8 +803,6 @@ class RecentsController(bundle: Bundle? = null) :
else binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.expand()
}
override fun sheetIsFullscreen(): Boolean = binding.downloadBottomSheet.dlBottomSheet.sheetBehavior.isExpanded()
override fun expandSearch() {
if (showingDownloads) {
binding.downloadBottomSheet.dlBottomSheet.dismiss()
@ -797,9 +812,6 @@ class RecentsController(bundle: Bundle? = null) :
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (showingDownloads) {
return binding.downloadBottomSheet.dlBottomSheet.onOptionsItemSelected(item)
}
when (item.itemId) {
R.id.display_options -> {
displaySheet = TabbedRecentsOptionsSheet(

View file

@ -135,8 +135,7 @@ class RecentsPresenter(
}
val viewType = customViewType ?: viewType
val showRead = ((preferences.showReadInAllRecents().get() || query.isNotEmpty()) && !limit) ||
includeReadAnyway == true
val showRead = ((preferences.showReadInAllRecents().get() || query.isNotEmpty()) && !limit) || includeReadAnyway
val isUngrouped = viewType > VIEW_TYPE_GROUP_ALL || query.isNotEmpty()
val groupChaptersUpdates = preferences.groupChaptersUpdates().get()
val groupChaptersHistory = preferences.groupChaptersHistory().get()
@ -195,6 +194,10 @@ class RecentsPresenter(
else -> emptyList()
}
if (cReading.size < ENDLESS_LIMIT) {
finished = true
}
if (!isCustom &&
(pageOffset == 0 || updatePageCount)
) {
@ -284,7 +287,7 @@ class RecentsPresenter(
.compareTo(d1)
}
val byDay =
pairs.groupByTo(map, { getMapKey(it.first.history.last_read) })
pairs.groupByTo(map) { getMapKey(it.first.history.last_read) }
byDay.flatMap {
val dateItem = DateItem(it.key, true)
it.value

View file

@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupport
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.startAuthentication
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.preference.AdaptiveTitlePreferenceCategory
import eu.kanade.tachiyomi.widget.preference.IntListMatPreference
import eu.kanade.tachiyomi.widget.preference.ListMatPreference
import eu.kanade.tachiyomi.widget.preference.MultiListMatPreference
@ -103,12 +104,7 @@ inline fun PreferenceGroup.triStateListPreference(
}
inline fun PreferenceScreen.preferenceCategory(block: (@DSL PreferenceCategory).() -> Unit): PreferenceCategory {
return addThenInit(
PreferenceCategory(context).apply {
isIconSpaceReserved = false
},
block
)
return addThenInit(AdaptiveTitlePreferenceCategory(context), block)
}
inline fun PreferenceScreen.switchPreference(block: (@DSL SwitchPreferenceCompat).() -> Unit): SwitchPreferenceCompat {

View file

@ -4,6 +4,8 @@ import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.doOnNextLayout
import androidx.core.view.isVisible
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
@ -13,6 +15,8 @@ import eu.kanade.tachiyomi.util.system.appDelegateNightMode
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getPrefTheme
import eu.kanade.tachiyomi.util.system.isInNightMode
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.moveRecyclerViewUp
import kotlinx.coroutines.flow.launchIn
import kotlin.math.max
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
@ -82,6 +86,35 @@ class SettingsAppearanceController : SettingsController() {
}
}
preferenceCategory {
switchPreference {
key = Keys.useLargeToolbar
titleRes = R.string.expanded_toolbar
summaryRes = R.string.show_larger_toolbar
defaultValue = true
onChange {
val useLarge = it as Boolean
activityBinding?.appBar?.setToolbarModeBy(this@SettingsAppearanceController, !useLarge)
activityBinding?.appBar?.hideBigView(!useLarge, !useLarge)
activityBinding?.toolbar?.alpha = 1f
activityBinding?.toolbar?.translationY = 0f
activityBinding?.toolbar?.isVisible = true
activityBinding?.appBar?.doOnNextLayout {
listView.requestApplyInsets()
listView.post {
if (useLarge) {
moveRecyclerViewUp(true)
} else {
activityBinding?.appBar?.updateAppBarAfterY(listView)
}
}
}
true
}
}
}
preferenceCategory {
titleRes = R.string.details_page
switchPreference {

View file

@ -8,6 +8,7 @@ import android.os.Bundle
import android.util.TypedValue
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
@ -20,8 +21,10 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.widget.LinearLayoutManagerAccurateOffset
import kotlinx.coroutines.MainScope
import rx.Observable
import rx.Subscription
@ -39,6 +42,11 @@ abstract class SettingsController : PreferenceController() {
var untilDestroySubscriptions = CompositeSubscription()
private set
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listView.layoutManager = LinearLayoutManagerAccurateOffset(view?.context)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View {
if (untilDestroySubscriptions.isUnsubscribed) {
untilDestroySubscriptions = CompositeSubscription()
@ -80,6 +88,9 @@ abstract class SettingsController : PreferenceController() {
abstract fun setupPreferenceScreen(screen: PreferenceScreen): PreferenceScreen
open fun onActionViewExpand(item: MenuItem?) { }
open fun onActionViewCollapse(item: MenuItem?) { }
private fun getThemedContext(): Context {
val tv = TypedValue()
activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true)
@ -97,11 +108,12 @@ abstract class SettingsController : PreferenceController() {
}
}
open fun getTitle(): String? {
if (this is FloatingSearchInterface) {
return searchTitle(preferenceScreen?.title?.toString()?.lowercase(Locale.ROOT))
}
return preferenceScreen?.title?.toString()
open fun getTitle(): String? = preferenceScreen?.title?.toString()
open fun getSearchTitle(): String? {
return if (this is FloatingSearchInterface) {
searchTitle(preferenceScreen?.title?.toString()?.lowercase(Locale.ROOT))
} else null
}
fun setTitle() {
@ -113,7 +125,8 @@ abstract class SettingsController : PreferenceController() {
parentController = parentController.parentController
}
(activity as? AppCompatActivity)?.supportActionBar?.title = getTitle()
(activity as? AppCompatActivity)?.title = getTitle()
(activity as? MainActivity)?.searchTitle = getSearchTitle()
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {

View file

@ -174,18 +174,6 @@ class SettingsLibraryController : SettingsController() {
allSelectionRes = R.string.all
}
intListPreference(activity) {
key = Keys.updateOnRefresh
titleRes = R.string.categories_on_manual
entriesRes = arrayOf(
R.string.first_category,
R.string.categories_in_global_update
)
entryRange = 0..1
defaultValue = -1
}
switchPreference {
key = Keys.refreshCoversToo
titleRes = R.string.auto_refresh_covers

View file

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.setting
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import androidx.preference.PreferenceScreen
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.RouterTransaction
@ -11,6 +10,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.setting.search.SettingsSearchController
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.withFadeTransaction
class SettingsMainController : SettingsController(), FloatingSearchInterface {
@ -88,29 +88,14 @@ class SettingsMainController : SettingsController(), FloatingSearchInterface {
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.settings_main, menu)
// Initialize search option.
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE
// Change hint to show global search.
searchView.queryHint = applicationContext?.getString(R.string.search_settings)
activityBinding?.cardToolbar?.searchQueryHint = applicationContext?.getString(R.string.search_settings)
}
searchItem.setOnActionExpandListener(
object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
SettingsSearchController.lastSearch = "" // reset saved search query
router.pushController(
RouterTransaction.with(SettingsSearchController())
)
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
return true
}
}
override fun onActionViewExpand(item: MenuItem?) {
SettingsSearchController.lastSearch = "" // reset saved search query
router.pushController(
RouterTransaction.with(SettingsSearchController())
)
}

View file

@ -15,13 +15,18 @@ import eu.kanade.tachiyomi.data.preference.plusAssign
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.icon
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.isControllerVisible
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.TreeMap
class SettingsSourcesController : SettingsController() {
class SettingsSourcesController : SettingsController(), FloatingSearchInterface {
init {
setHasOptionsMenu(true)
}
@ -35,6 +40,10 @@ class SettingsSourcesController : SettingsController() {
private var sourcesByLang: TreeMap<String, MutableList<HttpSource>> = TreeMap()
private var sorting = SourcesSort.Alpha
override fun getSearchTitle(): String? {
return view?.context?.getString(R.string.search)
}
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = R.string.filter
sorting = SourcesSort.from(preferences.sourceSorting().get()) ?: SourcesSort.Alpha
@ -43,7 +52,7 @@ class SettingsSourcesController : SettingsController() {
val activeLangsCodes = preferences.enabledLanguages().get()
// Get a map of sources grouped by language.
sourcesByLang = onlineSources.groupByTo(TreeMap(), { it.lang })
sourcesByLang = onlineSources.groupByTo(TreeMap()) { it.lang }
// Order first by active languages, then inactive ones
orderedLangs = sourcesByLang.keys.filter { it in activeLangsCodes } + sourcesByLang.keys
@ -163,26 +172,43 @@ class SettingsSourcesController : SettingsController() {
if (sorting == SourcesSort.Alpha) menu.findItem(R.id.action_sort_alpha).isChecked = true
else menu.findItem(R.id.action_sort_enabled).isChecked = true
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE
if (query.isNotEmpty()) {
searchItem.expandActionView()
searchView.setQuery(query, true)
searchView.clearFocus()
val useSearchTB = showFloatingBar()
val searchItem = if (useSearchTB) activityBinding?.cardToolbar?.searchItem
else (menu.findItem(R.id.action_search))
val searchView = if (useSearchTB) activityBinding?.cardToolbar?.searchView
else searchItem?.actionView as? SearchView
if (!useSearchTB) {
searchView?.maxWidth = Int.MAX_VALUE
}
searchView.queryTextChanges().filter { router.backstack.lastOrNull()?.controller == this }
.subscribeUntilDestroy {
activityBinding?.cardToolbar?.setQueryHint(getSearchTitle(), query.isEmpty())
if (query.isNotEmpty()) {
searchItem?.expandActionView()
searchView?.setQuery(query, true)
searchView?.clearFocus()
}
setOnQueryTextChangeListener(activityBinding?.cardToolbar?.searchView) {
query = it ?: ""
drawSources()
true
}
searchView?.queryTextChanges()?.filter { isControllerVisible }
?.subscribeUntilDestroy {
query = it.toString()
drawSources()
}
// Fixes problem with the overflow icon showing up in lieu of search
searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() })
if (useSearchTB) {
// Fixes problem with the overflow icon showing up in lieu of search
searchItem?.fixExpand(onExpand = { invalidateMenuOnExpand() })
}
}
override fun showFloatingBar() = activityBinding?.appBar?.useLargeToolbar == true
var expandActionViewFromInteraction = false
private fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) {
setOnActionExpandListener(
@ -248,6 +274,14 @@ class SettingsSourcesController : SettingsController() {
else -> return super.onOptionsItemSelected(item)
}
item.isChecked = true
(activity as? MainActivity)?.let {
val otherTB = if (it.currentToolbar == it.binding.cardToolbar) {
it.binding.toolbar
} else {
it.binding.cardToolbar
}
otherTB.menu.findItem(item.itemId).isChecked = true
}
preferences.sourceSorting().set(sorting.value)
drawSources()
return true

View file

@ -10,9 +10,11 @@ import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.SettingsSearchControllerBinding
import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.setting.SettingsController
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.liftAppbarWith
import eu.kanade.tachiyomi.util.view.withFadeTransaction
@ -23,13 +25,14 @@ import eu.kanade.tachiyomi.util.view.withFadeTransaction
class SettingsSearchController :
NucleusController<SettingsSearchControllerBinding, SettingsSearchPresenter>(),
FloatingSearchInterface,
SmallToolbarInterface,
SettingsSearchAdapter.OnTitleClickListener {
/**
* Adapter containing search results grouped by lang.
*/
private var adapter: SettingsSearchAdapter? = null
private lateinit var searchView: SearchView
private var searchView: SearchView? = null
init {
setHasOptionsMenu(true)
@ -60,31 +63,15 @@ class SettingsSearchController :
// Inflate menu.
inflater.inflate(R.menu.settings_main, menu)
// Initialize search menu
val searchItem = menu.findItem(R.id.action_search)
searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE
val searchItem = activityBinding?.cardToolbar?.searchItem
searchView = activityBinding?.cardToolbar?.searchView
// Change hint to show "search settings."
searchView.queryHint = applicationContext?.getString(R.string.search_settings)
activityBinding?.cardToolbar?.setQueryHint(applicationContext?.getString(R.string.search_settings), false)
searchItem.expandActionView()
searchItem?.expandActionView()
setItems(getResultSet())
searchItem.setOnActionExpandListener(
object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
router.popCurrentController()
return false
}
}
)
searchView.setOnQueryTextListener(
searchView?.setOnQueryTextListener(
object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
setItems(getResultSet(query))
@ -101,7 +88,11 @@ class SettingsSearchController :
}
)
searchView.setQuery(lastSearch, true)
searchView?.setQuery(lastSearch, true)
}
override fun onActionViewCollapse(item: MenuItem?) {
router.popCurrentController()
}
override fun onViewCreated(view: View) {
@ -159,7 +150,7 @@ class SettingsSearchController :
* Opens a catalogue with the given search.
*/
override fun onTitleClick(ctrl: SettingsController) {
searchView.query.let {
searchView?.query.let {
lastSearch = it.toString()
}

View file

@ -1,17 +1,20 @@
package eu.kanade.tachiyomi.ui.source
import android.app.Activity
import android.graphics.Color
import android.os.Build
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.RoundedCorner
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.core.graphics.ColorUtils
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.doOnNextLayout
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePaddingRelative
import androidx.recyclerview.widget.RecyclerView
@ -23,6 +26,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.snackbar.Snackbar
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.BrowseControllerBinding
@ -47,22 +51,25 @@ import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
import eu.kanade.tachiyomi.util.system.spToPx
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.checkHeightThen
import eu.kanade.tachiyomi.util.view.collapse
import eu.kanade.tachiyomi.util.view.expand
import eu.kanade.tachiyomi.util.view.isCollapsed
import eu.kanade.tachiyomi.util.view.isExpanded
import eu.kanade.tachiyomi.util.view.isControllerVisible
import eu.kanade.tachiyomi.util.view.requestFilePermissionsSafe
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.toolbarHeight
import eu.kanade.tachiyomi.util.view.updateGradiantBGRadius
import eu.kanade.tachiyomi.util.view.withFadeTransaction
import eu.kanade.tachiyomi.widget.LinearLayoutManagerAccurateOffset
import kotlinx.parcelize.Parcelize
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Date
import java.util.Locale
import kotlin.math.max
import kotlin.math.roundToInt
/**
* This controller shows and manages the different catalogues enabled by the user.
@ -96,6 +103,12 @@ class BrowseController :
var snackbar: Snackbar? = null
private var ogRadius = 0f
private var deviceRadius = 0f
override val mainRecycler: RecyclerView
get() = binding.sourceRecycler
/**
* Called when controller is initialized.
*/
@ -103,15 +116,10 @@ class BrowseController :
setHasOptionsMenu(true)
}
override fun getTitle(): String? {
return if (showingExtensions) {
view?.context?.getString(
when (binding.bottomSheet.tabs.selectedTabPosition) {
0 -> R.string.extensions
else -> R.string.source_migration
}
)
} else searchTitle(view?.context?.getString(R.string.sources)?.lowercase(Locale.ROOT))
override fun getTitle(): String? = view?.context?.getString(R.string.browse)
override fun getSearchTitle(): String? {
return searchTitle(view?.context?.getString(R.string.sources)?.lowercase(Locale.ROOT))
}
val presenter = SourcePresenter(this)
@ -120,36 +128,37 @@ class BrowseController :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
val isReturning = adapter != null
adapter = SourceAdapter(this)
// Create binding.sourceRecycler and set adapter.
binding.sourceRecycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
binding.sourceRecycler.layoutManager = LinearLayoutManagerAccurateOffset(view.context)
binding.sourceRecycler.adapter = adapter
adapter?.isSwipeEnabled = true
adapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
val attrsArray = intArrayOf(R.attr.mainActionBarSize)
val array = view.context.obtainStyledAttributes(attrsArray)
val appBarHeight = array.getDimensionPixelSize(0, 0)
array.recycle()
scrollViewWith(
binding.sourceRecycler,
customPadding = true,
afterInsets = {
headerHeight = it.getInsets(systemBars()).top + appBarHeight
headerHeight = binding.sourceRecycler.paddingTop
binding.sourceRecycler.updatePaddingRelative(
top = headerHeight,
bottom = (activityBinding?.bottomNav?.height ?: it.getBottomGestureInsets()) + 58.spToPx
)
if (activityBinding?.bottomNav == null) {
setBottomPadding()
}
deviceRadius = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
it.toWindowInsets()?.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT)?.radius?.toFloat() ?: ogRadius
} else {
ogRadius
}
},
onBottomNavUpdate = {
setBottomPadding()
}
)
if (!isReturning) {
activityBinding?.appBar?.lockYPos = true
}
binding.sourceRecycler.post {
setBottomSheetTabs(if (binding.bottomSheet.root.sheetBehavior.isCollapsed()) 0f else 1f)
binding.sourceRecycler.updatePaddingRelative(
@ -167,12 +176,12 @@ class BrowseController :
object : BottomSheetBehavior
.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, progress: Float) {
activityBinding?.appBar?.y = max(activityBinding!!.appBar.y, -headerHeight * (1 - progress))
val oldShow = showingExtensions
showingExtensions = progress > 0.92f
if (oldShow != showingExtensions) {
updateTitleAndMenu()
}
binding.bottomSheet.sheetToolbar.isVisible = true
setBottomSheetTabs(max(0f, progress))
}
@ -184,14 +193,12 @@ class BrowseController :
binding.bottomSheet.root.isExpanding = false
}
val extBottomSheet = binding.bottomSheet.root
if (state == BottomSheetBehavior.STATE_EXPANDED) {
activityBinding?.appBar?.y = 0f
}
if (state == BottomSheetBehavior.STATE_EXPANDED ||
state == BottomSheetBehavior.STATE_COLLAPSED
) {
binding.bottomSheet.root.sheetBehavior?.isDraggable = true
showingExtensions = state == BottomSheetBehavior.STATE_EXPANDED
binding.bottomSheet.sheetToolbar.isVisible = showingExtensions
updateTitleAndMenu()
if (state == BottomSheetBehavior.STATE_EXPANDED) {
extBottomSheet.fetchOnlineExtensionsIfNeeded()
@ -213,29 +220,93 @@ class BrowseController :
if (showingExtensions) {
binding.bottomSheet.root.sheetBehavior?.expand()
}
ogRadius = view.resources.getDimension(R.dimen.rounded_radius)
setSheetToolbar()
presenter.onCreate()
if (presenter.sourceItems.isNotEmpty()) {
setSources(presenter.sourceItems, presenter.lastUsedItem)
} else {
binding.sourceRecycler.checkHeightThen {
binding.sourceRecycler.scrollToPosition(0)
}
}
}
fun updateTitleAndMenu() {
if (router.backstack.lastOrNull()?.controller == this) {
val activity = (activity as? MainActivity) ?: return
(activity as? MainActivity)?.setFloatingToolbar(!showingExtensions)
if (showingExtensions) {
val color = activity.getResourceColor(R.attr.colorPrimaryVariant)
activityBinding?.appBar?.setBackgroundColor(color)
activity.window?.statusBarColor =
ColorUtils.setAlphaComponent(color, (0.87f * 255).roundToInt())
private fun updateSheetMenu() {
binding.bottomSheet.sheetToolbar.title =
view?.context?.getString(
if (binding.bottomSheet.tabs.selectedTabPosition == 0) R.string.extensions
else R.string.source_migration
)
val onExtensionTab = binding.bottomSheet.tabs.selectedTabPosition == 0
if (binding.bottomSheet.sheetToolbar.menu.findItem(if (onExtensionTab) R.id.action_search else R.id.action_migration_guide) != null) {
return
}
val oldSearchView = binding.bottomSheet.sheetToolbar.menu.findItem(R.id.action_search)?.actionView as? SearchView
oldSearchView?.setOnQueryTextListener(null)
binding.bottomSheet.sheetToolbar.menu.clear()
binding.bottomSheet.sheetToolbar.inflateMenu(
if (binding.bottomSheet.tabs.selectedTabPosition == 0) R.menu.extension_main
else R.menu.migration_main
)
// Initialize search option.
binding.bottomSheet.sheetToolbar.menu.findItem(R.id.action_search)?.let { searchItem ->
val searchView = searchItem.actionView as SearchView
// Change hint to show global search.
searchView.queryHint = view?.context?.getString(R.string.search_extensions)
if (extQuery.isNotEmpty()) {
searchView.setOnQueryTextListener(null)
searchItem.expandActionView()
searchView.setQuery(extQuery, true)
searchView.clearFocus()
} else {
activityBinding?.appBar?.setBackgroundColor(Color.TRANSPARENT)
activity.window?.statusBarColor = activity.getResourceColor(
android.R.attr.statusBarColor
)
searchItem.collapseActionView()
}
activity.invalidateOptionsMenu()
setTitle()
// Create query listener which opens the global search view.
setOnQueryTextChangeListener(searchView) {
extQuery = it ?: ""
binding.bottomSheet.root.drawExtensions()
true
}
}
}
private fun setSheetToolbar() {
binding.bottomSheet.sheetToolbar.setOnMenuItemClickListener { item ->
when (item.itemId) {
// Initialize option to open catalogue settings.
R.id.action_filter -> {
val controller = ExtensionFilterController()
router.pushController(
RouterTransaction.with(controller)
.popChangeHandler(SettingsSourcesFadeChangeHandler())
.pushChangeHandler(FadeChangeHandler())
)
}
R.id.action_migration_guide -> {
activity?.openInBrowser(HELP_URL)
}
R.id.action_sources_settings -> {
router.pushController(SettingsBrowseController().withFadeTransaction())
}
}
return@setOnMenuItemClickListener true
}
binding.bottomSheet.sheetToolbar.setNavigationOnClickListener {
binding.bottomSheet.root.sheetBehavior?.collapse()
}
updateSheetMenu()
}
fun updateTitleAndMenu() {
if (isControllerVisible) {
val activity = (activity as? MainActivity) ?: return
activityBinding?.appBar?.isInvisible = showingExtensions
(activity as? MainActivity)?.setStatusBarColorTransparent(showingExtensions)
updateSheetMenu()
}
}
@ -243,9 +314,27 @@ class BrowseController :
val bottomSheet = binding.bottomSheet.root
val halfStepProgress = (max(0.5f, progress) - 0.5f) * 2
binding.bottomSheet.tabs.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = ((activityBinding?.appBar?.height?.minus(9f.dpToPx) ?: 0f) * halfStepProgress).toInt()
topMargin = (
(
activityBinding?.appBar?.paddingTop
?.minus(9f.dpToPx)
?.plus(toolbarHeight ?: 0) ?: 0f
) * halfStepProgress
).toInt()
}
binding.bottomSheet.pill.alpha = (1 - progress) * 0.25f
binding.bottomSheet.sheetToolbar.alpha = progress
if (isControllerVisible) {
activityBinding?.appBar?.alpha = (1 - progress * 3) + 0.5f
}
binding.bottomSheet.root.updateGradiantBGRadius(
ogRadius,
deviceRadius,
progress,
binding.bottomSheet.sheetLayout
)
val selectedColor = ColorUtils.setAlphaComponent(
bottomSheet.context.getResourceColor(R.attr.tabBarIconColor),
(progress * 255).toInt()
@ -318,8 +407,6 @@ class BrowseController :
}
}
override fun sheetIsFullscreen(): Boolean = binding.bottomSheet.root.sheetBehavior.isExpanded()
override fun handleSheetBack(): Boolean {
if (showingExtensions) {
if (binding.bottomSheet.root.canGoBack()) {
@ -346,9 +433,22 @@ class BrowseController :
binding.bottomSheet.root.updateExtTitle()
binding.bottomSheet.root.presenter.refreshExtensions()
presenter.updateSources()
if (type.isEnter) {
activityBinding?.appBar?.doOnNextLayout {
activityBinding?.appBar?.y = 0f
activityBinding?.appBar?.updateAppBarAfterY(binding.sourceRecycler)
}
updateSheetMenu()
}
}
if (!type.isEnter) {
binding.bottomSheet.root.canExpand = false
activityBinding?.appBar?.alpha = 1f
activityBinding?.appBar?.isInvisible = false
binding.bottomSheet.sheetToolbar.menu.findItem(R.id.action_search)?.let { searchItem ->
val searchView = searchItem.actionView as SearchView
searchView.clearFocus()
}
} else {
binding.bottomSheet.root.presenter.refreshMigrations()
updateTitleAndMenu()
@ -372,7 +472,15 @@ class BrowseController :
binding.bottomSheet.root.presenter.refreshMigrations()
setBottomPadding()
if (showingExtensions) {
activity.invalidateOptionsMenu()
updateSheetMenu()
}
if (BuildConfig.DEBUG && isControllerVisible) {
val searchView = activityBinding?.cardToolbar?.searchView
setOnQueryTextChangeListener(searchView, onlyOnSubmit = true) {
if (!it.isNullOrBlank()) performGlobalSearch(it)
true
}
}
}
@ -462,52 +570,19 @@ class BrowseController :
* @param inflater used to load the menu xml.
*/
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
if (onRoot) (activity as? MainActivity)?.setDismissIcon(showingExtensions)
if (showingExtensions) {
if (binding.bottomSheet.tabs.selectedTabPosition == 0) {
// Inflate menu
inflater.inflate(R.menu.extension_main, menu)
// Inflate menu
inflater.inflate(R.menu.catalogue_main, menu)
// Initialize search option.
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
// Initialize search option.
val searchView = activityBinding?.cardToolbar?.searchView
// Change hint to show global search.
searchView.queryHint = view?.context?.getString(R.string.search_extensions)
searchItem.collapseActionView()
if (extQuery.isNotEmpty()) {
searchItem.expandActionView()
searchView.setQuery(extQuery, true)
searchView.clearFocus()
}
// Create query listener which opens the global search view.
setOnQueryTextChangeListener(searchView) {
extQuery = it ?: ""
binding.bottomSheet.root.drawExtensions()
true
}
searchItem.fixExpandInvalidate()
} else {
inflater.inflate(R.menu.migration_main, menu)
}
} else {
// Inflate menu
inflater.inflate(R.menu.catalogue_main, menu)
// Change hint to show global search.
activityBinding?.cardToolbar?.searchQueryHint = view?.context?.getString(R.string.global_search)
// Initialize search option.
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
// Change hint to show global search.
searchView.queryHint = view?.context?.getString(R.string.global_search)
searchItem.fixExpandInvalidate()
// Create query listener which opens the global search view.
setOnQueryTextChangeListener(searchView, true) {
if (!it.isNullOrBlank()) performGlobalSearch(it)
true
}
hideItemsIfExpanded(searchItem, menu)
// Create query listener which opens the global search view.
setOnQueryTextChangeListener(searchView, true) {
if (!it.isNullOrBlank()) performGlobalSearch(it)
true
}
}
@ -525,10 +600,7 @@ class BrowseController :
when (item.itemId) {
// Initialize option to open catalogue settings.
R.id.action_filter -> {
val controller =
if (showingExtensions) {
ExtensionFilterController()
} else SettingsSourcesController()
val controller = SettingsSourcesController()
router.pushController(
RouterTransaction.with(controller)
.popChangeHandler(SettingsSourcesFadeChangeHandler())
@ -552,6 +624,9 @@ class BrowseController :
fun setSources(sources: List<IFlexible<*>>, lastUsed: SourceItem?) {
adapter?.updateDataSet(sources, false)
setLastUsedSource(lastUsed)
if (isControllerVisible) {
activityBinding?.appBar?.lockYPos = false
}
}
/**

View file

@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.util.system.withUIContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@ -66,7 +67,7 @@ class SourcePresenter(
else -> d1.compareTo(d2)
}
}
val byLang = sources.groupByTo(map, { it.lang })
val byLang = sources.groupByTo(map) { it.lang }
sourceItems = byLang.flatMap {
val langItem = LangItem(it.key)
it.value.map { source ->
@ -94,6 +95,7 @@ class SourcePresenter(
private fun loadLastUsedSource() {
lastUsedJob?.cancel()
lastUsedJob = preferences.lastUsedCatalogueSource().asFlow()
.drop(1)
.onEach {
lastUsedItem = getLastUsedSource(it)
withUIContext {

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.ui.source.browse
import android.app.Activity
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
@ -9,11 +10,11 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.core.view.WindowInsetsCompat.Type.ime
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -27,6 +28,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.BrowseSourceControllerBinding
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.icon
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.online.HttpSource
@ -41,8 +43,11 @@ import eu.kanade.tachiyomi.util.addOrRemoveToFavorites
import eu.kanade.tachiyomi.util.system.connectivityManager
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.applyBottomAnimatedInsets
import eu.kanade.tachiyomi.util.view.fullAppBarHeight
import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.isControllerVisible
import eu.kanade.tachiyomi.util.view.requestFilePermissionsSafe
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
@ -50,8 +55,10 @@ import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.withFadeTransaction
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import eu.kanade.tachiyomi.widget.EmptyView
import eu.kanade.tachiyomi.widget.LinearLayoutManagerAccurateOffset
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import kotlin.math.roundToInt
/**
* Controller to manage the catalogues available in the app.
@ -115,14 +122,28 @@ open class BrowseSourceController(bundle: Bundle) :
/** Current filter sheet */
var filterSheet: SourceFilterSheet? = null
private val isBehindGlobalSearch: Boolean
get() = router.backstackSize >= 2 && router.backstack[router.backstackSize - 2].controller is GlobalSearchController
init {
setHasOptionsMenu(true)
}
override val mainRecycler: RecyclerView?
get() = recycler
override fun getTitle(): String? {
return presenter.source.name
}
override fun getSearchTitle(): String? {
return searchTitle(presenter.source.name)
}
override fun getBigIcon(): Drawable? {
return presenter.source.icon()
}
override fun createPresenter(): BrowseSourcePresenter {
return BrowseSourcePresenter(args.getLong(SOURCE_ID_KEY), args.getString(SEARCH_QUERY_KEY))
}
@ -139,6 +160,9 @@ open class BrowseSourceController(bundle: Bundle) :
binding.fab.isVisible = presenter.sourceFilters.isNotEmpty()
binding.fab.setOnClickListener { showFilters() }
binding.progress.isVisible = true
activityBinding?.appBar?.y = 0f
activityBinding?.appBar?.updateAppBarAfterY(recycler)
activityBinding?.appBar?.lockYPos = true
requestFilePermissionsSafe(301, preferences, presenter.source is LocalSource)
}
@ -151,9 +175,13 @@ open class BrowseSourceController(bundle: Bundle) :
private fun setupRecycler(view: View) {
var oldPosition = RecyclerView.NO_POSITION
var oldOffset = 0f
val oldRecycler = binding.catalogueView.getChildAt(1)
if (oldRecycler is RecyclerView) {
oldPosition = (oldRecycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
oldPosition = (oldRecycler.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
.takeIf { it != RecyclerView.NO_POSITION }
?: (oldRecycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
oldOffset = oldRecycler.layoutManager?.findViewByPosition(oldPosition)?.y?.minus(oldRecycler.paddingTop) ?: 0f
oldRecycler.adapter = null
binding.catalogueView.removeView(oldRecycler)
@ -162,7 +190,7 @@ open class BrowseSourceController(bundle: Bundle) :
val recycler = if (presenter.isListMode) {
RecyclerView(view.context).apply {
id = R.id.recycler
layoutManager = LinearLayoutManager(context)
layoutManager = LinearLayoutManagerAccurateOffset(context)
layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
}
@ -184,6 +212,7 @@ open class BrowseSourceController(bundle: Bundle) :
recycler.setHasFixedSize(true)
recycler.adapter = adapter
binding.catalogueView.addView(recycler, 1)
scrollViewWith(
recycler,
true,
@ -193,6 +222,14 @@ open class BrowseSourceController(bundle: Bundle) :
bottomMargin = insets.getInsets(systemBars() or ime()).bottom + 16.dpToPx
}
}
val bigToolbarHeight = fullAppBarHeight ?: 0
binding.progress.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = (bigToolbarHeight + insets.getInsets(systemBars()).top) / 2
}
binding.emptyView.updatePadding(
top = (bigToolbarHeight + insets.getInsets(systemBars()).top),
bottom = insets.getInsets(systemBars()).bottom
)
}
)
binding.fab.applyBottomAnimatedInsets(16.dpToPx)
@ -209,9 +246,11 @@ open class BrowseSourceController(bundle: Bundle) :
}
)
binding.catalogueView.addView(recycler, 1)
if (oldPosition != RecyclerView.NO_POSITION) {
recycler.layoutManager?.scrollToPosition(oldPosition)
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(oldPosition, oldOffset.roundToInt())
if (oldPosition > 0 && (activity as? MainActivity)?.currentToolbar != activityBinding?.cardToolbar) {
activityBinding?.appBar?.useSearchToolbarForMenu(true)
}
}
this.recycler = recycler
}
@ -220,58 +259,45 @@ open class BrowseSourceController(bundle: Bundle) :
inflater.inflate(R.menu.browse_source, menu)
// Initialize search menu
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
val searchItem = activityBinding?.cardToolbar?.searchItem
val searchView = activityBinding?.cardToolbar?.searchView
activityBinding?.cardToolbar?.setQueryHint("", !isBehindGlobalSearch && presenter.query.isBlank())
val query = presenter.query
if (query.isNotBlank()) {
searchItem.expandActionView()
searchView.setQuery(query, true)
searchView.clearFocus()
searchItem?.expandActionView()
searchView?.setQuery(query, true)
searchView?.clearFocus()
} else if (activityBinding?.cardToolbar?.isSearchExpanded == true) {
searchItem?.collapseActionView()
searchView?.setQuery("", true)
}
// val searchEventsObservable = searchView.queryTextChangeEvents()
// .skip(1)
// .filter { router.backstack.lastOrNull()?.controller == this@BrowseSourceController }
// .share()
// val writingObservable = searchEventsObservable
// .filter { !it.isSubmitted }
// .debounce(1250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
// val submitObservable = searchEventsObservable
// .filter { it.isSubmitted }
//
// searchViewSubscription?.unsubscribe()
// searchViewSubscription = Observable.merge(writingObservable, submitObservable)
// .map { it.queryText().toString() }
// .subscribeUntilDestroy { searchWithQuery(it) }
setOnQueryTextChangeListener(searchView, onlyOnSubmit = true, hideKbOnSubmit = true) {
searchWithQuery(it ?: "")
true
}
searchItem.fixExpand(
onExpand = { invalidateMenuOnExpand() },
onCollapse = {
if (router.backstackSize >= 2 && router.backstack[router.backstackSize - 2].controller is GlobalSearchController) {
router.popController(this)
} else {
searchWithQuery("")
}
true
}
)
// Show next display mode
menu.findItem(R.id.action_display_mode).apply {
val icon = if (presenter.isListMode) {
updateDisplayMenuItem(menu)
}
private fun updateDisplayMenuItem(menu: Menu?, isListMode: Boolean? = null) {
menu?.findItem(R.id.action_display_mode)?.apply {
val icon = if (isListMode ?: presenter.isListMode) {
R.drawable.ic_view_module_24dp
} else {
R.drawable.ic_view_list_24dp
}
setIcon(icon)
}
hideItemsIfExpanded(searchItem, menu)
}
override fun onActionViewCollapse(item: MenuItem?) {
if (isBehindGlobalSearch) {
router.popController(this)
} else {
searchWithQuery("")
}
}
override fun onPrepareOptionsMenu(menu: Menu) {
@ -463,15 +489,16 @@ open class BrowseSourceController(bundle: Bundle) :
resetProgressItem()
}
adapter.onLoadMoreComplete(mangas)
if (isControllerVisible) {
activityBinding?.appBar?.lockYPos = false
}
}
override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity)
if (BuildConfig.DEBUG && presenter.query.isBlank()) {
val searchItem =
(activity as? MainActivity)?.binding?.cardToolbar?.menu?.findItem(R.id.action_search)
val searchView = searchItem?.actionView as? SearchView ?: return
if (BuildConfig.DEBUG && isControllerVisible) {
val searchView = activityBinding?.cardToolbar?.searchView
setOnQueryTextChangeListener(searchView, onlyOnSubmit = true, hideKbOnSubmit = true) {
searchWithQuery(it ?: "")
true
@ -531,6 +558,9 @@ open class BrowseSourceController(bundle: Bundle) :
setAction(R.string.retry, retryAction)
}
}
if (isControllerVisible) {
activityBinding?.appBar?.lockYPos = false
}
}
private fun getErrorMessage(error: Throwable): String {
@ -581,13 +611,14 @@ open class BrowseSourceController(bundle: Bundle) :
/**
* Swaps the current display mode.
*/
fun swapDisplayMode() {
private fun swapDisplayMode() {
val view = view ?: return
val adapter = adapter ?: return
presenter.swapDisplayMode()
val isListMode = presenter.isListMode
activity?.invalidateOptionsMenu()
updateDisplayMenuItem(activityBinding?.toolbar?.menu, isListMode)
updateDisplayMenuItem(activityBinding?.cardToolbar?.menu, isListMode)
setupRecycler(view)
if (!isListMode || !view.context.connectivityManager.isActiveNetworkMetered) {
// Initialize mangas if going to grid view or if over wifi when going to list view

View file

@ -6,16 +6,17 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.appcompat.widget.SearchView
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.updatePaddingRelative
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.SourceGlobalSearchControllerBinding
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.main.MainActivity
@ -26,6 +27,7 @@ import eu.kanade.tachiyomi.util.addOrRemoveToFavorites
import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.toolbarHeight
import eu.kanade.tachiyomi.util.view.withFadeTransaction
@ -42,6 +44,7 @@ open class GlobalSearchController(
bundle: Bundle? = null
) : NucleusController<SourceGlobalSearchControllerBinding, GlobalSearchPresenter>(bundle),
FloatingSearchInterface,
SmallToolbarInterface,
GlobalSearchAdapter.OnTitleClickListener,
GlobalSearchCardAdapter.OnMangaClickListener {
@ -148,31 +151,38 @@ open class GlobalSearchController(
inflater.inflate(R.menu.catalogue_new_list, menu)
// Initialize search menu
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
activityBinding?.cardToolbar?.setQueryHint(view?.context?.getString(R.string.global_search), false)
activityBinding?.cardToolbar?.searchItem?.expandActionView()
activityBinding?.cardToolbar?.searchView?.setQuery(presenter.query, false)
searchItem.isVisible = customTitle == null
searchItem.setOnActionExpandListener(
object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
searchView.onActionViewExpanded() // Required to show the query in the view
searchView.setQuery(presenter.query, false)
return true
}
setOnQueryTextChangeListener(activityBinding?.cardToolbar?.searchView, onlyOnSubmit = true, hideKbOnSubmit = true) {
presenter.search(it ?: "")
setTitle() // Update toolbar title
true
}
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
return true
}
}
)
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (type.isEnter) {
val searchView = activityBinding?.cardToolbar?.searchView ?: return
val searchItem = activityBinding?.cardToolbar?.searchItem ?: return
searchItem.expandActionView()
searchView.setQuery(presenter.query, false)
}
}
searchView.queryTextChangeEvents()
.filter { it.isSubmitted }
.subscribeUntilDestroy {
presenter.search(it.queryText().toString())
searchItem.collapseActionView()
setTitle() // Update toolbar title
}
override fun onActionViewExpand(item: MenuItem?) {
val searchView = activityBinding?.cardToolbar?.searchView ?: return
searchView.setQuery(presenter.query, false)
}
override fun onActionViewCollapse(item: MenuItem?) {
if (activity is SearchActivity && extensionFilter != null) {
(activity as? SearchActivity)?.backPress()
} else if (customTitle == null) {
router.popCurrentController()
}
}
/**
@ -253,6 +263,7 @@ open class GlobalSearchController(
customTitle = null
setTitle()
activity?.invalidateOptionsMenu()
activityBinding?.appBar?.updateAppBarAfterY(binding.recycler)
}
}
adapter?.updateDataSet(searchResult)

View file

@ -37,6 +37,6 @@ class LatestUpdatesController(bundle: Bundle) : BrowseSourceController(bundle) {
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
menu.findItem(R.id.action_search).isVisible = false
menu.findItem(R.id.action_search)?.isVisible = false
}
}

View file

@ -1,12 +1,12 @@
package eu.kanade.tachiyomi.util.view
import android.Manifest
import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.graphics.Color
import android.os.Build
import android.os.Environment
@ -17,6 +17,7 @@ import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.inputmethod.InputMethodManager
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.appcompat.widget.SearchView
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
@ -26,8 +27,12 @@ import androidx.core.net.toUri
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type.ime
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.doOnLayout
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updatePaddingRelative
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.bluelinelabs.conductor.Controller
@ -37,33 +42,35 @@ import com.bluelinelabs.conductor.RouterTransaction
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.MainActivityBinding
import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.OneWayFadeChangeHandler
import eu.kanade.tachiyomi.ui.main.BottomSheetController
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.main.TabbedInterface
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.setting.SettingsController
import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.isTablet
import eu.kanade.tachiyomi.util.system.materialAlertDialog
import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
import eu.kanade.tachiyomi.util.system.toInt
import eu.kanade.tachiyomi.util.system.toast
import uy.kohesive.injekt.injectLazy
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.random.Random
fun Controller.setOnQueryTextChangeListener(
searchView: SearchView,
searchView: SearchView?,
onlyOnSubmit: Boolean = false,
hideKbOnSubmit: Boolean = true,
f: (text: String?) -> Boolean
) {
searchView.setOnQueryTextListener(
searchView?.setOnQueryTextListener(
object : SearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String?): Boolean {
if (!onlyOnSubmit && router.backstack.lastOrNull()
@ -75,7 +82,7 @@ fun Controller.setOnQueryTextChangeListener(
}
override fun onQueryTextSubmit(query: String?): Boolean {
if (router.backstack.lastOrNull()?.controller == this@setOnQueryTextChangeListener) {
if (isControllerVisible) {
if (hideKbOnSubmit) {
val imm =
activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
@ -105,16 +112,13 @@ fun Controller.removeQueryListener() {
fun Controller.liftAppbarWith(recycler: RecyclerView, padView: Boolean = false) {
if (padView) {
val attrsArray = intArrayOf(R.attr.mainActionBarSize)
val array = recycler.context.obtainStyledAttributes(attrsArray)
var appBarHeight = (
if (toolbarHeight ?: 0 > 0) toolbarHeight!!
else array.getDimensionPixelSize(0, 0)
if (fullAppBarHeight ?: 0 > 0) fullAppBarHeight!!
else activityBinding?.appBar?.attrToolbarHeight ?: 0
)
array.recycle()
activityBinding!!.toolbar.post {
if (toolbarHeight!! > 0) {
appBarHeight = toolbarHeight!!
if (fullAppBarHeight!! > 0) {
appBarHeight = fullAppBarHeight!!
recycler.requestApplyInsets()
}
}
@ -136,13 +140,14 @@ fun Controller.liftAppbarWith(recycler: RecyclerView, padView: Boolean = false)
}
}
} else {
view?.applyWindowInsetsForController()
view?.applyWindowInsetsForController(activityBinding?.appBar?.attrToolbarHeight ?: 0)
recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
}
var toolbarColorAnim: ValueAnimator? = null
var isToolbarColored = false
val preferences: PreferencesHelper by injectLazy()
val colorToolbar: (Boolean) -> Unit = f@{ isColored ->
isToolbarColored = isColored
toolbarColorAnim?.cancel()
@ -169,7 +174,13 @@ fun Controller.liftAppbarWith(recycler: RecyclerView, padView: Boolean = false)
if (floatingBar) {
setAppBarBG(0f)
}
colorToolbar(recycler.canScrollVertically(-1))
activityBinding?.appBar?.setToolbarModeBy(this)
activityBinding?.appBar?.hideBigView(true)
activityBinding?.appBar?.y = 0f
activityBinding?.appBar?.updateAppBarAfterY(recycler)
setAppBarBG(0f)
recycler.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
@ -194,22 +205,23 @@ fun Controller.scrollViewWith(
liftOnScroll: ((Boolean) -> Unit)? = null,
onLeavingController: (() -> Unit)? = null,
onBottomNavUpdate: (() -> Unit)? = null,
includeTabView: Boolean = false
): ((Boolean) -> Unit) {
var statusBarHeight = -1
val tabBarHeight = 48.dpToPx
activityBinding?.appBar?.lockYPos = false
activityBinding?.appBar?.y = 0f
val attrsArray = intArrayOf(R.attr.mainActionBarSize)
val array = recycler.context.obtainStyledAttributes(attrsArray)
val includeTabView = this is TabbedInterface
activityBinding?.appBar?.useTabsInPreLayout = includeTabView
activityBinding?.appBar?.setToolbarModeBy(this@scrollViewWith)
var appBarHeight = (
if (toolbarHeight ?: 0 > 0) toolbarHeight!!
else array.getDimensionPixelSize(0, 0)
) + if (includeTabView) tabBarHeight else 0
array.recycle()
if (fullAppBarHeight ?: 0 > 0) fullAppBarHeight!!
else activityBinding?.appBar?.preLayoutHeight ?: 0
)
swipeRefreshLayout?.setDistanceToTriggerSync(150.dpToPx)
activityBinding!!.toolbar.post {
if (toolbarHeight!! > 0) {
appBarHeight = toolbarHeight!! + if (includeTabView) tabBarHeight else 0
val swipeCircle = swipeRefreshLayout?.findChild<ImageView>()
activityBinding!!.appBar.doOnLayout {
if (fullAppBarHeight!! > 0) {
appBarHeight = fullAppBarHeight!!
recycler.requestApplyInsets()
}
}
@ -220,9 +232,13 @@ fun Controller.scrollViewWith(
recycler.post {
updateViewsNearBottom()
}
setItemAnimatorForAppBar(recycler)
val randomTag = Random.nextLong()
var lastY = 0f
var fakeToolbarView: View? = null
val preferences: PreferencesHelper by injectLazy()
var fakeBottomNavView: View? = null
if (!customPadding) {
recycler.updatePaddingRelative(
@ -232,7 +248,17 @@ fun Controller.scrollViewWith(
) + appBarHeight
)
}
val atTopOfRecyclerView: () -> Boolean = f@{
if (this is SmallToolbarInterface || activityBinding?.appBar?.useLargeToolbar == false) {
return@f !recycler.canScrollVertically(-1)
}
val activityBinding = activityBinding ?: return@f true
return@f recycler.computeVerticalScrollOffset() - recycler.paddingTop <=
0 - activityBinding.appBar.paddingTop -
activityBinding.toolbar.height - if (includeTabView) tabBarHeight else 0
}
recycler.doOnApplyWindowInsetsCompat { view, insets, _ ->
appBarHeight = fullAppBarHeight!!
val headerHeight = insets.getInsets(systemBars()).top + appBarHeight
if (!customPadding) view.updatePaddingRelative(
top = headerHeight,
@ -250,7 +276,6 @@ fun Controller.scrollViewWith(
var toolbarColorAnim: ValueAnimator? = null
var isToolbarColor = false
var isInView = true
val preferences: PreferencesHelper by injectLazy()
val colorToolbar: (Boolean) -> Unit = f@{ isColored ->
isToolbarColor = isColored
if (liftOnScroll != null) {
@ -260,7 +285,7 @@ fun Controller.scrollViewWith(
val floatingBar =
(this as? FloatingSearchInterface)?.showFloatingBar() == true && !includeTabView
if (floatingBar) {
setAppBarBG(0f, includeTabView)
setAppBarBG(isColored.toInt().toFloat(), includeTabView)
return@f
}
val percent = ImageUtil.getPercentOfColor(
@ -288,6 +313,12 @@ fun Controller.scrollViewWith(
super.onChangeStart(controller, changeHandler, changeType)
isInView = changeType.isEnter
if (changeType.isEnter) {
activityBinding?.appBar?.hideBigView(
this@scrollViewWith is SmallToolbarInterface,
setTitleAlpha = this@scrollViewWith !is MangaDetailsController
)
activityBinding?.appBar?.setToolbarModeBy(this@scrollViewWith)
activityBinding?.appBar?.useTabsInPreLayout = includeTabView
colorToolbar(isToolbarColor)
if (fakeToolbarView?.parent != null) {
val parent = fakeToolbarView?.parent as? ViewGroup ?: return
@ -300,13 +331,10 @@ fun Controller.scrollViewWith(
fakeBottomNavView = null
}
lastY = 0f
activityBinding!!.appBar.updateAppBarAfterY(recycler)
activityBinding!!.toolbar.tag = randomTag
activityBinding!!.toolbar.setOnClickListener {
if ((this@scrollViewWith as? BottomSheetController)?.sheetIsFullscreen() != true) {
recycler.smoothScrollToTop()
} else {
(this@scrollViewWith as? BottomSheetController)?.toggleSheet()
}
recycler.smoothScrollToTop()
}
} else {
if (!customPadding && lastY == 0f && (
@ -349,18 +377,17 @@ fun Controller.scrollViewWith(
}
}
)
colorToolbar(recycler.canScrollVertically(-1))
colorToolbar(!atTopOfRecyclerView())
recycler.post {
colorToolbar(recycler.canScrollVertically(-1))
activityBinding!!.appBar.updateAppBarAfterY(recycler)
colorToolbar(!atTopOfRecyclerView())
}
val isTablet = recycler.context.isTablet() && recycler.context.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE
recycler.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (router?.backstack?.lastOrNull()
?.controller == this@scrollViewWith && statusBarHeight > -1 &&
if (isControllerVisible && statusBarHeight > -1 &&
(this@scrollViewWith as? BaseController<*>)?.isDragging != true &&
activity != null && activityBinding!!.appBar.height > 0 &&
recycler.translationY == 0f
@ -369,9 +396,8 @@ fun Controller.scrollViewWith(
val shortAnimationDuration = resources?.getInteger(
android.R.integer.config_shortAnimTime
) ?: 0
activityBinding!!.appBar.animate().y(0f)
.setDuration(shortAnimationDuration.toLong())
.start()
activityBinding!!.appBar.y = 0f
activityBinding!!.appBar.updateAppBarAfterY(recycler)
if (router.backstackSize == 1 && isInView) {
activityBinding!!.bottomNav?.let {
val animator = it.animate()?.translationY(0f)
@ -385,46 +411,43 @@ fun Controller.scrollViewWith(
lastY = 0f
if (isToolbarColor) colorToolbar(false)
} else {
if (!isTablet) {
activityBinding!!.appBar.y -= dy
activityBinding!!.appBar.y = MathUtils.clamp(
activityBinding!!.appBar.y,
-activityBinding!!.appBar.height.toFloat(),
0f
)
activityBinding!!.bottomNav?.let { bottomNav ->
if (bottomNav.isVisible && isInView) {
if (preferences.hideBottomNavOnScroll().get()) {
bottomNav.translationY += dy
bottomNav.translationY = MathUtils.clamp(
bottomNav.translationY,
0f,
bottomNav.height.toFloat()
)
updateViewsNearBottom()
} else if (bottomNav.translationY != 0f) {
bottomNav.translationY = 0f
activityBinding!!.bottomView?.translationY = 0f
}
activityBinding!!.appBar.y -= dy
activityBinding!!.appBar.updateAppBarAfterY(recycler)
activityBinding!!.bottomNav?.let { bottomNav ->
if (bottomNav.isVisible && isInView) {
if (preferences.hideBottomNavOnScroll().get()) {
bottomNav.translationY += dy
bottomNav.translationY = MathUtils.clamp(
bottomNav.translationY,
0f,
bottomNav.height.toFloat()
)
updateViewsNearBottom()
} else if (bottomNav.translationY != 0f) {
bottomNav.translationY = 0f
activityBinding!!.bottomView?.translationY = 0f
}
}
if (!isToolbarColor && (
dy == 0 ||
(
activityBinding!!.appBar.y <= -activityBinding!!.appBar.height.toFloat() ||
dy == 0 && activityBinding!!.appBar.y == 0f
)
)
) {
colorToolbar(true)
}
} else {
val notAtTop = recycler.canScrollVertically(-1)
if (notAtTop != isToolbarColor) colorToolbar(notAtTop)
}
if (!isToolbarColor && (
dy == 0 ||
(
activityBinding!!.appBar.y <= -activityBinding!!.appBar.height.toFloat() ||
dy == 0 && activityBinding!!.appBar.y == 0f
)
)
) {
colorToolbar(true)
}
val notAtTop = !atTopOfRecyclerView()
if (notAtTop != isToolbarColor) colorToolbar(notAtTop)
lastY = activityBinding!!.appBar.y
}
swipeCircle?.translationY = max(
activityBinding!!.appBar.y,
-activityBinding!!.appBar.height + activityBinding!!.appBar.paddingTop.toFloat()
)
}
}
@ -433,9 +456,6 @@ fun Controller.scrollViewWith(
if (newState == RecyclerView.SCROLL_STATE_IDLE &&
(this@scrollViewWith as? BaseController<*>)?.isDragging != true
) {
if (isTablet) {
return
}
if (router?.backstack?.lastOrNull()
?.controller == this@scrollViewWith && statusBarHeight > -1 &&
activity != null && activityBinding!!.appBar.height > 0 &&
@ -453,10 +473,12 @@ fun Controller.scrollViewWith(
if (activityBinding!!.bottomNav?.isVisible == true &&
preferences.hideBottomNavOnScroll().get()
) closerToBottom else closerToTop
lastY =
if (closerToEdge && !atTop) (-activityBinding!!.appBar.height.toFloat()) else 0f
activityBinding!!.appBar.animate().y(lastY)
.setDuration(shortAnimationDuration.toLong()).start()
lastY = activityBinding!!.appBar.snapAppBarY(recycler) {
swipeCircle?.translationY = max(
activityBinding!!.appBar.y,
-activityBinding!!.appBar.height + activityBinding!!.appBar.paddingTop.toFloat()
)
}
if (activityBinding!!.bottomNav?.isVisible == true &&
isInView && preferences.hideBottomNavOnScroll().get()
) {
@ -471,8 +493,8 @@ fun Controller.scrollViewWith(
animator?.start()
}
}
if (recycler.canScrollVertically(-1) && !isToolbarColor) colorToolbar(true)
else if (!recycler.canScrollVertically(-1) && isToolbarColor) colorToolbar(false)
val notAtTop = !atTopOfRecyclerView()
if (notAtTop != isToolbarColor) colorToolbar(notAtTop)
}
} else if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
val view = activity?.window?.currentFocus ?: return
@ -487,16 +509,112 @@ fun Controller.scrollViewWith(
return colorToolbar
}
fun Controller.setItemAnimatorForAppBar(recycler: RecyclerView) {
var itemAppBarAnimator: Animator? = null
fun animateAppBar() {
if (this !is SmallToolbarInterface) {
itemAppBarAnimator?.cancel()
val duration = (recycler.itemAnimator?.changeDuration ?: 250) * 2
itemAppBarAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
addUpdateListener {
activityBinding?.appBar?.updateAppBarAfterY(recycler)
}
}
itemAppBarAnimator?.duration = duration
itemAppBarAnimator?.start()
}
}
recycler.itemAnimator = object : DefaultItemAnimator() {
override fun animateMove(
holder: RecyclerView.ViewHolder?,
fromX: Int,
fromY: Int,
toX: Int,
toY: Int
): Boolean {
animateAppBar()
return super.animateMove(holder, fromX, fromY, toX, toY)
}
override fun onAnimationFinished(viewHolder: RecyclerView.ViewHolder) {
activityBinding?.appBar?.updateAppBarAfterY(recycler)
super.onAnimationFinished(viewHolder)
}
override fun animateChange(
oldHolder: RecyclerView.ViewHolder,
newHolder: RecyclerView.ViewHolder,
preInfo: ItemHolderInfo,
postInfo: ItemHolderInfo
): Boolean {
animateAppBar()
return super.animateChange(oldHolder, newHolder, preInfo, postInfo)
}
override fun animateChange(
oldHolder: RecyclerView.ViewHolder?,
newHolder: RecyclerView.ViewHolder?,
fromX: Int,
fromY: Int,
toX: Int,
toY: Int
): Boolean {
animateAppBar()
return super.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY)
}
}
}
val Controller.mainRecyclerView: RecyclerView?
get() = (this as? SettingsController)?.listView ?: (this as? BaseController<*>)?.mainRecycler
fun Controller.moveRecyclerViewUp(allTheWayUp: Boolean = false, scrollUpAnyway: Boolean = false) {
if (activityBinding?.bigToolbar?.isVisible == false) return
val recycler = mainRecyclerView ?: return
val activityBinding = activityBinding ?: return
val appBarOffset = activityBinding.appBar.toolbarDistanceToTop
if (allTheWayUp && recycler.computeVerticalScrollOffset() - recycler.paddingTop <= fullAppBarHeight ?: activityBinding.appBar.preLayoutHeight) {
(recycler.layoutManager as? LinearLayoutManager)?.scrollToPosition(0)
recycler.post {
activityBinding.appBar.updateAppBarAfterY(recycler)
activityBinding.appBar.useSearchToolbarForMenu(false)
}
return
}
if (scrollUpAnyway || recycler.computeVerticalScrollOffset() - recycler.paddingTop <= 0 - appBarOffset) {
(recycler.layoutManager as? LinearLayoutManager)
?.scrollToPositionWithOffset(0, activityBinding.appBar.yNeededForSmallToolbar)
recycler.post {
activityBinding.appBar.updateAppBarAfterY(recycler)
activityBinding.appBar.useSearchToolbarForMenu(recycler.computeVerticalScrollOffset() != 0)
}
}
}
fun Controller.setAppBarBG(value: Float, includeTabView: Boolean = false) {
val context = view?.context ?: return
val floatingBar =
(this as? FloatingSearchInterface)?.showFloatingBar() == true && !includeTabView
if ((this as? BottomSheetController)?.sheetIsFullscreen() == true) return
if (router.backstack.lastOrNull()?.controller != this) return
if (!isControllerVisible) return
if (floatingBar) {
(activityBinding?.cardView as? CardView)?.setCardBackgroundColor(context.getResourceColor(R.attr.colorPrimaryVariant))
activityBinding?.appBar?.setBackgroundColor(Color.TRANSPARENT)
activity?.window?.statusBarColor = context.getResourceColor(android.R.attr.statusBarColor)
if (this !is SmallToolbarInterface && activityBinding?.appBar?.useLargeToolbar == true) {
val colorSurface = context.getResourceColor(R.attr.colorSurface)
val color = ColorUtils.blendARGB(
colorSurface,
ColorUtils.setAlphaComponent(colorSurface, 0),
value
)
activityBinding?.appBar?.backgroundColor = color
} else {
activityBinding?.appBar?.backgroundColor = Color.TRANSPARENT
}
if (activityBinding?.appBar?.isInvisible != true) {
activity?.window?.statusBarColor =
context.getResourceColor(android.R.attr.statusBarColor)
}
} else {
val color = ColorUtils.blendARGB(
context.getResourceColor(R.attr.colorSurface),
@ -504,8 +622,10 @@ fun Controller.setAppBarBG(value: Float, includeTabView: Boolean = false) {
value
)
activityBinding?.appBar?.setBackgroundColor(color)
activity?.window?.statusBarColor =
ColorUtils.setAlphaComponent(color, (0.87f * 255).roundToInt())
if (activityBinding?.appBar?.isInvisible != true) {
activity?.window?.statusBarColor =
ColorUtils.setAlphaComponent(color, (0.87f * 255).roundToInt())
}
if ((this as? FloatingSearchInterface)?.showFloatingBar() == true) {
val invColor = ColorUtils.blendARGB(
context.getResourceColor(R.attr.colorSurface),
@ -590,3 +710,14 @@ val Controller.activityBinding: MainActivityBinding?
val Controller.toolbarHeight: Int?
get() = (activity as? MainActivity)?.toolbarHeight
/** Returns the expected height of the app bar - top insets, based on what the controller needs */
val Controller.fullAppBarHeight: Int?
get() = (activity as? MainActivity)?.bigToolbarHeight(
(this as? FloatingSearchInterface)?.showFloatingBar() == true,
this is TabbedInterface,
this !is SmallToolbarInterface
)
val Controller.isControllerVisible: Boolean
get() = router.backstack.lastOrNull()?.controller == this

View file

@ -17,6 +17,7 @@ import android.graphics.Point
import android.graphics.RenderEffect
import android.graphics.Shader
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.os.PowerManager
import android.view.Gravity
@ -44,6 +45,7 @@ import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type.ime
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.children
import androidx.core.view.descendants
import androidx.core.view.forEach
import androidx.core.view.marginBottom
@ -74,6 +76,7 @@ import eu.kanade.tachiyomi.util.system.pxToDp
import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.roundToInt
@ -194,13 +197,10 @@ fun View.applyBottomAnimatedInsets(
)
}
object ControllerViewWindowInsetsListener : OnApplyWindowInsetsListener {
class ControllerViewWindowInsetsListener(private val topHeight: Int) : OnApplyWindowInsetsListener {
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
v.updateLayoutParams<FrameLayout.LayoutParams> {
val attrsArray = intArrayOf(R.attr.mainActionBarSize)
val array = v.context.obtainStyledAttributes(attrsArray)
topMargin = insets.getInsets(systemBars()).top + array.getDimensionPixelSize(0, 0)
array.recycle()
topMargin = insets.getInsets(systemBars()).top + topHeight
}
return insets
}
@ -226,8 +226,8 @@ fun View.doOnApplyWindowInsetsCompat(f: (View, WindowInsetsCompat, ViewPaddingSt
requestApplyInsetsWhenAttached()
}
fun View.applyWindowInsetsForController() {
ViewCompat.setOnApplyWindowInsetsListener(this, ControllerViewWindowInsetsListener)
fun View.applyWindowInsetsForController(topHeight: Int) {
ViewCompat.setOnApplyWindowInsetsListener(this, ControllerViewWindowInsetsListener(topHeight))
requestApplyInsetsWhenAttached()
}
@ -422,8 +422,18 @@ fun setCards(
mainCard.strokeWidth = if (showOutline) 1.dpToPx else 0
}
val View.backgroundColor
var View.backgroundColor: Int?
get() = (background as? ColorDrawable)?.color
set(value) {
if (value != null) setBackgroundColor(value) else background = null
}
/**
* Returns this ViewGroup's first descendant of specified class
*/
inline fun <reified T> ViewGroup.findChild(): T? {
return children.find { it is T } as? T
}
/**
* Returns this ViewGroup's first descendant of specified class
@ -493,6 +503,23 @@ fun Dialog.blurBehindWindow(
}
}
fun TextView.setTextColorAlpha(alpha: Int) {
setTextColor(ColorUtils.setAlphaComponent(currentTextColor, alpha))
}
fun View.updateGradiantBGRadius(ogRadius: Float, deviceRadius: Float, progress: Float, vararg updateOtherViews: View) {
(background as? GradientDrawable)?.let { drawable ->
val lerp = min(ogRadius, deviceRadius) * (1 - progress) +
max(ogRadius, deviceRadius) * progress
drawable.shape = GradientDrawable.RECTANGLE
drawable.cornerRadii = floatArrayOf(lerp, lerp, lerp, lerp, 0f, 0f, 0f, 0f)
background = drawable
updateOtherViews.forEach {
it.background = drawable
}
}
}
@RequiresApi(31)
fun View.animateBlur(
@FloatRange(from = 0.1) from: Float,

View file

@ -4,7 +4,7 @@ import android.content.Context
import android.util.AttributeSet
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.system.pxToDp
import kotlin.math.max
@ -14,7 +14,7 @@ import kotlin.math.roundToInt
class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
androidx.recyclerview.widget.RecyclerView(context, attrs) {
val manager = GridLayoutManager(context, 1)
val manager = GridLayoutManagerAccurateOffset(context, 1)
var lastMeasuredWidth = 0
var columnWidth = -1f

View file

@ -3,10 +3,13 @@ package eu.kanade.tachiyomi.widget
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import com.google.android.material.button.MaterialButton
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.CommonViewEmptyBinding
@ -43,6 +46,7 @@ class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
binding.textLabel.text = message
binding.actionsContainer.removeAllViews()
binding.actionsContainer.isVisible = !actions.isNullOrEmpty()
if (!actions.isNullOrEmpty()) {
actions.forEach {
val button =
@ -51,8 +55,14 @@ class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
setText(it.resId)
setOnClickListener(it.listener)
}
binding.actionsContainer.addView(button)
if (context.resources.configuration.screenHeightDp < 600) {
button.textAlignment = View.TEXT_ALIGNMENT_TEXT_START
button.updateLayoutParams<MarginLayoutParams> {
width = ViewGroup.LayoutParams.WRAP_CONTENT
height = ViewGroup.LayoutParams.WRAP_CONTENT
}
}
}
}

View file

@ -0,0 +1,67 @@
package eu.kanade.tachiyomi.widget
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.dpToPx
import kotlin.math.min
import kotlin.math.roundToInt
object EstimatedItemHeight {
/** gives height of a view holder, or an estimated based on others, or a hard coded estimate */
fun itemOrEstimatedHeight(
pos: Int,
itemViewType: Int?,
childSizesMap: HashMap<Int, Int>,
childTypeMap: HashMap<Int, Int>,
childTypeHeightMap: HashMap<Int, HashMap<Int, Int>>,
childTypeEstimateMap: HashMap<Int, Int>,
childAvgHeightMap: HashMap<Int, Int>
): Int {
return if (childSizesMap[pos] != null) {
childSizesMap[pos] ?: 0
} else {
val type = if (childTypeMap[pos] == null) {
val t = itemViewType ?: 0
childTypeMap[pos] = t
t
} else {
childTypeMap[pos] ?: 0
}
when {
childTypeEstimateMap[type] != null -> childTypeEstimateMap[type] ?: 0
childAvgHeightMap[type] == null && !childTypeHeightMap[type]?.values.isNullOrEmpty() -> {
val array = (childTypeHeightMap[type]?.values ?: mutableListOf(0)).toIntArray()
childAvgHeightMap[type] = array
.copyOfRange(0, min(array.size, 10))
.average()
.roundToInt()
if (array.size >= 10) {
childTypeEstimateMap[type] = childAvgHeightMap[type]!!
}
childAvgHeightMap[type] ?: 0
}
else -> childAvgHeightMap[type] ?: estimatedHeight(type)
}
}
}
/**
* Used for estimates of heights of recycler view holders
*
* Only needed to provide in cases where a layout type only shows up when scroll
* (for example: R.layout.chapters_item might not show until scrolling if
* R.layout.manga_header_item is too tall
*/
private fun estimatedHeight(id: Int): Int {
return when (id) {
R.layout.recent_manga_item -> 92.dpToPx
R.layout.recents_header_item -> 40.dpToPx
R.layout.recent_chapters_section_item -> 32.dpToPx
R.layout.chapters_item -> 60.dpToPx
R.layout.manga_header_item -> 500.dpToPx
R.layout.chapter_header_item -> 47.dpToPx
R.layout.manga_grid_item -> 222.dpToPx
R.layout.manga_list_item -> 52.dpToPx
else -> 0
}
}
}

View file

@ -0,0 +1,143 @@
package eu.kanade.tachiyomi.widget
import android.content.Context
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import kotlin.math.max
class GridLayoutManagerAccurateOffset(context: Context?, spanCount: Int) : GridLayoutManager(context, spanCount) {
// map of child adapter position to its height.
private val childSizesMap = HashMap<Int, Int>()
private val childSpanMap = HashMap<Int, Int>()
private val childTypeHeightMap = HashMap<Int, HashMap<Int, Int>>()
private val childTypeMap = HashMap<Int, Int>()
private val childTypeEstimateMap = HashMap<Int, Int>()
var computedRange: Int? = null
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 onLayoutCompleted(state: RecyclerView.State) {
super.onLayoutCompleted(state)
computedRange = null
for (i in 0 until childCount) {
val child = getChildAt(i) ?: return
val position = getPosition(child)
childSizesMap[position] = child.height
childSpanMap[position] = spanSizeLookup.getSpanSize(getPosition(child))
val type = getItemViewType(child)
childTypeMap[position] = type
if (childTypeHeightMap[type] != null) {
childTypeHeightMap[type]!![position] = child.height
} else {
childTypeHeightMap[type] = hashMapOf(position to child.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 computeVerticalScrollRange(state: RecyclerView.State): Int {
if (childCount == 0) return 0
computedRange?.let {
return it
}
rView ?: return super.computeVerticalScrollRange(state)
var scrolledY = 0
var spanC = 0
var maxHeight = 0
val childAvgHeightMap = HashMap<Int, Int>()
for (i in 0 until itemCount) {
val height: Int = getItemHeight(i, childAvgHeightMap)
val spanCurrentSize = childSpanMap[i] ?: spanSizeLookup.getSpanSize(i)
if (spanCount <= spanCurrentSize) {
scrolledY += height
scrolledY += maxHeight
maxHeight = 0
spanC = 0
} else if (spanCurrentSize == 1) {
maxHeight = max(maxHeight, height)
spanC++
if (spanC <= spanCount) {
scrolledY += maxHeight
maxHeight = 0
spanC = 0
}
}
}
computedRange = scrolledY
return scrolledY
}
override fun computeVerticalScrollOffset(state: RecyclerView.State): Int {
if (childCount == 0) {
return 0
}
rView ?: return super.computeVerticalScrollOffset(state)
val firstChild = getChildAt(0) ?: return 0
val firstChildPosition = (0 until childCount)
.mapNotNull { getChildAt(it) }
.mapNotNull { pos -> getPosition(pos).takeIf { it != RecyclerView.NO_POSITION } }
.minOrNull() ?: 0
var scrolledY: Int = -firstChild.y.toInt()
var spanC = 0
var maxHeight = 0
val childAvgHeightMap = HashMap<Int, Int>()
for (i in 0 until firstChildPosition) {
val height: Int = getItemHeight(i, childAvgHeightMap)
val spanCurrentSize = childSpanMap[i] ?: spanSizeLookup.getSpanSize(i)
if (spanCount <= spanCurrentSize) {
scrolledY += height
scrolledY += maxHeight
maxHeight = 0
spanC = 0
} else if (spanCurrentSize == 1) {
maxHeight = max(maxHeight, height)
spanC++
if (spanC <= spanCount) {
scrolledY += maxHeight
maxHeight = 0
spanC = 0
}
}
}
scrolledY += maxHeight
return scrolledY + paddingTop
}
private fun getItemHeight(pos: Int, childAvgHeightMap: HashMap<Int, Int>): Int {
return EstimatedItemHeight.itemOrEstimatedHeight(
pos,
rView?.adapter?.getItemViewType(pos),
childSizesMap,
childTypeMap,
childTypeHeightMap,
childTypeEstimateMap,
childAvgHeightMap
)
}
override fun findFirstVisibleItemPosition(): Int {
return getFirstPos(rView, toolbarHeight)
}
override fun findFirstCompletelyVisibleItemPosition(): Int {
return getFirstCompletePos(rView, toolbarHeight)
}
}

View file

@ -0,0 +1,124 @@
package eu.kanade.tachiyomi.widget
import android.content.Context
import android.widget.TextView
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.marginTop
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
class LinearLayoutManagerAccurateOffset(context: Context?) : LinearLayoutManager(context) {
// map of child adapter position to its height.
private val childSizesMap = HashMap<Int, Int>()
private val childTypeMap = HashMap<Int, Int>()
private val childTypeHeightMap = HashMap<Int, HashMap<Int, Int>>()
private val childTypeEstimateMap = HashMap<Int, Int>()
var rView: RecyclerView? = null
var computedRange: Int? = 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 onLayoutCompleted(state: RecyclerView.State) {
super.onLayoutCompleted(state)
computedRange = null
for (i in 0 until childCount) {
val child = getChildAt(i) ?: return
val position = getPosition(child)
childSizesMap[position] = child.height
val type = getItemViewType(child)
childTypeMap[position] = type
if (childTypeHeightMap[type] != null) {
childTypeHeightMap[type]!![position] = child.height
} else {
childTypeHeightMap[type] = hashMapOf(position to child.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 computeVerticalScrollRange(state: RecyclerView.State): Int {
if (childCount == 0) return 0
computedRange?.let { return it }
val childAvgHeightMap = HashMap<Int, Int>()
val computedRange = (0 until itemCount).sumOf { getItemHeight(it, childAvgHeightMap) }
this.computedRange = computedRange
return computedRange
}
override fun computeVerticalScrollOffset(state: RecyclerView.State): Int {
if (childCount == 0) return 0
val firstChild = getChildAt(0) ?: return 0
val firstChildPosition = (0 to childCount).toList()
.mapNotNull { getChildAt(it) }
.mapNotNull { pos -> getPosition(pos).takeIf { it != RecyclerView.NO_POSITION } }
.minOrNull() ?: 0
val childAvgHeightMap = HashMap<Int, Int>()
val scrolledY: Int = -firstChild.y.toInt() +
(0 until firstChildPosition).sumOf { getItemHeight(it, childAvgHeightMap) }
return scrolledY + paddingTop
}
private fun getItemHeight(pos: Int, childAvgHeightMap: HashMap<Int, Int>): Int {
return EstimatedItemHeight.itemOrEstimatedHeight(
pos,
rView?.adapter?.getItemViewType(pos),
childSizesMap,
childTypeMap,
childTypeHeightMap,
childTypeEstimateMap,
childAvgHeightMap
)
}
override fun findFirstVisibleItemPosition(): Int {
return getFirstPos(rView, toolbarHeight)
}
override fun findFirstCompletelyVisibleItemPosition(): Int {
return getFirstCompletePos(rView, toolbarHeight)
}
}
fun RecyclerView.LayoutManager.getFirstPos(recyclerView: RecyclerView?, toolbarHeight: Int): Int {
val inset = recyclerView?.rootWindowInsetsCompat?.getInsets(systemBars())?.top ?: 0
return (0 until childCount)
.mapNotNull { getChildAt(it) }
.filter {
val isLibraryHeader = getItemViewType(it) == R.layout.library_category_header_item
val marginTop = if (isLibraryHeader) it.findViewById<TextView>(R.id.category_title)?.marginTop ?: 0 else 0
it.bottom >= inset + toolbarHeight - marginTop
}
.mapNotNull { pos -> getPosition(pos).takeIf { it != RecyclerView.NO_POSITION } }
.minOrNull() ?: RecyclerView.NO_POSITION
}
fun RecyclerView.LayoutManager.getFirstCompletePos(recyclerView: RecyclerView?, toolbarHeight: Int): Int {
val inset = recyclerView?.rootWindowInsetsCompat?.getInsets(systemBars())?.top ?: 0
return (0 until childCount)
.mapNotNull { getChildAt(it) }
.filter {
val isLibraryHeader = getItemViewType(it) == R.layout.library_category_header_item
val marginTop = if (isLibraryHeader) it.findViewById<TextView>(R.id.category_title)?.marginTop ?: 0 else 0
it.y >= inset + toolbarHeight - marginTop
}
.mapNotNull { pos -> getPosition(pos).takeIf { it != RecyclerView.NO_POSITION } }
.minOrNull() ?: RecyclerView.NO_POSITION
}

View file

@ -0,0 +1,22 @@
package eu.kanade.tachiyomi.widget.preference
import android.content.Context
import androidx.core.view.updateLayoutParams
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceViewHolder
import androidx.recyclerview.widget.RecyclerView
/**
* PreferenceCategory that hides the title placeholder layout if the title is unset
*/
class AdaptiveTitlePreferenceCategory(context: Context) : PreferenceCategory(context) {
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
if (title.isNullOrBlank()) {
holder.itemView.updateLayoutParams<RecyclerView.LayoutParams> {
height = 0
topMargin = 0
}
}
}
}

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toTopOf="@id/text_label"
app:layout_constraintDimensionRatio="h,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_max="128dp"
app:layout_constraintHeight_min="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintWidth_max="128dp"
app:layout_constraintWidth_min="24dp"
tools:src="@drawable/ic_file_download_24dp" />
<TextView
android:id="@+id/text_label"
style="?textAppearanceLabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@id/actions_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_view"
tools:text="Label" />
<LinearLayout
android:id="@+id/actions_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_label" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -20,7 +20,7 @@
</com.bluelinelabs.conductor.ChangeHandlerFrameLayout>
<com.google.android.material.appbar.AppBarLayout
<eu.kanade.tachiyomi.ui.base.ExpandedAppBarLayout
android:id="@+id/app_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -35,7 +35,9 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
app:titleTextColor="?actionBarTintColor"
android:layout_height="?attr/mainActionBarSize"
android:layout_height="?mainActionBarSize"
android:translationZ="5dp"
android:outlineProvider="none"
app:collapseIcon="@drawable/ic_arrow_back_24dp"
android:background="@android:color/transparent">
@ -55,10 +57,49 @@
tools:text="Title Text" />
</eu.kanade.tachiyomi.ui.base.CenteredToolbar>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/big_toolbar"
android:layout_width="match_parent"
android:paddingBottom="12dp"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/big_icon"
android:visibility="gone"
tools:visibility="visible"
android:layout_width="52dp"
android:layout_height="52dp"
tools:srcCompat="@mipmap/ic_launcher"
app:layout_constraintBottom_toBottomOf="@id/big_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/big_title"
android:layout_marginStart="16dp"
/>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/big_title"
android:paddingStart="16dp"
android:paddingEnd="12dp"
style="?textAppearanceHeadlineLarge"
android:layout_width="0dp"
android:maxLines="2"
android:layout_marginTop="52dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/big_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:drawableTint="?actionBarTintColor"
android:ellipsize="end"
android:layout_gravity="start"
android:textColor="?actionBarTintColor"
tools:text="Title Text" />
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/card_frame"
android:layout_width="match_parent"
android:layout_height="?attr/mainActionBarSize" >
android:layout_height="?mainActionBarSize" >
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_view"
@ -66,10 +107,10 @@
android:layout_marginTop="4dp"
app:cardBackgroundColor="?colorPrimaryVariant"
android:layout_marginBottom="4dp"
android:layout_marginStart="10dp"
app:strokeWidth="0dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
app:cardCornerRadius="30dp"
app:cardCornerRadius="24dp"
android:layout_height="match_parent" >
<eu.kanade.tachiyomi.ui.base.FloatingToolbar
@ -162,7 +203,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:tabGravity="fill"/>
</FrameLayout>
</com.google.android.material.appbar.AppBarLayout>
</eu.kanade.tachiyomi.ui.base.ExpandedAppBarLayout>
<com.google.android.material.navigationrail.NavigationRailView

View file

@ -18,9 +18,10 @@
android:id="@+id/progress"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:visibility="gone"/>
android:visibility="gone"
tools:visibility="visible"/>
</FrameLayout>
@ -32,9 +33,11 @@
<eu.kanade.tachiyomi.widget.EmptyView
android:id="@+id/empty_view"
tools:paddingTop="300dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
tools:visibility="visible"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"

View file

@ -3,23 +3,23 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toTopOf="@id/text_label"
app:layout_constraintDimensionRatio="h,1:1"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintDimensionRatio="W,1:1"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_max="128dp"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintHeight_min="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/text_label"
app:layout_constraintWidth_max="128dp"
app:layout_constraintWidth_min="24dp"
tools:src="@drawable/ic_file_download_24dp" />
@ -29,23 +29,26 @@
style="?textAppearanceLabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:gravity="center"
android:layout_marginEnd="16dp"
android:paddingStart="4dp"
android:paddingEnd="0dp"
app:layout_constrainedWidth="true"
android:textAlignment="textStart"
app:layout_constraintBottom_toTopOf="@id/actions_container"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/image_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_view"
tools:text="Label" />
tools:text="Label Label Label Label Label Label Label Label Label Label" />
<LinearLayout
android:id="@+id/actions_container"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:gravity="start"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/image_view"
app:layout_constraintTop_toBottomOf="@id/text_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_label" />
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -12,7 +12,7 @@
app:behavior_peekHeight="48sp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/sheet_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -44,9 +44,10 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:layout_marginTop="6dp"
android:paddingTop="6dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:paddingBottom="10dp"
app:layout_constraintTop_toBottomOf="@id/pill"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
@ -54,7 +55,36 @@
android:textColor="?actionBarTintColor"
android:textSize="18sp"
tools:text="Downloads" />
</LinearLayout>
<eu.kanade.tachiyomi.ui.base.CenteredToolbar
android:id="@+id/sheet_toolbar"
android:layout_width="match_parent"
android:layout_height="?mainActionBarSize"
android:background="@android:color/transparent"
app:collapseIcon="@drawable/ic_arrow_back_24dp"
app:layout_constraintTop_toBottomOf="@id/pill"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="1.0"
app:menu="@menu/download_queue"
app:navigationIcon="@drawable/ic_close_24dp"
app:navigationIconTint="?actionBarTintColor"
app:titleTextColor="?actionBarTintColor" >
<com.google.android.material.textview.MaterialTextView
android:id="@+id/toolbar_title"
style="?textAppearanceTitleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableTint="?actionBarTintColor"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/extensions"
android:textColor="?actionBarTintColor"
android:textSize="20sp"
tools:text="Title Text" />
</eu.kanade.tachiyomi.ui.base.CenteredToolbar>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/recycler_layout"

View file

@ -12,7 +12,7 @@
app:behavior_peekHeight="60sp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/sheet_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -37,6 +37,40 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<eu.kanade.tachiyomi.ui.base.CenteredToolbar
android:id="@+id/sheet_toolbar"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@android:color/transparent"
android:clipChildren="false"
android:clipToPadding="false"
app:collapseIcon="@drawable/ic_arrow_back_24dp"
app:layout_constraintBottom_toTopOf="@id/tabs"
app:layout_constraintHeight_max="?mainActionBarSize"
app:layout_constraintTop_toBottomOf="@id/pill"
app:layout_constraintVertical_bias="1.0"
app:navigationIcon="@drawable/ic_close_24dp"
app:navigationIconTint="?actionBarTintColor"
app:titleTextColor="?actionBarTintColor"
tools:visibility="visible">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/toolbar_title"
style="?textAppearanceTitleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableTint="?actionBarTintColor"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/extensions"
android:textColor="?actionBarTintColor"
android:textSize="20sp"
tools:drawableEnd="@drawable/ic_arrow_drop_down_24dp"
tools:drawableStart="@drawable/ic_blank_24dp"
tools:text="Title Text" />
</eu.kanade.tachiyomi.ui.base.CenteredToolbar>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
style="@style/Theme.Widget.Tabs"
@ -44,15 +78,14 @@
android:layout_height="wrap_content"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/menu"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@id/pill"
app:tabTextColor="@color/tabs_selector_background"
app:tabIndicatorColor="?attr/colorSecondary"
app:tabMaxWidth="0dp"
app:tabGravity="fill"
app:tabMode="fixed" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"

View file

@ -13,14 +13,14 @@
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0">
app:layout_constraintVertical_bias="0.0">
</com.bluelinelabs.conductor.ChangeHandlerFrameLayout>
<com.google.android.material.appbar.AppBarLayout
<eu.kanade.tachiyomi.ui.base.ExpandedAppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -35,6 +35,8 @@
android:layout_width="match_parent"
app:titleTextColor="?actionBarTintColor"
android:layout_height="?mainActionBarSize"
android:translationZ="5dp"
android:outlineProvider="none"
app:collapseIcon="@drawable/ic_arrow_back_24dp"
android:background="@android:color/transparent">
@ -54,6 +56,45 @@
tools:text="Title Text" />
</eu.kanade.tachiyomi.ui.base.CenteredToolbar>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/big_toolbar"
android:layout_width="match_parent"
android:paddingBottom="12dp"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/big_icon"
android:visibility="gone"
tools:visibility="visible"
android:layout_width="52dp"
android:layout_height="52dp"
tools:srcCompat="@mipmap/ic_launcher"
app:layout_constraintBottom_toBottomOf="@id/big_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/big_title"
android:layout_marginStart="16dp"
/>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/big_title"
android:paddingStart="16dp"
android:paddingEnd="12dp"
style="?textAppearanceHeadlineLarge"
android:layout_width="0dp"
android:maxLines="2"
android:layout_marginTop="52dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/big_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:drawableTint="?actionBarTintColor"
android:ellipsize="end"
android:layout_gravity="start"
android:textColor="?actionBarTintColor"
tools:text="Title Text" />
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/card_frame"
android:layout_width="match_parent"
@ -161,7 +202,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:tabGravity="fill"/>
</FrameLayout>
</com.google.android.material.appbar.AppBarLayout>
</eu.kanade.tachiyomi.ui.base.ExpandedAppBarLayout>
<View
android:id="@+id/bottom_view"

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search_24dp"
android:title="@string/search"
android:visible="false"
app:actionViewClass="eu.kanade.tachiyomi.ui.base.MiniSearchView"
app:showAsAction="collapseActionView|ifRoom" />
</menu>

View file

@ -14,7 +14,8 @@
android:icon="@drawable/ic_sort_24dp"
app:showAsAction="ifRoom">
<menu>
<group android:checkableBehavior="single">
<group android:checkableBehavior="single"
android:id="@+id/action_sort_group">
<item
android:id="@+id/action_sort_alpha"
android:title="@string/alphabetically"/>

View file

@ -90,11 +90,7 @@
<string name="category_is_empty">Category is empty</string>
<string name="show_categories_while_filtering">Show empty categories while filtering</string>
<string name="no_matches_for_filters_short">No matches for your filters</string>
<string name="top_category">Top category (%1$s)</string>
<string name="default_category">Default category</string>
<string name="first_category">First category</string>
<string name="categories_on_manual">Categories to update when manually refreshing</string>
<string name="categories_in_global_update">All categories in global update</string>
<string name="confirm_category_deletion">Delete category?</string>
<string name="confirm_category_deletion_message">Manga in this category will moved into the
default category.</string>
@ -116,7 +112,6 @@
<!-- Updates -->
<string name="update">Update</string>
<string name="what_should_update">What should update?</string>
<string name="updating_">Updating %1$s</string>
<string name="update_available">Update available</string>
<string name="new_version_available">New version available!</string>
@ -696,6 +691,8 @@
<string name="pure_black_dark_mode">Pure black dark mode</string>
<string name="details_page">Details page</string>
<string name="theme_buttons_based_on_cover">Theme buttons based on cover</string>
<string name="expanded_toolbar">Expanded toolbar</string>
<string name="show_larger_toolbar">Show a larger, expanded toolbar at the top of most pages (does not show on smaller devices regardless of setting)</string>
<string name="hides_on_scroll">Hides when scrolling</string>
<string name="hide_app_block_screenshots">Hide app contents when switching apps and block screenshots</string>
<string name="hide_notification_content">Hide notification content</string>

View file

@ -126,6 +126,8 @@
<style name="BottomSheetDialogTheme" parent="@style/ThemeOverlay.Material3.BottomSheetDialog">
<item name="paddingBottomSystemWindowInsets">false</item>
<item name="paddingLeftSystemWindowInsets">false</item>
<item name="paddingRightSystemWindowInsets">false</item>
</style>
<style name="OverflowDialogTheme" parent="BottomSheetDialogTheme">