Merge branch 'tachiyomiorg:master' into master

This commit is contained in:
jmir1 2021-05-23 23:11:55 +02:00
commit 1b186d75cd
27 changed files with 308 additions and 198 deletions

View file

@ -13,7 +13,9 @@ object PreferenceKeys {
const val confirmExit = "pref_confirm_exit"
const val hideBottomBar = "pref_hide_bottom_bar_on_scroll"
const val hideBottomBarOnScroll = "pref_hide_bottom_bar_on_scroll"
const val showSideNavOnBottom = "pref_show_side_nav_on_bottom"
const val enableTransitions = "pref_enable_transitions_key"

View file

@ -64,7 +64,9 @@ class PreferencesHelper(val context: Context) {
fun confirmExit() = prefs.getBoolean(Keys.confirmExit, false)
fun hideBottomBar() = flowPrefs.getBoolean(Keys.hideBottomBar, true)
fun hideBottomBarOnScroll() = flowPrefs.getBoolean(Keys.hideBottomBarOnScroll, true)
fun showSideNavOnBottom() = flowPrefs.getBoolean(Keys.showSideNavOnBottom, false)
fun useAuthenticator() = flowPrefs.getBoolean(Keys.useAuthenticator, false)

View file

@ -5,6 +5,7 @@ import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
@ -149,11 +150,19 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
)
// Set behavior of bottom nav
preferences.hideBottomBar()
preferences.hideBottomBarOnScroll()
.asImmediateFlow { setBottomNavBehaviorOnScroll() }
.launchIn(lifecycleScope)
}
if (binding.sideNav != null) {
preferences.showSideNavOnBottom()
.asImmediateFlow {
binding.sideNav?.menuGravity = if (!it) Gravity.TOP else Gravity.BOTTOM
}
.launchIn(lifecycleScope)
}
nav.setOnItemSelectedListener { item ->
val id = item.itemId
@ -532,7 +541,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
binding.bottomNav?.updateLayoutParams<CoordinatorLayout.LayoutParams> {
behavior = when {
preferences.hideBottomBar().get() -> HideBottomViewOnScrollBehavior<View>()
preferences.hideBottomBarOnScroll().get() -> HideBottomViewOnScrollBehavior<View>()
else -> null
}
}

View file

@ -206,11 +206,17 @@ class MangaController :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
listOfNotNull(binding.fullRecycler, binding.infoRecycler, binding.chaptersRecycler)
.forEach {
it.applyInsetter {
type(navigationBars = true) {
padding()
}
}
it.layoutManager = LinearLayoutManager(view.context)
it.setHasFixedSize(true)
}
binding.actionToolbar.applyInsetter {
type(navigationBars = true) {
margin(bottom = true)
@ -220,32 +226,41 @@ class MangaController :
if (manga == null || source == null) return
// Init RecyclerView and adapter
mangaInfoAdapter = MangaInfoHeaderAdapter(this, fromSource)
mangaInfoAdapter = MangaInfoHeaderAdapter(this, fromSource, binding.infoRecycler != null)
chaptersHeaderAdapter = MangaChaptersHeaderAdapter(this)
chaptersAdapter = ChaptersAdapter(this, view.context)
binding.recycler.adapter = ConcatAdapter(mangaInfoAdapter, chaptersHeaderAdapter, chaptersAdapter)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.setHasFixedSize(true)
chaptersAdapter?.fastScroller = binding.fastScroller
// Phone layout
binding.fullRecycler?.let {
it.adapter = ConcatAdapter(mangaInfoAdapter, chaptersHeaderAdapter, chaptersAdapter)
actionFabScrollListener = actionFab?.shrinkOnScroll(binding.recycler)
it.scrollEvents()
.onEach { updateToolbarTitleAlpha() }
.launchIn(viewScope)
// Skips directly to chapters list if navigated to from the library
binding.recycler.post {
it.post {
if (!fromSource && preferences.jumpToChapters()) {
(binding.recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(1, 0)
(it.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(1, 0)
}
// Delayed in case we need to jump to chapters
binding.recycler.post {
it.post {
updateToolbarTitleAlpha()
}
}
}
// Tablet layout
binding.infoRecycler?.let {
it.adapter = mangaInfoAdapter
}
binding.chaptersRecycler?.let {
it.adapter = ConcatAdapter(chaptersHeaderAdapter, chaptersAdapter)
}
binding.recycler.scrollEvents()
.onEach { updateToolbarTitleAlpha() }
.launchIn(viewScope)
chaptersAdapter?.fastScroller = binding.fastScroller
actionFabScrollListener = actionFab?.shrinkOnScroll(chapterRecycler)
binding.swipeRefresh.refreshes()
.onEach {
@ -269,15 +284,19 @@ class MangaController :
}
private fun updateToolbarTitleAlpha(alpha: Int? = null) {
if (binding.fullRecycler == null) {
return
}
val calculatedAlpha = when {
// Specific alpha provided
alpha != null -> alpha
// First item isn't in view, full opacity
((binding.recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() > 0) -> 255
((binding.fullRecycler!!.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() > 0) -> 255
// Based on scroll amount when first item is in view
else -> min(binding.recycler.computeVerticalScrollOffset(), 255)
else -> min(binding.fullRecycler!!.computeVerticalScrollOffset(), 255)
}
(activity as? MainActivity)?.binding?.toolbar?.setTitleTextColor(
@ -321,7 +340,7 @@ class MangaController :
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
actionFabScrollListener?.let { chapterRecycler.removeOnScrollListener(it) }
actionFab = null
}
@ -690,7 +709,7 @@ class MangaController :
fun onNextChapters(chapters: List<ChapterItem>) {
// If the list is empty and it hasn't requested previously, fetch chapters from source
// We use presenter chapters instead because they are always unfiltered
if (!presenter.hasRequested && presenter.chapters.isEmpty()) {
if (!presenter.hasRequested && presenter.allChapters.isEmpty()) {
fetchChaptersFromSource()
}
@ -1020,29 +1039,17 @@ class MangaController :
// OVERFLOW MENU DIALOGS
private fun getUnreadChaptersSorted(): List<ChapterItem> {
val chapters = presenter.chapters
.sortedWith(presenter.getChapterSort())
.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }
.distinctBy { it.name }
return if (presenter.sortDescending()) {
chapters.reversed()
} else {
chapters
}
}
private fun downloadChapters(choice: Int) {
val chaptersToDownload = when (choice) {
R.id.download_next -> getUnreadChaptersSorted().take(1)
R.id.download_next_5 -> getUnreadChaptersSorted().take(5)
R.id.download_next_10 -> getUnreadChaptersSorted().take(10)
R.id.download_next -> presenter.getUnreadChaptersSorted().take(1)
R.id.download_next_5 -> presenter.getUnreadChaptersSorted().take(5)
R.id.download_next_10 -> presenter.getUnreadChaptersSorted().take(10)
R.id.download_custom -> {
showCustomDownloadDialog()
return
}
R.id.download_unread -> presenter.chapters.filter { !it.read }
R.id.download_all -> presenter.chapters
R.id.download_unread -> presenter.allChapters.filter { !it.read }
R.id.download_all -> presenter.allChapters
else -> emptyList()
}
if (chaptersToDownload.isNotEmpty()) {
@ -1054,12 +1061,12 @@ class MangaController :
private fun showCustomDownloadDialog() {
DownloadCustomChaptersDialog(
this,
presenter.chapters.size
presenter.allChapters.size
).showDialog(router)
}
override fun downloadCustomChapters(amount: Int) {
val chaptersToDownload = getUnreadChaptersSorted().take(amount)
val chaptersToDownload = presenter.getUnreadChaptersSorted().take(amount)
if (chaptersToDownload.isNotEmpty()) {
downloadChapters(chaptersToDownload)
}
@ -1096,6 +1103,9 @@ class MangaController :
// Tracker sheet - end
private val chapterRecycler: RecyclerView
get() = binding.fullRecycler ?: binding.chaptersRecycler!!
companion object {
const val FROM_SOURCE_EXTRA = "from_source"
const val MANGA_EXTRA = "manga"

View file

@ -26,6 +26,7 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
import eu.kanade.tachiyomi.util.chapter.getChapterSort
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay
import eu.kanade.tachiyomi.util.isLocal
@ -65,10 +66,9 @@ class MangaPresenter(
*/
private var fetchMangaJob: Job? = null
/**
* List of chapters of the manga. It's always unfiltered and unsorted.
*/
var chapters: List<ChapterItem> = emptyList()
var allChapters: List<ChapterItem> = emptyList()
private set
var filteredAndSortedChapters: List<ChapterItem> = emptyList()
private set
/**
@ -124,7 +124,13 @@ class MangaPresenter(
// Prepare the relay.
chaptersRelay.flatMap { applyChapterFilters(it) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(MangaController::onNextChapters) { _, error -> Timber.e(error) }
.subscribeLatestCache(
{ _, chapters ->
filteredAndSortedChapters = chapters
view?.onNextChapters(chapters)
},
{ _, error -> Timber.e(error) }
)
// Manga info - end
@ -143,7 +149,7 @@ class MangaPresenter(
setDownloadedChapters(chapters)
// Store the last emission
this.chapters = chapters
this.allChapters = chapters
// Listen for download status changes
observeDownloads()
@ -401,7 +407,7 @@ class MangaPresenter(
* Updates the UI after applying the filters.
*/
private fun refreshChapters() {
chaptersRelay.call(chapters)
chaptersRelay.call(allChapters)
}
/**
@ -433,25 +439,7 @@ class MangaPresenter(
observable = observable.filter { !it.bookmark }
}
return observable.toSortedList(getChapterSort())
}
fun getChapterSort(): (Chapter, Chapter) -> Int {
return when (manga.sorting) {
Manga.CHAPTER_SORTING_SOURCE -> when (sortDescending()) {
true -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) }
false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
}
Manga.CHAPTER_SORTING_NUMBER -> when (sortDescending()) {
true -> { c1, c2 -> c2.chapter_number.compareTo(c1.chapter_number) }
false -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) }
}
Manga.CHAPTER_SORTING_UPLOAD_DATE -> when (sortDescending()) {
true -> { c1, c2 -> c2.date_upload.compareTo(c1.date_upload) }
false -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) }
}
else -> throw NotImplementedError("Unimplemented sorting method")
}
return observable.toSortedList(getChapterSort(manga))
}
/**
@ -461,7 +449,7 @@ class MangaPresenter(
private fun onDownloadStatusChange(download: Download) {
// Assign the download to the model object.
if (download.status == Download.State.QUEUE) {
chapters.find { it.id == download.chapter.id }?.let {
allChapters.find { it.id == download.chapter.id }?.let {
if (it.download == null) {
it.download = download
}
@ -478,11 +466,22 @@ class MangaPresenter(
* Returns the next unread chapter or null if everything is read.
*/
fun getNextUnreadChapter(): ChapterItem? {
val chapters = chapters.sortedWith(getChapterSort())
return if (sortDescending()) {
return chapters.findLast { !it.read }
return filteredAndSortedChapters.findLast { !it.read }
} else {
chapters.find { !it.read }
filteredAndSortedChapters.find { !it.read }
}
}
fun getUnreadChaptersSorted(): List<ChapterItem> {
val chapters = allChapters
.sortedWith(getChapterSort(manga))
.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }
.distinctBy { it.name }
return if (sortDescending()) {
chapters.reversed()
} else {
chapters
}
}
@ -713,7 +712,7 @@ class MangaPresenter(
db.insertTrack(track).executeAsBlocking()
if (it.service is UnattendedTrackService) {
syncChaptersWithTrackServiceTwoWay(db, chapters, track, it.service)
syncChaptersWithTrackServiceTwoWay(db, allChapters, track, it.service)
}
}
}
@ -748,7 +747,7 @@ class MangaPresenter(
db.insertTrack(item).executeAsBlocking()
if (service is UnattendedTrackService) {
syncChaptersWithTrackServiceTwoWay(db, chapters, item, service)
syncChaptersWithTrackServiceTwoWay(db, allChapters, item, service)
}
} catch (e: Throwable) {
withUIContext { view?.applicationContext?.toast(e.message) }

View file

@ -28,7 +28,8 @@ import uy.kohesive.injekt.injectLazy
class MangaInfoHeaderAdapter(
private val controller: MangaController,
private val fromSource: Boolean
private val fromSource: Boolean,
private val isTablet: Boolean,
) :
RecyclerView.Adapter<MangaInfoHeaderAdapter.HeaderViewHolder>() {
@ -194,11 +195,17 @@ class MangaInfoHeaderAdapter(
*/
private fun setMangaInfo(manga: Manga, source: Source?) {
// Update full title TextView.
binding.mangaFullTitle.text = if (manga.title.isBlank()) {
with(binding.mangaFullTitle) {
if (isTablet) {
isVisible = false
} else {
text = if (manga.title.isBlank()) {
view.context.getString(R.string.unknown)
} else {
manga.title
}
}
}
// Update author TextView.
binding.mangaAuthor.text = if (manga.author.isNullOrBlank()) {
@ -282,8 +289,9 @@ class MangaInfoHeaderAdapter(
.onEach { toggleMangaInfo() }
.launchIn(controller.viewScope)
// Expand manga info if navigated from source listing
if (initialLoad && fromSource) {
// Expand manga info if navigated from source listing or explicitly set to
// (e.g. on tablets)
if (initialLoad && (fromSource || isTablet)) {
toggleMangaInfo()
initialLoad = false
}

View file

@ -1,46 +0,0 @@
package eu.kanade.tachiyomi.ui.reader
import eu.kanade.tachiyomi.data.database.models.Chapter
/**
* Load strategy using the source order. This is the default ordering.
*/
class ChapterLoadBySource {
fun get(allChapters: List<Chapter>): List<Chapter> {
return allChapters.sortedByDescending { it.source_order }
}
}
/**
* Load strategy using unique chapter numbers with same scanlator preference.
*/
class ChapterLoadByNumber {
fun get(allChapters: List<Chapter>, selectedChapter: Chapter): List<Chapter> {
val chapters = mutableListOf<Chapter>()
val chaptersByNumber = allChapters.groupBy { it.chapter_number }
for ((number, chaptersForNumber) in chaptersByNumber) {
val preferredChapter = when {
// Make sure the selected chapter is always present
number == selectedChapter.chapter_number -> selectedChapter
// If there is only one chapter for this number, use it
chaptersForNumber.size == 1 -> chaptersForNumber.first()
// Prefer a chapter of the same scanlator as the selected
else ->
chaptersForNumber.find { it.scanlator == selectedChapter.scanlator }
?: chaptersForNumber.first()
}
chapters.add(preferredChapter)
}
return chapters.sortedBy { it.chapter_number }
}
}
/**
* Load strategy using the chapter upload date. This ordering ignores scanlators
*/
class ChapterLoadByUploadDate {
fun get(allChapters: List<Chapter>): List<Chapter> {
return allChapters.sortedBy { it.date_upload }
}
}

View file

@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.util.chapter.getChapterSort
import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.lang.byteSize
import eu.kanade.tachiyomi.util.lang.launchIO
@ -96,27 +97,21 @@ class ReaderPresenter(
val selectedChapter = dbChapters.find { it.id == chapterId }
?: error("Requested chapter of id $chapterId not found in chapter list")
val chaptersForReader =
if (preferences.skipRead() || preferences.skipFiltered()) {
val list = dbChapters
.filter {
if (preferences.skipRead() && it.read) {
return@filter false
} else if (preferences.skipFiltered()) {
if (
val chaptersForReader = when {
(preferences.skipRead() || preferences.skipFiltered()) -> {
val list = dbChapters.filterNot {
when {
preferences.skipRead() && it.read -> true
preferences.skipFiltered() -> {
(manga.readFilter == Manga.CHAPTER_SHOW_READ && !it.read) ||
(manga.readFilter == Manga.CHAPTER_SHOW_UNREAD && it.read) ||
(
manga.downloadedFilter == Manga.CHAPTER_SHOW_DOWNLOADED &&
!downloadManager.isChapterDownloaded(it, manga)
) ||
(manga.bookmarkedFilter == Manga.CHAPTER_SHOW_BOOKMARKED && !it.bookmark)
) {
return@filter false
(manga.downloadedFilter == Manga.CHAPTER_SHOW_DOWNLOADED && !downloadManager.isChapterDownloaded(it, manga)) ||
(manga.downloadedFilter == Manga.CHAPTER_SHOW_NOT_DOWNLOADED && downloadManager.isChapterDownloaded(it, manga)) ||
(manga.bookmarkedFilter == Manga.CHAPTER_SHOW_BOOKMARKED && !it.bookmark) ||
(manga.bookmarkedFilter == Manga.CHAPTER_SHOW_NOT_BOOKMARKED && it.bookmark)
}
else -> false
}
true
}
.toMutableList()
@ -125,16 +120,13 @@ class ReaderPresenter(
list.add(selectedChapter)
}
list
} else {
dbChapters
}
else -> dbChapters
}
when (manga.sorting) {
Manga.CHAPTER_SORTING_SOURCE -> ChapterLoadBySource().get(chaptersForReader)
Manga.CHAPTER_SORTING_NUMBER -> ChapterLoadByNumber().get(chaptersForReader, selectedChapter)
Manga.CHAPTER_SORTING_UPLOAD_DATE -> ChapterLoadByUploadDate().get(chaptersForReader)
else -> error("Unknown sorting method")
}.map(::ReaderChapter)
chaptersForReader
.sortedWith(getChapterSort(manga, sortDescending = false))
.map(::ReaderChapter)
}
private var hasTrackers: Boolean = false

View file

@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory
import eu.kanade.tachiyomi.util.preference.switchPreference
import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.isTablet
import kotlinx.coroutines.flow.launchIn
import java.util.Date
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
@ -45,11 +46,19 @@ class SettingsGeneralController : SettingsController() {
titleRes = R.string.pref_confirm_exit
defaultValue = false
}
if (context.isTablet()) {
switchPreference {
key = Keys.hideBottomBar
key = Keys.showSideNavOnBottom
titleRes = R.string.pref_move_side_nav_to_bottom
defaultValue = false
}
} else {
switchPreference {
key = Keys.hideBottomBarOnScroll
titleRes = R.string.pref_hide_bottom_bar_on_scroll
defaultValue = true
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
preference {

View file

@ -28,6 +28,7 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory
import eu.kanade.tachiyomi.util.preference.summaryRes
import eu.kanade.tachiyomi.util.preference.switchPreference
import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.isTablet
import eu.kanade.tachiyomi.widget.MinMaxNumberPicker
import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateCheckBox
import eu.kanade.tachiyomi.widget.materialdialogs.listItemsQuadStateMultiChoice
@ -75,12 +76,14 @@ class SettingsLibraryController : SettingsController() {
}
.launchIn(viewScope)
}
if (!context.isTablet()) {
switchPreference {
key = Keys.jumpToChapters
titleRes = R.string.pref_jump_to_chapters
defaultValue = false
}
}
}
preferenceCategory {
titleRes = R.string.categories

View file

@ -0,0 +1,22 @@
package eu.kanade.tachiyomi.util.chapter
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
fun getChapterSort(manga: Manga, sortDescending: Boolean = manga.sortDescending()): (Chapter, Chapter) -> Int {
return when (manga.sorting) {
Manga.CHAPTER_SORTING_SOURCE -> when (sortDescending) {
true -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) }
false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
}
Manga.CHAPTER_SORTING_NUMBER -> when (sortDescending) {
true -> { c1, c2 -> c2.chapter_number.compareTo(c1.chapter_number) }
false -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) }
}
Manga.CHAPTER_SORTING_UPLOAD_DATE -> when (sortDescending) {
true -> { c1, c2 -> c2.date_upload.compareTo(c1.date_upload) }
false -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) }
}
else -> throw NotImplementedError("Unimplemented sorting method")
}
}

View file

@ -258,3 +258,10 @@ fun Context.createFileInCacheDir(name: String): File {
file.createNewFile()
return file
}
/**
* We consider anything with a width of >= 600dp as a tablet, i.e. with layouts in layout-sw600dp.
*/
fun Context.isTablet(): Boolean {
return resources.configuration.screenWidthDp >= 600
}

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="71"
android:viewportHeight="71">
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M60.105,4.898A58.55,58.55 0,0 0,45.653 0.415a0.22,0.22 0,0 0,-0.233 0.11,40.783 40.783,0 0,0 -1.8,3.697c-5.456,-0.817 -10.886,-0.817 -16.23,0 -0.485,-1.164 -1.201,-2.587 -1.828,-3.697a0.228,0.228 0,0 0,-0.233 -0.11,58.387 58.387,0 0,0 -14.451,4.483 0.207,0.207 0,0 0,-0.095 0.082C1.578,18.73 -0.944,32.144 0.293,45.39a0.244,0.244 0,0 0,0.093 0.167c6.073,4.46 11.955,7.167 17.729,8.962a0.23,0.23 0,0 0,0.249 -0.082,42.08 42.08,0 0,0 3.627,-5.9 0.225,0.225 0,0 0,-0.123 -0.312,38.772 38.772,0 0,1 -5.539,-2.64 0.228,0.228 0,0 1,-0.022 -0.378c0.372,-0.279 0.744,-0.569 1.1,-0.862a0.22,0.22 0,0 1,0.23 -0.03c11.619,5.304 24.198,5.304 35.68,0a0.219,0.219 0,0 1,0.233 0.027c0.356,0.293 0.728,0.586 1.103,0.865a0.228,0.228 0,0 1,-0.02 0.378,36.384 36.384,0 0,1 -5.54,2.637 0.227,0.227 0,0 0,-0.121 0.315,47.249 47.249,0 0,0 3.624,5.897 0.225,0.225 0,0 0,0.249 0.084c5.801,-1.794 11.684,-4.502 17.757,-8.961a0.228,0.228 0,0 0,0.092 -0.164c1.48,-15.315 -2.48,-28.618 -10.497,-40.412a0.18,0.18 0,0 0,-0.093 -0.084zM23.725,37.325c-3.497,0 -6.38,-3.211 -6.38,-7.156 0,-3.944 2.827,-7.156 6.38,-7.156 3.583,0 6.438,3.24 6.382,7.156 0,3.945 -2.827,7.156 -6.381,7.156zM47.318,37.325c-3.498,0 -6.38,-3.211 -6.38,-7.156 0,-3.944 2.826,-7.156 6.38,-7.156 3.582,0 6.437,3.24 6.38,7.156 0,3.945 -2.798,7.156 -6.38,7.156z" />
android:pathData="M20.349,4.389a0.06,0.06 0,0 0,-0.032 -0.029,19.77 19.77,0 0,0 -4.885,-1.515 0.075,0.075 0,0 0,-0.079 0.037c-0.209,0.375 -0.444,0.865 -0.607,1.249a18.35,18.35 0,0 0,-5.487 0,12.583 12.583,0 0,0 -0.617,-1.249 0.078,0.078 0,0 0,-0.079 -0.037,19.746 19.746,0 0,0 -4.886,1.515 0.06,0.06 0,0 0,-0.031 0.028c-3.111,4.648 -3.964,9.183 -3.545,13.66a0.082,0.082 0,0 0,0.031 0.057,19.912 19.912,0 0,0 5.993,3.03 0.075,0.075 0,0 0,0.084 -0.028c0.461,-0.631 0.873,-1.296 1.226,-1.994a0.076,0.076 0,0 0,-0.042 -0.106,13.172 13.172,0 0,1 -1.872,-0.892 0.077,0.077 0,0 1,-0.007 -0.128c0.125,-0.094 0.251,-0.192 0.371,-0.292a0.078,0.078 0,0 1,0.078 -0.011c3.928,1.794 8.179,1.794 12.062,0a0.073,0.073 0,0 1,0.077 0.01c0.122,0.099 0.247,0.199 0.373,0.293a0.078,0.078 0,0 1,-0.007 0.128c-0.597,0.349 -1.22,0.645 -1.873,0.892a0.075,0.075 0,0 0,-0.04 0.106c0.359,0.697 0.771,1.362 1.225,1.993a0.077,0.077 0,0 0,0.085 0.029,19.842 19.842,0 0,0 6.003,-3.03 0.077,0.077 0,0 0,0.03 -0.056c0.499,-5.177 -0.839,-9.674 -3.549,-13.66zM8.02,15.322c-1.183,0 -2.157,-1.087 -2.157,-2.419 0,-1.334 0.956,-2.42 2.157,-2.42 1.21,0 2.176,1.095 2.157,2.42 0,1.332 -0.955,2.419 -2.157,2.419zM15.995,15.322c-1.184,0 -2.157,-1.087 -2.157,-2.419 0,-1.334 0.955,-2.42 2.157,-2.42 1.211,0 2.175,1.095 2.156,2.42 0,1.332 -0.945,2.419 -2.156,2.419z" />
</vector>

View file

@ -76,7 +76,7 @@
android:layout_width="wrap_content"
android:layout_height="0dp"
app:itemIconTint="@color/nav_selector"
app:itemRippleColor="?attr/rippleNavColor"
app:itemRippleColor="?attr/rippleSecondaryColor"
app:itemTextColor="@color/nav_selector"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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="match_parent"
android:orientation="vertical">
<eu.kanade.tachiyomi.widget.RevealAnimationView
android:id="@+id/reveal_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorAccent"
android:elevation="5dp"
android:visibility="invisible" />
<eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/linear_recycler_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/info_recycler"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/chapters_recycler"
app:layout_constraintWidth_percent="0.4"
tools:itemCount="1"
tools:listitem="@layout/manga_info_header" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/chapters_recycler"
android:layout_width="0dp"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/fab_list_padding"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/info_recycler"
tools:listitem="@layout/chapters_item" />
<eu.kanade.tachiyomi.widget.MaterialFastScroll
android:id="@+id/fast_scroller"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_gravity="end"
app:fastScrollerBubbleEnabled="false"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible" />
<eu.kanade.tachiyomi.widget.ActionToolbar
android:id="@+id/action_toolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/chapters_recycler"
app:layout_constraintEnd_toEndOf="@+id/chapters_recycler"
app:layout_dodgeInsetEdges="bottom" />
</androidx.constraintlayout.widget.ConstraintLayout>
</eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -13,7 +13,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="8dp"
android:paddingBottom="@dimen/action_toolbar_list_padding"

View file

@ -82,7 +82,7 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:itemIconTint="@color/nav_selector"
app:itemRippleColor="?attr/rippleNavColor"
app:itemRippleColor="?attr/rippleSecondaryColor"
app:itemTextColor="@color/nav_selector"
app:labelVisibilityMode="labeled"
app:layout_insetEdge="bottom"

View file

@ -18,11 +18,10 @@
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/toolbar_bottom"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:id="@+id/full_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"

View file

@ -8,7 +8,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="match_parent" />
<eu.kanade.tachiyomi.widget.MaterialFastScroll
android:id="@+id/fast_scroller"

View file

@ -8,7 +8,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="8dp"
android:paddingBottom="@dimen/action_toolbar_list_padding" />

View file

@ -8,7 +8,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="8dp"
android:paddingBottom="@dimen/action_toolbar_list_padding"

View file

@ -22,6 +22,7 @@
<attr name="colorFilterActive" format="reference|integer"/>
<attr name="colorBackgroundSplash" format="reference|integer"/>
<attr name="colorAccentOnPrimary" format="reference|integer"/>
<attr name="rippleNavColor" format="reference|integer"/>
<attr name="rippleSecondaryColor" format="reference|integer"/>
<attr name="rippleToolbarColor" format="reference|integer"/>
</resources>

View file

@ -19,7 +19,8 @@
<color name="textColorHintLight">@color/md_black_1000_38</color>
<color name="dividerLight">@color/md_black_1000_12</color>
<color name="rippleColorLight">@color/md_black_1000_6</color>
<color name="rippleNavColorLight">@color/md_blue_A400_4</color>
<color name="rippleSecondaryColorLight">@color/md_blue_A400_4</color>
<color name="rippleToolbarColorLight">@color/rippleColorLight</color>
<color name="backgroundLight">@color/md_grey_50</color>
<color name="dialogLight">@color/md_white_1000</color>
<color name="selectorColorLight">@color/md_blue_A400_38</color>
@ -31,7 +32,8 @@
<color name="textColorHintDark">@color/md_white_1000_50</color>
<color name="dividerDark">@android:color/transparent</color>
<color name="rippleColorDark">@color/md_white_1000_6</color>
<color name="rippleNavColorDark">#0A3399FF</color>
<color name="rippleSecondaryColorDark">#0A3399FF</color>
<color name="rippleToolbarColorDark">@color/rippleColorDark</color>
<color name="backgroundDark">@color/colorDarkPrimaryDark</color>
<color name="dialogDark">@color/colorDarkPrimary</color>
<color name="selectorColorDark">@color/md_blue_A200_50</color>

View file

@ -175,6 +175,7 @@
<string name="pref_date_format">Date format</string>
<string name="pref_confirm_exit">Confirm exit</string>
<string name="pref_hide_bottom_bar_on_scroll">Hide bottom bar on scroll</string>
<string name="pref_move_side_nav_to_bottom">Move side navigation buttons to bottom</string>
<string name="pref_manage_notifications">Manage notifications</string>
<string name="pref_category_security">Security</string>

View file

@ -6,12 +6,16 @@
<!--========-->
<style name="Theme.Toolbar" parent="@style/ThemeOverlay.MaterialComponents.ActionBar" />
<style name="Theme.Toolbar.Light" parent="Theme.Toolbar.Custom">
<style name="Theme.Toolbar.Light" parent="Theme.Toolbar.Custom.Dark">
<item name="popupTheme">@style/ThemeOverlay.MaterialComponents.Light</item>
</style>
<style name="Theme.Toolbar.Custom" parent="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
<item name="android:colorControlHighlight">?attr/rippleColor</item>
<style name="Theme.Toolbar.Custom" parent="@style/ThemeOverlay.MaterialComponents.ActionBar">
<item name="android:colorControlHighlight">?attr/rippleToolbarColor</item>
</style>
<style name="Theme.Toolbar.Custom.Dark" parent="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
<item name="android:colorControlHighlight">?attr/rippleToolbarColor</item>
</style>
<style name="Theme.Toolbar.Navigation" parent="Widget.AppCompat.Toolbar.Button.Navigation">
@ -292,7 +296,7 @@
<!--==============-->
<style name="Theme.Widget.Button" parent="Widget.MaterialComponents.Button.TextButton">
<item name="android:textColor">?attr/colorAccent</item>
<item name="rippleColor">?attr/rippleColor</item>
<item name="rippleColor">?attr/rippleSecondaryColor</item>
<item name="android:textAllCaps">false</item>
</style>
@ -348,7 +352,7 @@
<item name="tabMinWidth">75dp</item>
<item name="tabMode">scrollable</item>
<item name="tabTextAppearance">@style/TextAppearance.Widget.Tab</item>
<item name="tabRippleColor">?attr/rippleNavColor</item>
<item name="tabRippleColor">?attr/rippleSecondaryColor</item>
</style>

View file

@ -36,13 +36,16 @@
<item name="android:textColorPrimaryInverse">@color/textColorPrimaryDark</item>
<item name="android:textColorSecondaryInverse">@color/textColorSecondaryDark</item>
<item name="android:textColorHintInverse">@color/textColorHintDark</item>
<item name="rippleColor">@color/rippleColorLight</item>
<item name="rippleNavColor">@color/rippleNavColorLight</item>
<item name="android:colorEdgeEffect">?attr/colorAccent</item>
<item name="android:divider">@color/dividerLight</item>
<item name="android:listDivider">@drawable/line_divider</item>
<!-- Ripples -->
<item name="rippleColor">@color/rippleColorLight</item>
<item name="rippleSecondaryColor">@color/rippleSecondaryColorLight</item>
<item name="rippleToolbarColor">@color/rippleToolbarColorLight</item>
<!-- Handles RTL text -->
<item name="android:textAlignment">gravity</item>
<item name="android:textDirection">locale</item>
@ -57,6 +60,7 @@
<item name="actionModeStyle">@style/Theme.ActionMode</item>
<item name="actionModeCloseButtonStyle">@style/Theme.ActionMode.CloseButton</item>
<item name="actionModeCloseDrawable">@drawable/ic_close_24dp</item>
<item name="actionBarTheme">@style/Theme.Toolbar.Custom</item>
<item name="actionBarPopupTheme">@style/ThemeOverlay.MaterialComponents</item>
<item name="toolbarNavigationButtonStyle">@style/Theme.Toolbar.Navigation</item>
<item name="preferenceTheme">@style/PreferenceThemeCustom</item>
@ -104,7 +108,8 @@
<item name="colorAccentOnPrimary">@color/textColorPrimaryDark</item>
<item name="colorPrimaryVariant">@color/colorPrimaryDark</item>
<item name="colorFilterActive">@color/filterColorDark</item>
<item name="rippleNavColor">@color/md_white_1000_6</item>
<item name="rippleSecondaryColor">@color/md_white_1000_6</item>
<item name="rippleToolbarColor">@color/md_white_1000_12</item>
<item name="actionBarTheme">@style/Theme.Toolbar.Light</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item>
@ -138,13 +143,16 @@
<item name="android:textColorPrimaryInverse">@color/textColorPrimaryLight</item>
<item name="android:textColorSecondaryInverse">@color/textColorSecondaryLight</item>
<item name="android:textColorHintInverse">@color/textColorHintLight</item>
<item name="rippleNavColor">@color/rippleNavColorDark</item>
<item name="rippleColor">@color/rippleColorDark</item>
<item name="android:colorEdgeEffect">?attr/colorAccent</item>
<item name="android:divider">@color/dividerDark</item>
<item name="android:listDivider">@drawable/line_divider</item>
<!-- Ripples -->
<item name="rippleColor">@color/rippleColorDark</item>
<item name="rippleSecondaryColor">@color/rippleSecondaryColorDark</item>
<item name="rippleToolbarColor">@color/rippleToolbarColorDark</item>
<!-- Themes -->
<item name="android:statusBarColor">?attr/colorPrimary</item>
<item name="android:navigationBarColor">?attr/colorPrimary</item>
@ -155,7 +163,7 @@
<item name="actionModeStyle">@style/Theme.ActionMode</item>
<item name="actionModeCloseButtonStyle">@style/Theme.ActionMode.CloseButton</item>
<item name="actionModeCloseDrawable">@drawable/ic_close_24dp</item>
<item name="actionBarTheme">@style/Theme.Toolbar.Custom</item>
<item name="actionBarTheme">@style/Theme.Toolbar.Custom.Dark</item>
<item name="actionBarPopupTheme">@style/ThemeOverlay.MaterialComponents</item>
<item name="toolbarNavigationButtonStyle">@style/Theme.Toolbar.Navigation</item>
<item name="preferenceTheme">@style/PreferenceThemeCustom</item>
@ -194,8 +202,8 @@
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorAccentOnPrimary">@color/textColorPrimaryDark</item>
<item name="colorPrimaryVariant">@color/colorPrimary</item>
<item name="rippleNavColor">@color/md_white_1000_6</item>
<item name="rippleSecondaryColor">@color/md_black_1000_6</item>
<item name="rippleToolbarColor">@color/md_black_1000_12</item>
</style>
<style name="Theme.Tachiyomi.Dark.Amoled">
@ -226,7 +234,7 @@
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<item name="android:navigationBarColor">?attr/colorPrimaryVariant</item>
<item name="actionBarTheme">@style/Theme.Toolbar.Custom</item>
<item name="actionBarTheme">@style/Theme.Toolbar.Custom.Dark</item>
<item name="actionBarPopupTheme">@style/ThemeOverlay.MaterialComponents</item>
<item name="switchStyle">@style/Theme.Widget.BasicSwitch</item>
<item name="bottomSheetDialogTheme">@style/Theme.BottomSheet</item>
@ -242,7 +250,7 @@
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<item name="android:navigationBarColor">?attr/colorPrimaryVariant</item>
<item name="actionBarTheme">@style/Theme.Toolbar.Custom</item>
<item name="actionBarTheme">@style/Theme.Toolbar.Custom.Dark</item>
<item name="actionBarPopupTheme">@style/ThemeOverlay.MaterialComponents</item>
<item name="switchStyle">@style/Theme.Widget.BasicSwitch</item>
<item name="bottomSheetDialogTheme">@style/Theme.BottomSheet</item>

View file

@ -11,7 +11,7 @@
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx3072m
org.gradle.jvmargs=-Xmx4096m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit