Totally rewrote the all in one manga page, now is a recycler header

It works perfect, there is no lag it all
This commit is contained in:
Jobobby04
2020-05-21 00:29:59 -04:00
parent a846f7da47
commit 51c8430e9c
11 changed files with 1633 additions and 1278 deletions

View File

@ -0,0 +1,81 @@
package eu.kanade.tachiyomi.ui.manga
import android.content.Context
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.manga.chapter.MangaAllInOneChapterItem
import eu.kanade.tachiyomi.util.system.getResourceColor
import java.text.DateFormat
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import kotlinx.coroutines.CoroutineScope
import uy.kohesive.injekt.injectLazy
class MangaAllInOneAdapter(
controller: MangaAllInOneController,
context: Context
) : FlexibleAdapter<IFlexible<*>>(null, controller, true) {
val delegate: MangaAllInOneInterface = controller
val preferences: PreferencesHelper by injectLazy()
var items: List<MangaAllInOneChapterItem> = emptyList()
val readColor = context.getResourceColor(R.attr.colorOnSurface, 0.38f)
val unreadColor = context.getResourceColor(R.attr.colorOnSurface)
val bookmarkedColor = context.getResourceColor(R.attr.colorAccent)
val decimalFormat = DecimalFormat(
"#.###",
DecimalFormatSymbols()
.apply { decimalSeparator = '.' }
)
val dateFormat: DateFormat = preferences.dateFormat()
override fun updateDataSet(items: List<IFlexible<*>>?) {
this.items = items as List<MangaAllInOneChapterItem>? ?: emptyList()
super.updateDataSet(items)
}
fun indexOf(item: MangaAllInOneChapterItem): Int {
return items.indexOf(item)
}
interface MangaAllInOneInterface : MangaHeaderInterface
interface MangaHeaderInterface {
fun openSmartSearch()
fun mangaPresenter(): MangaAllInOnePresenter
fun openRecommends()
fun onNextManga(manga: Manga, source: Source, chapters: List<MangaAllInOneChapterItem>)
fun setMangaInfo(manga: Manga, source: Source?, chapters: List<MangaAllInOneChapterItem>)
fun openInWebView()
fun shareManga()
fun fetchMangaFromSource(manualFetch: Boolean = false, fetchManga: Boolean = true, fetchChapters: Boolean = true)
fun onFetchMangaDone()
fun onFetchMangaError(error: Throwable)
fun setRefreshing(value: Boolean)
fun onFavoriteClick()
fun onCategoriesClick()
fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>)
fun performGlobalSearch(query: String)
fun wrapTag(namespace: String, tag: String): String
fun isEHentaiBasedSource(): Boolean
fun performSearch(query: String)
fun openTracking()
suspend fun mergeWithAnother()
fun copyToClipboard(label: String, text: String)
fun migrateManga()
fun isInitialLoadAndFromSource(): Boolean
fun removeInitialLoad()
val controllerScope: CoroutineScope
}
}

View File

@ -3,10 +3,8 @@ package eu.kanade.tachiyomi.ui.manga
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
@ -15,11 +13,9 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.snackbar.Snackbar
import com.google.gson.Gson
import eu.davidea.flexibleadapter.FlexibleAdapter
@ -30,16 +26,11 @@ import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.databinding.MangaAllInOneControllerBinding
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.source.SourceController
@ -49,12 +40,11 @@ import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.main.offsetAppbarHeight
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterHolder
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter
import eu.kanade.tachiyomi.ui.manga.chapter.DeleteChaptersDialog
import eu.kanade.tachiyomi.ui.manga.chapter.DownloadCustomChaptersDialog
import eu.kanade.tachiyomi.ui.manga.chapter.MangaAllInOneChapterHolder
import eu.kanade.tachiyomi.ui.manga.chapter.MangaAllInOneChapterItem
import eu.kanade.tachiyomi.ui.manga.track.TrackController
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
@ -69,13 +59,9 @@ import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.shrinkOnScroll
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.util.view.visibleIf
import exh.EH_SOURCE_ID
import exh.EXH_SOURCE_ID
import exh.MERGED_SOURCE_ID
import exh.util.setChipsExtended
import java.text.DateFormat
import java.text.DecimalFormat
import java.util.Date
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CancellationException
@ -86,11 +72,8 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import reactivecircus.flowbinding.android.view.clicks
import reactivecircus.flowbinding.android.view.longClicks
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -110,7 +93,8 @@ class MangaAllInOneController :
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
DownloadCustomChaptersDialog.Listener,
DeleteChaptersDialog.Listener {
DeleteChaptersDialog.Listener,
MangaAllInOneAdapter.MangaAllInOneInterface {
constructor(manga: Manga?, fromSource: Boolean = false, smartSearchConfig: SourceController.SmartSearchConfig? = null, update: Boolean = false) : super(
Bundle().apply {
@ -158,7 +142,7 @@ class MangaAllInOneController :
/**
* Adapter containing a list of chapters.
*/
private var adapter: ChaptersAdapter? = null
private var adapter: MangaAllInOneAdapter? = null
/**
* Action mode for multiple selection.
@ -168,7 +152,7 @@ class MangaAllInOneController :
/**
* Selected items. Used to restore selections after a rotation.
*/
private val selectedItems = mutableSetOf<ChapterItem>()
private val selectedItems = mutableSetOf<MangaAllInOneChapterItem>()
private var lastClickPosition = -1
@ -192,6 +176,8 @@ class MangaAllInOneController :
var update = args.getBoolean(UPDATE_EXTRA, false)
override val controllerScope = scope
init {
setHasOptionsMenu(true)
}
@ -214,178 +200,10 @@ class MangaAllInOneController :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
// Setting this via XML doesn't work
binding.mangaCover.clipToOutline = true
binding.btnFavorite.clicks()
.onEach { onFavoriteClick() }
.launchIn(scope)
if ((Injekt.get<TrackManager>().hasLoggedServices()) && presenter.manga.favorite) {
binding.btnTracking.visible()
}
scope.launch(Dispatchers.IO) {
if (Injekt.get<DatabaseHelper>().getTracks(presenter.manga).executeAsBlocking().any {
val status = Injekt.get<TrackManager>().getService(it.sync_id)?.getStatus(it.status)
status != null
}
) {
withContext(Dispatchers.Main) {
binding.btnTracking.icon = resources!!.getDrawable(R.drawable.ic_cloud_white_24dp, null)
}
}
}
binding.btnTracking.clicks()
.onEach { openTracking() }
.launchIn(scope)
if (presenter.manga.favorite && presenter.getCategories().isNotEmpty()) {
binding.btnCategories.visible()
}
binding.btnCategories.clicks()
.onEach { onCategoriesClick() }
.launchIn(scope)
if (presenter.source is HttpSource) {
binding.btnWebview.visible()
binding.btnShare.visible()
binding.btnWebview.clicks()
.onEach { openInWebView() }
.launchIn(scope)
binding.btnShare.clicks()
.onEach { shareManga() }
.launchIn(scope)
}
if (presenter.manga.favorite) {
binding.btnMigrate.visible()
binding.btnSmartSearch.visible()
}
binding.btnMigrate.clicks()
.onEach {
PreMigrationController.navigateToMigration(
preferences.skipPreMigration().get(),
router,
listOf(presenter.manga.id!!)
)
}
.launchIn(scope)
binding.btnSmartSearch.clicks()
.onEach { openSmartSearch() }
.launchIn(scope)
// Set SwipeRefresh to refresh manga data.
binding.swipeRefresh.refreshes()
.onEach { fetchMangaFromSource(manualFetch = true) }
.launchIn(scope)
binding.mangaFullTitle.longClicks()
.onEach {
activity?.copyToClipboard(view.context.getString(R.string.title), binding.mangaFullTitle.text.toString())
}
.launchIn(scope)
binding.mangaFullTitle.clicks()
.onEach {
performGlobalSearch(binding.mangaFullTitle.text.toString())
}
.launchIn(scope)
binding.mangaArtist.longClicks()
.onEach {
activity?.copyToClipboard(binding.mangaArtistLabel.text.toString(), binding.mangaArtist.text.toString())
}
.launchIn(scope)
binding.mangaArtist.clicks()
.onEach {
var text = binding.mangaArtist.text.toString()
if (isEHentaiBasedSource()) {
text = wrapTag("artist", text)
}
performGlobalSearch(text)
}
.launchIn(scope)
binding.mangaAuthor.longClicks()
.onEach {
// EXH Special case E-Hentai/ExHentai to ignore author field (unused)
if (!isEHentaiBasedSource()) {
activity?.copyToClipboard(binding.mangaAuthor.text.toString(), binding.mangaAuthor.text.toString())
}
}
.launchIn(scope)
binding.mangaAuthor.clicks()
.onEach {
// EXH Special case E-Hentai/ExHentai to ignore author field (unused)
if (!isEHentaiBasedSource()) {
performGlobalSearch(binding.mangaAuthor.text.toString())
}
}
.launchIn(scope)
binding.mangaSummary.longClicks()
.onEach {
activity?.copyToClipboard(view.context.getString(R.string.description), binding.mangaSummary.text.toString())
}
.launchIn(scope)
binding.mangaCover.longClicks()
.onEach {
activity?.copyToClipboard(view.context.getString(R.string.title), presenter.manga.title)
}
.launchIn(scope)
// EXH -->
if (smartSearchConfig == null) {
binding.recommendBtn.visible()
binding.recommendBtn.clicks()
.onEach { openRecommends() }
.launchIn(scope)
}
smartSearchConfig?.let { smartSearchConfig ->
if (smartSearchConfig.origMangaId != null) { binding.mergeBtn.visible() }
binding.mergeBtn.clicks()
.onEach {
// Init presenter here to avoid threading issues
presenter
launch {
try {
val mergedManga = withContext(Dispatchers.IO + NonCancellable) {
presenter.smartSearchMerge(presenter.manga, smartSearchConfig.origMangaId!!)
}
router?.pushController(
MangaAllInOneController(
mergedManga,
true,
update = true
).withFadeTransaction()
)
applicationContext?.toast("Manga merged!")
} catch (e: Exception) {
if (e is CancellationException) throw e
else {
applicationContext?.toast("Failed to merge manga: ${e.message}")
}
}
}
}
.launchIn(scope)
}
// EXH <--
if (manga == null || source == null) return
// Init RecyclerView and adapter
adapter = ChaptersAdapter(this, view.context)
adapter = MangaAllInOneAdapter(this, view.context)
binding.recycler.adapter = adapter
binding.recycler.layoutManager = LinearLayoutManager(view.context)
@ -421,8 +239,28 @@ class MangaAllInOneController :
binding.fab.offsetAppbarHeight(activity!!)
}
private fun getHeader(): MangaAllInOneHolder? {
return binding.recycler.findViewHolderForAdapterPosition(0) as? MangaAllInOneHolder
}
private fun addMangaHeader() {
if (adapter?.scrollableHeaders?.isEmpty() == true) {
adapter?.removeAllScrollableHeaders()
adapter?.addScrollableHeader(presenter.headerItem)
}
}
fun updateHeader() {
// binding.swipeRefresh?.isRefreshing = presenter.isLoading
adapter?.updateDataSet(presenter.chapters)
addMangaHeader()
activity?.invalidateOptionsMenu()
}
fun refreshAdapter() = adapter?.notifyDataSetChanged()
// EXH -->
private fun openSmartSearch() {
override fun openSmartSearch() {
val smartSearchConfig = SourceController.SmartSearchConfig(presenter.manga.title, presenter.manga.id!!)
router?.pushController(
@ -433,10 +271,48 @@ class MangaAllInOneController :
).withFadeTransaction()
)
}
override suspend fun mergeWithAnother() {
try {
val mergedManga = withContext(Dispatchers.IO + NonCancellable) {
presenter.smartSearchMerge(presenter.manga, smartSearchConfig?.origMangaId!!)
}
router?.pushController(
MangaAllInOneController(
mergedManga,
true,
update = true
).withFadeTransaction()
)
applicationContext?.toast("Manga merged!")
} catch (e: Exception) {
if (e is CancellationException) throw e
else {
applicationContext?.toast("Failed to merge manga: ${e.message}")
}
}
}
override fun copyToClipboard(label: String, text: String) {
activity!!.copyToClipboard(label, text)
}
override fun migrateManga() {
PreMigrationController.navigateToMigration(
preferences.skipPreMigration().get(),
router,
listOf(presenter.manga.id!!)
)
}
override fun mangaPresenter(): MangaAllInOnePresenter {
return presenter
}
// EXH <--
// AZ -->
private fun openRecommends() {
override fun openRecommends() {
val recommendsConfig = BrowseSourceController.RecommendsConfig(presenter.manga)
router?.pushController(
@ -449,7 +325,7 @@ class MangaAllInOneController :
}
// AZ <--
private fun openTracking() {
override fun openTracking() {
router?.pushController(
TrackController(fromAllInOne = true, manga = manga).withFadeTransaction()
)
@ -463,7 +339,7 @@ class MangaAllInOneController :
* @param manga manga object containing information about manga.
* @param source the source of the manga.
*/
fun onNextManga(manga: Manga, source: Source, chapters: List<ChapterItem>) {
override fun onNextManga(manga: Manga, source: Source, chapters: List<MangaAllInOneChapterItem>) {
if (manga.initialized) {
// Update view.
setMangaInfo(manga, source, chapters)
@ -482,121 +358,9 @@ class MangaAllInOneController :
* @param manga manga object containing information about manga.
* @param source the source of the manga.
*/
private fun setMangaInfo(manga: Manga, source: Source?, chapters: List<ChapterItem>) {
override fun setMangaInfo(manga: Manga, source: Source?, chapters: List<MangaAllInOneChapterItem>) {
val view = view ?: return
// update full title TextView.
binding.mangaFullTitle.text = if (manga.title.isBlank()) {
view.context.getString(R.string.unknown)
} else {
manga.title
}
// Update artist TextView.
binding.mangaArtist.text = if (manga.artist.isNullOrBlank()) {
view.context.getString(R.string.unknown)
} else {
manga.artist
}
// Update author TextView.
binding.mangaAuthor.text = if (manga.author.isNullOrBlank()) {
view.context.getString(R.string.unknown)
} else {
manga.author
}
// If manga source is known update source TextView.
val mangaSource = source?.toString()
with(binding.mangaSource) {
// EXH -->
if (mangaSource == null) {
text = view.context.getString(R.string.unknown)
} else if (source.id == MERGED_SOURCE_ID) {
text = MergedSource.MangaConfig.readFromUrl(gson, manga.url).children.map {
sourceManager.getOrStub(it.source).toString()
}.distinct().joinToString()
} else {
text = mangaSource
setOnClickListener {
val sourceManager = Injekt.get<SourceManager>()
performSearch(sourceManager.getOrStub(source.id).name)
}
}
// EXH <--
}
// EXH -->
if (source?.id == MERGED_SOURCE_ID) {
binding.mangaSourceLabel.text = "Sources"
} else {
binding.mangaSourceLabel.setText(R.string.manga_info_source_label)
}
// EXH <--
// Update status TextView.
binding.mangaStatus.setText(
when (manga.status) {
SManga.ONGOING -> R.string.ongoing
SManga.COMPLETED -> R.string.completed
SManga.LICENSED -> R.string.licensed
else -> R.string.unknown
}
)
// Set the favorite drawable to the correct one.
setFavoriteButtonState(manga.favorite)
// Set cover if it wasn't already.
val mangaThumbnail = manga.toMangaThumbnail()
GlideApp.with(view.context)
.load(mangaThumbnail)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(binding.mangaCover)
binding.backdrop?.let {
GlideApp.with(view.context)
.load(mangaThumbnail)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(it)
}
// Manga info section
if (manga.description.isNullOrBlank() && manga.genre.isNullOrBlank()) {
hideMangaInfo()
} else {
// Update description TextView.
binding.mangaSummary.text = if (manga.description.isNullOrBlank()) {
view.context.getString(R.string.unknown)
} else {
manga.description
}
// Update genres list
if (!manga.genre.isNullOrBlank()) {
binding.mangaGenresTagsCompactChips.setChipsExtended(manga.getGenres(), this::performSearch, this::performGlobalSearch, manga.source)
binding.mangaGenresTagsFullChips.setChipsExtended(manga.getGenres(), this::performSearch, this::performGlobalSearch, manga.source)
} else {
binding.mangaGenresTagsWrapper.gone()
}
// Handle showing more or less info
binding.mangaSummary.clicks()
.onEach { toggleMangaInfo(view.context) }
.launchIn(scope)
binding.mangaInfoToggle.clicks()
.onEach { toggleMangaInfo(view.context) }
.launchIn(scope)
// Expand manga info if navigated from source listing
if (initialLoad && fromSource) {
toggleMangaInfo(view.context)
initialLoad = false
}
}
if (update ||
// Auto-update old format galleries
(
@ -610,6 +374,7 @@ class MangaAllInOneController :
val adapter = adapter ?: return
adapter.updateDataSet(chapters)
addMangaHeader()
if (selectedItems.isNotEmpty()) {
adapter.clearSelection() // we need to start from a clean state, index may have changed
@ -624,90 +389,7 @@ class MangaAllInOneController :
}
}
private fun hideMangaInfo() {
binding.mangaSummaryLabel.gone()
binding.mangaSummary.gone()
binding.mangaGenresTagsWrapper.gone()
binding.mangaInfoToggle.gone()
}
private fun toggleMangaInfo(context: Context) {
val isExpanded = binding.mangaInfoToggle.text == context.getString(R.string.manga_info_collapse)
binding.mangaInfoToggle.text =
if (isExpanded) {
context.getString(R.string.manga_info_expand)
} else {
context.getString(R.string.manga_info_collapse)
}
with(binding.mangaSummary) {
maxLines =
if (isExpanded) {
3
} else {
Int.MAX_VALUE
}
ellipsize =
if (isExpanded) {
TextUtils.TruncateAt.END
} else {
null
}
}
binding.mangaGenresTagsCompact.visibleIf { isExpanded }
binding.mangaGenresTagsFullChips.visibleIf { !isExpanded }
}
/**
* Update chapter count TextView.
*
* @param count number of chapters.
*/
fun setChapterCount(count: Float) {
if (count > 0f) {
binding.mangaChapters.text = DecimalFormat("#.#").format(count)
} else {
binding.mangaChapters.text = resources?.getString(R.string.unknown)
}
}
fun setLastUpdateDate(date: Date) {
if (date.time != 0L) {
binding.mangaLastUpdate.text = dateFormat.format(date)
} else {
binding.mangaLastUpdate.text = resources?.getString(R.string.unknown)
}
}
/**
* Toggles the favorite status and asks for confirmation to delete downloaded chapters.
*/
private fun toggleFavorite() {
val view = view
val isNowFavorite = presenter.toggleFavorite()
if (view != null && !isNowFavorite && presenter.hasDownloads()) {
view.snack(view.context.getString(R.string.delete_downloads_for_manga)) {
setAction(R.string.action_delete) {
presenter.deleteDownloads()
}
}
}
binding.btnCategories.visibleIf { isNowFavorite && presenter.getCategories().isNotEmpty() }
if (isNowFavorite) {
binding.btnSmartSearch.visible()
binding.btnMigrate.visible()
} else {
binding.btnSmartSearch.gone()
binding.btnMigrate.gone()
}
}
private fun openInWebView() {
override fun openInWebView() {
val source = presenter.source as? HttpSource ?: return
val url = try {
@ -724,7 +406,7 @@ class MangaAllInOneController :
/**
* Called to run Intent with [Intent.ACTION_SEND], which show share dialog.
*/
private fun shareManga() {
override fun shareManga() {
val context = view?.context ?: return
val source = presenter.source as? HttpSource ?: return
@ -740,38 +422,23 @@ class MangaAllInOneController :
}
}
/**
* Update favorite button with correct drawable and text.
*
* @param isFavorite determines if manga is favorite or not.
*/
fun setFavoriteButtonState(isFavorite: Boolean) {
// Set the Favorite drawable to the correct one.
// Border drawable if false, filled drawable if true.
binding.btnFavorite.apply {
icon = ContextCompat.getDrawable(context, if (isFavorite) R.drawable.ic_favorite_24dp else R.drawable.ic_favorite_border_24dp)
text = context.getString(if (isFavorite) R.string.in_library else R.string.add_to_library)
isChecked = isFavorite
}
}
/**
* Start fetching manga information from source.
*/
private fun fetchMangaFromSource(manualFetch: Boolean = false, fetchManga: Boolean = true, fetchChapters: Boolean = true) {
override fun fetchMangaFromSource(manualFetch: Boolean, fetchManga: Boolean, fetchChapters: Boolean) {
setRefreshing(true)
// Call presenter and start fetching manga information
presenter.fetchMangaFromSource(manualFetch, fetchManga, fetchChapters)
}
fun onFetchMangaDone() {
override fun onFetchMangaDone() {
setRefreshing(false)
}
/**
* Update swipe refresh to start showing refresh in progress spinner.
*/
fun onFetchMangaError(error: Throwable) {
override fun onFetchMangaError(error: Throwable) {
setRefreshing(false)
activity?.toast(error.message)
}
@ -781,15 +448,15 @@ class MangaAllInOneController :
*
* @param value whether it should be refreshing or not.
*/
fun setRefreshing(value: Boolean) {
override fun setRefreshing(value: Boolean) {
binding.swipeRefresh.isRefreshing = value
}
private fun onFavoriteClick() {
override fun onFavoriteClick() {
val manga = presenter.manga
if (manga.favorite) {
toggleFavorite()
getHeader()?.toggleFavorite()
activity?.toast(activity?.getString(R.string.manga_removed_library))
} else {
val categories = presenter.getCategories()
@ -799,14 +466,14 @@ class MangaAllInOneController :
when {
// Default category set
defaultCategory != null -> {
toggleFavorite()
getHeader()?.toggleFavorite()
presenter.moveMangaToCategory(manga, defaultCategory)
activity?.toast(activity?.getString(R.string.manga_added_library))
}
// Automatic 'Default' or no categories
defaultCategoryId == 0 || categories.isEmpty() -> {
toggleFavorite()
getHeader()?.toggleFavorite()
presenter.moveMangaToCategory(manga, null)
activity?.toast(activity?.getString(R.string.manga_added_library))
}
@ -825,7 +492,7 @@ class MangaAllInOneController :
}
}
private fun onCategoriesClick() {
override fun onCategoriesClick() {
val manga = presenter.manga
val categories = presenter.getCategories()
@ -842,7 +509,7 @@ class MangaAllInOneController :
val manga = mangas.firstOrNull() ?: return
if (!manga.favorite) {
toggleFavorite()
getHeader()?.toggleFavorite()
activity?.toast(activity?.getString(R.string.manga_added_library))
}
@ -854,13 +521,31 @@ class MangaAllInOneController :
*
* @param query the search query to pass to the search controller
*/
private fun performGlobalSearch(query: String) {
override fun performGlobalSearch(query: String) {
val router = router ?: return
router.pushController(GlobalSearchController(query).withFadeTransaction())
}
fun setChapterCount(count: Float) {
getHeader()?.setChapterCount(count)
}
fun setLastUpdateDate(date: Date) {
getHeader()?.setLastUpdateDate(date)
}
fun setFavoriteButtonState(isFavorite: Boolean) {
getHeader()?.setFavoriteButtonState(isFavorite)
}
override fun isInitialLoadAndFromSource() = fromSource && initialLoad
override fun removeInitialLoad() {
initialLoad = false
}
// --> EH
private fun wrapTag(namespace: String, tag: String) =
override fun wrapTag(namespace: String, tag: String) =
if (tag.contains(' ')) {
"$namespace:\"$tag$\""
} else {
@ -869,7 +554,7 @@ class MangaAllInOneController :
private fun parseTag(tag: String) = tag.substringBefore(':').trim() to tag.substringAfter(':').trim()
private fun isEHentaiBasedSource(): Boolean {
override fun isEHentaiBasedSource(): Boolean {
val sourceId = presenter.source.id
return sourceId == EH_SOURCE_ID ||
sourceId == EXH_SOURCE_ID
@ -881,7 +566,7 @@ class MangaAllInOneController :
*
* @param query the search query to the previous controller
*/
private fun performSearch(query: String) {
override fun performSearch(query: String) {
val router = router ?: return
if (router.backstackSize < 2) {
@ -1039,8 +724,8 @@ class MangaAllInOneController :
getHolder(download.chapter)?.notifyStatus(download.status)
}
private fun getHolder(chapter: Chapter): ChapterHolder? {
return binding.recycler.findViewHolderForItemId(chapter.id!!) as? ChapterHolder
private fun getHolder(chapter: Chapter): MangaAllInOneChapterHolder? {
return binding.recycler.findViewHolderForItemId(chapter.id!!) as? MangaAllInOneChapterHolder
}
fun openChapter(chapter: Chapter, hasAnimation: Boolean = false) {
@ -1054,7 +739,7 @@ class MangaAllInOneController :
override fun onItemClick(view: View?, position: Int): Boolean {
val adapter = adapter ?: return false
val item = adapter.getItem(position) ?: return false
val item = adapter.getItem(position) as MangaAllInOneChapterItem? ?: return false
return if (actionMode != null && adapter.mode == SelectableAdapter.Mode.MULTI) {
lastClickPosition = position
toggleSelection(position)
@ -1089,7 +774,7 @@ class MangaAllInOneController :
adapter.toggleSelection(position)
adapter.notifyDataSetChanged()
if (adapter.isSelected(position)) {
selectedItems.add(item)
selectedItems.add(item as MangaAllInOneChapterItem)
} else {
selectedItems.remove(item)
}
@ -1101,14 +786,14 @@ class MangaAllInOneController :
val item = adapter.getItem(position) ?: return
if (!adapter.isSelected(position)) {
adapter.toggleSelection(position)
selectedItems.add(item)
selectedItems.add(item as MangaAllInOneChapterItem)
actionMode?.invalidate()
}
}
private fun getSelectedChapters(): List<ChapterItem> {
private fun getSelectedChapters(): List<MangaAllInOneChapterItem> {
val adapter = adapter ?: return emptyList()
return adapter.selectedPositions.mapNotNull { adapter.getItem(it) }
return adapter.selectedPositions.mapNotNull { adapter.getItem(it) as MangaAllInOneChapterItem }
}
private fun createActionModeIfNeeded() {
@ -1209,24 +894,24 @@ class MangaAllInOneController :
for (i in 0..adapter.itemCount) {
adapter.toggleSelection(i)
}
selectedItems.addAll(adapter.selectedPositions.mapNotNull { adapter.getItem(it) })
selectedItems.addAll(adapter.selectedPositions.mapNotNull { adapter.getItem(it) as MangaAllInOneChapterItem })
actionMode?.invalidate()
adapter.notifyDataSetChanged()
}
private fun markAsRead(chapters: List<ChapterItem>) {
private fun markAsRead(chapters: List<MangaAllInOneChapterItem>) {
presenter.markChaptersRead(chapters, true)
if (presenter.preferences.removeAfterMarkedAsRead()) {
deleteChapters(chapters)
}
}
private fun markAsUnread(chapters: List<ChapterItem>) {
private fun markAsUnread(chapters: List<MangaAllInOneChapterItem>) {
presenter.markChaptersRead(chapters, false)
}
private fun downloadChapters(chapters: List<ChapterItem>) {
private fun downloadChapters(chapters: List<MangaAllInOneChapterItem>) {
val view = view
presenter.downloadChapters(chapters)
if (view != null && !presenter.manga.favorite) {
@ -1246,7 +931,7 @@ class MangaAllInOneController :
deleteChapters(getSelectedChapters())
}
private fun markPreviousAsRead(chapters: List<ChapterItem>) {
private fun markPreviousAsRead(chapters: List<MangaAllInOneChapterItem>) {
val adapter = adapter ?: return
val prevChapters = if (presenter.sortDescending()) adapter.items.reversed() else adapter.items
val chapterPos = prevChapters.indexOf(chapters.last())
@ -1255,17 +940,17 @@ class MangaAllInOneController :
}
}
private fun bookmarkChapters(chapters: List<ChapterItem>, bookmarked: Boolean) {
private fun bookmarkChapters(chapters: List<MangaAllInOneChapterItem>, bookmarked: Boolean) {
presenter.bookmarkChapters(chapters, bookmarked)
}
fun deleteChapters(chapters: List<ChapterItem>) {
fun deleteChapters(chapters: List<MangaAllInOneChapterItem>) {
if (chapters.isEmpty()) return
presenter.deleteChapters(chapters)
}
fun onChaptersDeleted(chapters: List<ChapterItem>) {
fun onChaptersDeleted(chapters: List<MangaAllInOneChapterItem>) {
// this is needed so the downloaded text gets removed from the item
chapters.forEach {
adapter?.updateItem(it)

View File

@ -0,0 +1,48 @@
package eu.kanade.tachiyomi.ui.manga
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.browse.source.SourceController
class MangaAllInOneHeaderItem(val manga: Manga, val source: Source, var smartSearchConfig: SourceController.SmartSearchConfig? = null) :
AbstractFlexibleItem<MangaAllInOneHolder>() {
override fun getLayoutRes(): Int {
return R.layout.manga_all_in_one_header
}
override fun isSelectable(): Boolean {
return false
}
override fun isSwipeable(): Boolean {
return false
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MangaAllInOneHolder {
return MangaAllInOneHolder(view, adapter as MangaAllInOneAdapter, smartSearchConfig)
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: MangaAllInOneHolder,
position: Int,
payloads: MutableList<Any?>?
) {
holder.bind(this, manga, source)
}
override fun equals(other: Any?): Boolean {
return (this === other)
}
override fun hashCode(): Int {
return -(manga.id).hashCode()
}
}

View File

@ -0,0 +1,440 @@
package eu.kanade.tachiyomi.ui.manga
import android.content.Context
import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.view.accessibility.AccessibilityEventCompat.setAction
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.gson.Gson
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
import eu.kanade.tachiyomi.data.preference.PreferenceKeys.dateFormat
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.ui.browse.source.SourceController
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.util.view.visibleIf
import exh.MERGED_SOURCE_ID
import exh.debug.DebugFunctions.sourceManager
import exh.util.setChipsExtended
import java.text.DecimalFormat
import java.util.Date
import kotlinx.android.synthetic.main.manga_all_in_one_header.backdrop
import kotlinx.android.synthetic.main.manga_all_in_one_header.btn_categories
import kotlinx.android.synthetic.main.manga_all_in_one_header.btn_favorite
import kotlinx.android.synthetic.main.manga_all_in_one_header.btn_migrate
import kotlinx.android.synthetic.main.manga_all_in_one_header.btn_share
import kotlinx.android.synthetic.main.manga_all_in_one_header.btn_smart_search
import kotlinx.android.synthetic.main.manga_all_in_one_header.btn_tracking
import kotlinx.android.synthetic.main.manga_all_in_one_header.btn_webview
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_artist
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_artist_label
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_author
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_author_label
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_chapters
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_cover
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_full_title
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_genres_tags_compact
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_genres_tags_compact_chips
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_genres_tags_full_chips
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_genres_tags_wrapper
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_info_toggle
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_last_update
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_source
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_source_label
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_status
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_summary
import kotlinx.android.synthetic.main.manga_all_in_one_header.manga_summary_label
import kotlinx.android.synthetic.main.manga_all_in_one_header.merge_btn
import kotlinx.android.synthetic.main.manga_all_in_one_header.recommend_btn
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import reactivecircus.flowbinding.android.view.clicks
import reactivecircus.flowbinding.android.view.longClicks
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class MangaAllInOneHolder(
view: View,
private val adapter: MangaAllInOneAdapter,
smartSearchConfig: SourceController.SmartSearchConfig? = null
) : BaseFlexibleViewHolder(view, adapter) {
private val gson: Gson by injectLazy()
init {
val presenter = adapter.delegate.mangaPresenter()
// Setting this via XML doesn't work
manga_cover.clipToOutline = true
btn_favorite.clicks()
.onEach { adapter.delegate.onFavoriteClick() }
.launchIn(adapter.delegate.controllerScope)
if ((Injekt.get<TrackManager>().hasLoggedServices()) && presenter.manga.favorite) {
btn_tracking.visible()
}
adapter.delegate.controllerScope.launch(Dispatchers.IO) {
if (Injekt.get<DatabaseHelper>().getTracks(presenter.manga).executeAsBlocking().any {
val status = Injekt.get<TrackManager>().getService(it.sync_id)?.getStatus(it.status)
status != null
}
) {
withContext(Dispatchers.Main) {
btn_tracking.icon = itemView.context.getDrawable(R.drawable.ic_cloud_white_24dp)
}
}
}
btn_tracking.clicks()
.onEach { adapter.delegate.openTracking() }
.launchIn(adapter.delegate.controllerScope)
if (presenter.manga.favorite && presenter.getCategories().isNotEmpty()) {
btn_categories.visible()
}
btn_categories.clicks()
.onEach { adapter.delegate.onCategoriesClick() }
.launchIn(adapter.delegate.controllerScope)
if (presenter.source is HttpSource) {
btn_webview.visible()
btn_share.visible()
btn_webview.clicks()
.onEach { adapter.delegate.openInWebView() }
.launchIn(adapter.delegate.controllerScope)
btn_share.clicks()
.onEach { adapter.delegate.shareManga() }
.launchIn(adapter.delegate.controllerScope)
}
if (presenter.manga.favorite) {
btn_migrate.visible()
btn_smart_search.visible()
}
btn_migrate.clicks()
.onEach {
adapter.delegate.migrateManga()
}
.launchIn(adapter.delegate.controllerScope)
btn_smart_search.clicks()
.onEach { adapter.delegate.openSmartSearch() }
.launchIn(adapter.delegate.controllerScope)
manga_full_title.longClicks()
.onEach {
adapter.delegate.copyToClipboard(view.context.getString(R.string.title), manga_full_title.text.toString())
}
.launchIn(adapter.delegate.controllerScope)
manga_full_title.clicks()
.onEach {
adapter.delegate.performGlobalSearch(manga_full_title.text.toString())
}
.launchIn(adapter.delegate.controllerScope)
manga_artist.longClicks()
.onEach {
adapter.delegate.copyToClipboard(manga_artist_label.text.toString(), manga_artist.text.toString())
}
.launchIn(adapter.delegate.controllerScope)
manga_artist.clicks()
.onEach {
var text = manga_artist.text.toString()
if (adapter.delegate.isEHentaiBasedSource()) {
text = adapter.delegate.wrapTag("artist", text)
}
adapter.delegate.performGlobalSearch(text)
}
.launchIn(adapter.delegate.controllerScope)
manga_author.longClicks()
.onEach {
// EXH Special case E-Hentai/ExHentai to ignore author field (unused)
if (!adapter.delegate.isEHentaiBasedSource()) {
adapter.delegate.copyToClipboard(manga_author_label.text.toString(), manga_author.text.toString())
}
}
.launchIn(adapter.delegate.controllerScope)
manga_author.clicks()
.onEach {
// EXH Special case E-Hentai/ExHentai to ignore author field (unused)
if (!adapter.delegate.isEHentaiBasedSource()) {
adapter.delegate.performGlobalSearch(manga_author.text.toString())
}
}
.launchIn(adapter.delegate.controllerScope)
manga_summary.longClicks()
.onEach {
adapter.delegate.copyToClipboard(view.context.getString(R.string.description), manga_summary.text.toString())
}
.launchIn(adapter.delegate.controllerScope)
manga_cover.longClicks()
.onEach {
adapter.delegate.copyToClipboard(view.context.getString(R.string.title), presenter.manga.title)
}
.launchIn(adapter.delegate.controllerScope)
// EXH -->
if (smartSearchConfig == null) {
recommend_btn.visible()
recommend_btn.clicks()
.onEach { adapter.delegate.openRecommends() }
.launchIn(adapter.delegate.controllerScope)
}
smartSearchConfig?.let { smartSearchConfig ->
if (smartSearchConfig.origMangaId != null) { merge_btn.visible() }
merge_btn.clicks()
.onEach {
adapter.delegate.mergeWithAnother()
}
.launchIn(adapter.delegate.controllerScope)
}
// EXH <--
}
fun bind(item: MangaAllInOneHeaderItem, manga: Manga, source: Source?) {
val presenter = adapter.delegate.mangaPresenter()
manga_full_title.text = if (manga.title.isBlank()) {
itemView.context.getString(R.string.unknown)
} else {
manga.title
}
// Update artist TextView.
manga_artist.text = if (manga.artist.isNullOrBlank()) {
itemView.context.getString(R.string.unknown)
} else {
manga.artist
}
// Update author TextView.
manga_author.text = if (manga.author.isNullOrBlank()) {
itemView.context.getString(R.string.unknown)
} else {
manga.author
}
// If manga source is known update source TextView.
val mangaSource = source?.toString()
with(manga_source) {
// EXH -->
if (mangaSource == null) {
text = itemView.context.getString(R.string.unknown)
} else if (source.id == MERGED_SOURCE_ID) {
text = eu.kanade.tachiyomi.source.online.all.MergedSource.MangaConfig.readFromUrl(gson, manga.url).children.map {
sourceManager.getOrStub(it.source).toString()
}.distinct().joinToString()
} else {
text = mangaSource
setOnClickListener {
val sourceManager = Injekt.get<SourceManager>()
adapter.delegate.performSearch(sourceManager.getOrStub(source.id).name)
}
}
// EXH <--
}
// EXH -->
if (source?.id == MERGED_SOURCE_ID) {
manga_source_label.text = "Sources"
} else {
manga_source_label.setText(R.string.manga_info_source_label)
}
// EXH <--
// Update status TextView.
manga_status.setText(
when (manga.status) {
SManga.ONGOING -> R.string.ongoing
SManga.COMPLETED -> R.string.completed
SManga.LICENSED -> R.string.licensed
else -> R.string.unknown
}
)
// Set the favorite drawable to the correct one.
setFavoriteButtonState(manga.favorite)
// Set cover if it wasn't already.
val mangaThumbnail = manga.toMangaThumbnail()
GlideApp.with(itemView.context)
.load(mangaThumbnail)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(manga_cover)
backdrop?.let {
GlideApp.with(itemView.context)
.load(mangaThumbnail)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(it)
}
// Manga info section
if (manga.description.isNullOrBlank() && manga.genre.isNullOrBlank()) {
hideMangaInfo()
} else {
// Update description TextView.
manga_summary.text = if (manga.description.isNullOrBlank()) {
itemView.context.getString(R.string.unknown)
} else {
manga.description
}
// Update genres list
if (!manga.genre.isNullOrBlank()) {
manga_genres_tags_compact_chips.setChipsExtended(manga.getGenres(), this::performSearch, this::performGlobalSearch, manga.source)
manga_genres_tags_full_chips.setChipsExtended(manga.getGenres(), this::performSearch, this::performGlobalSearch, manga.source)
} else {
manga_genres_tags_wrapper.gone()
}
// Handle showing more or less info
manga_summary.clicks()
.onEach { toggleMangaInfo(itemView.context) }
.launchIn(adapter.delegate.controllerScope)
manga_info_toggle.clicks()
.onEach { toggleMangaInfo(itemView.context) }
.launchIn(adapter.delegate.controllerScope)
// Expand manga info if navigated from source listing
if (adapter.delegate.isInitialLoadAndFromSource()) {
adapter.delegate.removeInitialLoad()
toggleMangaInfo(itemView.context)
}
}
}
private fun hideMangaInfo() {
manga_summary_label.gone()
manga_summary.gone()
manga_genres_tags_wrapper.gone()
manga_info_toggle.gone()
}
fun toggleMangaInfo(context: Context) {
val isExpanded = manga_info_toggle.text == context.getString(R.string.manga_info_collapse)
manga_info_toggle.text =
if (isExpanded) {
context.getString(R.string.manga_info_expand)
} else {
context.getString(R.string.manga_info_collapse)
}
with(manga_summary) {
maxLines =
if (isExpanded) {
3
} else {
Int.MAX_VALUE
}
ellipsize =
if (isExpanded) {
android.text.TextUtils.TruncateAt.END
} else {
null
}
}
manga_genres_tags_compact.visibleIf { isExpanded }
manga_genres_tags_full_chips.visibleIf { !isExpanded }
}
/**
* Update chapter count TextView.
*
* @param count number of chapters.
*/
fun setChapterCount(count: Float) {
if (count > 0f) {
manga_chapters.text = DecimalFormat("#.#").format(count)
} else {
manga_chapters.text = itemView.context.getString(R.string.unknown)
}
}
fun setLastUpdateDate(date: Date) {
if (date.time != 0L) {
manga_last_update.text = dateFormat.format(date)
} else {
manga_last_update.text = itemView.context.getString(R.string.unknown)
}
}
/**
* Toggles the favorite status and asks for confirmation to delete downloaded chapters.
*/
fun toggleFavorite() {
val presenter = adapter.delegate.mangaPresenter()
val isNowFavorite = presenter.toggleFavorite()
if (itemView != null && !isNowFavorite && presenter.hasDownloads()) {
itemView.snack(itemView.context.getString(R.string.delete_downloads_for_manga)) {
setAction(R.string.action_delete) {
presenter.deleteDownloads()
}
}
}
btn_categories.visibleIf { isNowFavorite && presenter.getCategories().isNotEmpty() }
if (isNowFavorite) {
btn_smart_search.visible()
btn_migrate.visible()
} else {
btn_smart_search.gone()
btn_migrate.gone()
}
}
/**
* Update favorite button with correct drawable and text.
*
* @param isFavorite determines if manga is favorite or not.
*/
fun setFavoriteButtonState(isFavorite: Boolean) {
// Set the Favorite drawable to the correct one.
// Border drawable if false, filled drawable if true.
btn_favorite.apply {
icon = ContextCompat.getDrawable(context, if (isFavorite) R.drawable.ic_favorite_24dp else R.drawable.ic_favorite_border_24dp)
text = context.getString(if (isFavorite) R.string.in_library else R.string.add_to_library)
isChecked = isFavorite
}
}
private fun performSearch(query: String) {
adapter.delegate.performSearch(query)
}
private fun performGlobalSearch(query: String) {
adapter.delegate.performGlobalSearch(query)
}
}

View File

@ -18,8 +18,8 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.source.SourceController
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter
import eu.kanade.tachiyomi.ui.manga.chapter.MangaAllInOneChapterItem
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.removeCovers
@ -64,7 +64,7 @@ class MangaAllInOnePresenter(
/**
* List of chapters of the manga. It's always unfiltered and unsorted.
*/
var chapters: List<ChapterItem> = emptyList()
var chapters: List<MangaAllInOneChapterItem> = emptyList()
private set
private val scope = CoroutineScope(Job() + Dispatchers.Default)
@ -86,6 +86,8 @@ class MangaAllInOnePresenter(
private val redirectUserRelay = BehaviorRelay.create<ChaptersPresenter.EXHRedirect>()
// EXH <--
var headerItem = MangaAllInOneHeaderItem(manga, source, smartSearchConfig)
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
@ -385,9 +387,9 @@ class MangaAllInOnePresenter(
/**
* Converts a chapter from the database to an extended model, allowing to store new fields.
*/
private fun Chapter.toModel(): ChapterItem {
private fun Chapter.toModel(): MangaAllInOneChapterItem {
// Create the model object.
val model = ChapterItem(this, manga)
val model = MangaAllInOneChapterItem(this, manga)
// Find an active download for this chapter.
val download = downloadManager.queue.find { it.chapter.id == id }
@ -404,7 +406,7 @@ class MangaAllInOnePresenter(
*
* @param chapters the list of chapter from the database.
*/
private fun setDownloadedChapters(chapters: List<ChapterItem>) {
private fun setDownloadedChapters(chapters: List<MangaAllInOneChapterItem>) {
for (chapter in chapters) {
if (downloadManager.isChapterDownloaded(chapter, manga)) {
chapter.status = Download.DOWNLOADED
@ -417,7 +419,7 @@ class MangaAllInOnePresenter(
* @param chapters the list of chapters from the database
* @return an observable of the list of chapters filtered and sorted.
*/
private fun applyChapterFilters(chapterList: List<ChapterItem>): List<ChapterItem> {
private fun applyChapterFilters(chapterList: List<MangaAllInOneChapterItem>): List<MangaAllInOneChapterItem> {
var chapters = chapterList
if (onlyUnread()) {
chapters = chapters.filter { !it.read }
@ -468,7 +470,7 @@ class MangaAllInOnePresenter(
/**
* Returns the next unread chapter or null if everything is read.
*/
fun getNextUnreadChapter(): ChapterItem? {
fun getNextUnreadChapter(): MangaAllInOneChapterItem? {
return chapters.sortedByDescending { it.source_order }.find { !it.read }
}
@ -477,7 +479,7 @@ class MangaAllInOnePresenter(
* @param selectedChapters the list of selected chapters.
* @param read whether to mark chapters as read or unread.
*/
fun markChaptersRead(selectedChapters: List<ChapterItem>, read: Boolean) {
fun markChaptersRead(selectedChapters: List<MangaAllInOneChapterItem>, read: Boolean) {
Observable.from(selectedChapters)
.doOnNext { chapter ->
chapter.read = read
@ -498,7 +500,7 @@ class MangaAllInOnePresenter(
* Downloads the given list of chapters with the manager.
* @param chapters the list of chapters to download.
*/
fun downloadChapters(chapters: List<ChapterItem>) {
fun downloadChapters(chapters: List<MangaAllInOneChapterItem>) {
downloadManager.downloadChapters(manga, chapters)
}
@ -506,7 +508,7 @@ class MangaAllInOnePresenter(
* Bookmarks the given list of chapters.
* @param selectedChapters the list of chapters to bookmark.
*/
fun bookmarkChapters(selectedChapters: List<ChapterItem>, bookmarked: Boolean) {
fun bookmarkChapters(selectedChapters: List<MangaAllInOneChapterItem>, bookmarked: Boolean) {
Observable.from(selectedChapters)
.doOnNext { chapter ->
chapter.bookmark = bookmarked
@ -521,7 +523,7 @@ class MangaAllInOnePresenter(
* Deletes the given list of chapter.
* @param chapters the list of chapters to delete.
*/
fun deleteChapters(chapters: List<ChapterItem>) {
fun deleteChapters(chapters: List<MangaAllInOneChapterItem>) {
Observable.just(chapters)
.doOnNext { deleteChaptersInternal(chapters) }
.doOnNext { if (onlyDownloaded()) updateChaptersView() }
@ -539,7 +541,7 @@ class MangaAllInOnePresenter(
* Deletes a list of chapters from disk. This method is called in a background thread.
* @param chapters the chapters to delete.
*/
private fun deleteChaptersInternal(chapters: List<ChapterItem>) {
private fun deleteChaptersInternal(chapters: List<MangaAllInOneChapterItem>) {
downloadManager.deleteChapters(chapters, manga, source)
chapters.forEach {
it.status = Download.NOT_DOWNLOADED

View File

@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.ui.manga.MangaAllInOneAdapter
import eu.kanade.tachiyomi.util.view.visibleIf
import java.util.Date
import kotlinx.android.synthetic.main.chapters_item.bookmark_icon
@ -76,3 +77,65 @@ class ChapterHolder(
}
}
}
class MangaAllInOneChapterHolder(
view: View,
private val adapter: MangaAllInOneAdapter
) : BaseFlexibleViewHolder(view, adapter) {
fun bind(item: MangaAllInOneChapterItem, manga: Manga) {
val chapter = item.chapter
chapter_title.text = when (manga.displayMode) {
Manga.DISPLAY_NUMBER -> {
val number = adapter.decimalFormat.format(chapter.chapter_number.toDouble())
itemView.context.getString(R.string.display_mode_chapter, number)
}
else -> chapter.name
}
// Set correct text color
val chapterColor = when {
chapter.read -> adapter.readColor
chapter.bookmark -> adapter.bookmarkedColor
else -> adapter.unreadColor
}
chapter_title.setTextColor(chapterColor)
chapter_description.setTextColor(chapterColor)
bookmark_icon.visibleIf { chapter.bookmark }
val descriptions = mutableListOf<CharSequence>()
if (chapter.date_upload > 0) {
descriptions.add(adapter.dateFormat.format(Date(chapter.date_upload)))
}
if (!chapter.read && chapter.last_page_read > 0) {
val lastPageRead = SpannableString(itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1)).apply {
setSpan(ForegroundColorSpan(adapter.readColor), 0, length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE)
}
descriptions.add(lastPageRead)
}
if (!chapter.scanlator.isNullOrBlank()) {
descriptions.add(chapter.scanlator!!)
}
if (descriptions.isNotEmpty()) {
chapter_description.text = descriptions.joinTo(SpannableStringBuilder(), "")
} else {
chapter_description.text = ""
}
notifyStatus(item.status)
}
fun notifyStatus(status: Int) = with(download_text) {
when (status) {
Download.QUEUE -> setText(R.string.chapter_queued)
Download.DOWNLOADING -> setText(R.string.chapter_downloading)
Download.DOWNLOADED -> setText(R.string.chapter_downloaded)
Download.ERROR -> setText(R.string.chapter_error)
else -> text = ""
}
}
}

View File

@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.manga.MangaAllInOneAdapter
class ChapterItem(val chapter: Chapter, val manga: Manga) :
AbstractFlexibleItem<ChapterHolder>(),
@ -57,3 +58,51 @@ class ChapterItem(val chapter: Chapter, val manga: Manga) :
return chapter.id!!.hashCode()
}
}
class MangaAllInOneChapterItem(val chapter: Chapter, val manga: Manga) :
AbstractFlexibleItem<MangaAllInOneChapterHolder>(),
Chapter by chapter {
private var _status: Int = 0
var status: Int
get() = download?.status ?: _status
set(value) {
_status = value
}
@Transient
var download: Download? = null
val isDownloaded: Boolean
get() = status == Download.DOWNLOADED
override fun getLayoutRes(): Int {
return R.layout.chapters_item
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MangaAllInOneChapterHolder {
return MangaAllInOneChapterHolder(view, adapter as MangaAllInOneAdapter)
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: MangaAllInOneChapterHolder,
position: Int,
payloads: List<Any?>?
) {
holder.bind(this, manga)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is MangaAllInOneChapterItem) {
return chapter.id!! == other.chapter.id!!
}
return false
}
override fun hashCode(): Int {
return chapter.id!!.hashCode()
}
}

View File

@ -14,425 +14,29 @@
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"
tools:context=".ui.browse.source.browse.BrowseSourceController">
<androidx.constraintlayout.widget.ConstraintLayout
<eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout
android:id="@id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
tools:context=".ui.browse.source.browse.BrowseSourceController">
<ImageView
android:id="@+id/manga_cover"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:background="@drawable/rounded_rectangle"
android:contentDescription="@string/description_cover"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="h,3:2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<FrameLayout
android:id="@+id/linear_recycler_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="@+id/info_scrollview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_cover"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
android:clipToPadding="false"
android:descendantFocusability="blocksDescendants"
android:paddingBottom="@dimen/fab_list_padding"
tools:listitem="@layout/chapters_item" />
<TextView
android:id="@+id/manga_full_title"
style="@style/TextAppearance.Medium.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:text="@string/manga_info_full_title_label"
android:textIsSelectable="false"
app:autoSizeMaxTextSize="20sp"
app:autoSizeMinTextSize="12sp"
app:autoSizeStepGranularity="2sp"
app:autoSizeTextType="uniform"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
<TextView
android:id="@+id/manga_author_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_author_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_full_title" />
<TextView
android:id="@+id/manga_author"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_author_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_author_label" />
<TextView
android:id="@+id/manga_artist_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_artist_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_author_label" />
<TextView
android:id="@+id/manga_artist"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_artist_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_artist_label" />
<TextView
android:id="@+id/manga_chapters_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_last_chapter_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_artist_label" />
<TextView
android:id="@+id/manga_chapters"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_chapters_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_chapters_label" />
<TextView
android:id="@+id/manga_last_update_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_latest_data_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_chapters_label" />
<TextView
android:id="@+id/manga_last_update"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_last_update_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_last_update_label" />
<TextView
android:id="@+id/manga_status_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_status_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_last_update_label" />
<TextView
android:id="@+id/manga_status"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_status_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_status_label" />
<TextView
android:id="@+id/manga_source_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_source_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_status_label" />
<TextView
android:id="@+id/manga_source"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_source_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_source_label" />
<HorizontalScrollView
android:id="@+id/actions_bar_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manga_source" >
<LinearLayout
android:id="@+id/actions_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="8dp"
android:paddingBottom="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_favorite"
style="@style/Theme.Widget.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_to_library"
app:icon="@drawable/ic_favorite_border_24dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_tracking"
style="@style/Theme.Widget.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/track"
android:visibility="gone"
tools:visibility="visible"
app:icon="@drawable/ic_cloud_off_24dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_categories"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_edit_categories"
android:visibility="gone"
app:icon="@drawable/ic_label_24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_share"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_share"
android:visibility="gone"
app:icon="@drawable/ic_share_24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_webview"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_open_in_web_view"
android:visibility="gone"
app:icon="@drawable/ic_public_24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_migrate"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/migrate"
android:visibility="gone"
app:icon="@drawable/baseline_swap_calls_24"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_smart_search"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/eh_merge_with_another_source"
android:visibility="gone"
app:icon="@drawable/eh_ic_find_replace_white_24dp"
tools:visibility="visible" />
</LinearLayout>
</HorizontalScrollView>
<TextView
android:id="@+id/manga_summary_label"
style="@style/TextAppearance.Regular.SubHeading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/manga_info_about_label"
android:textIsSelectable="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/actions_bar_scroll_view" />
<TextView
android:id="@+id/manga_summary"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:maxLines="3"
android:textIsSelectable="false"
app:layout_constraintBottom_toTopOf="@id/manga_genres_tags_wrapper"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manga_summary_label" />
<FrameLayout
android:id="@+id/manga_genres_tags_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constrainedHeight="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manga_summary">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_full_chips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:chipSpacingHorizontal="4dp" />
<HorizontalScrollView
android:id="@+id/manga_genres_tags_compact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_compact_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipSpacingHorizontal="4dp"
app:singleLine="true" />
</HorizontalScrollView>
</FrameLayout>
<Button
android:id="@+id/manga_info_toggle"
style="@style/Theme.Widget.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:text="@string/manga_info_expand"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manga_genres_tags_wrapper" />
<Button
android:id="@+id/merge_btn"
style="@style/Theme.Widget.Button.FilledAccent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="@string/merge"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle"
tools:visibility="visible" />
<Button
android:id="@+id/recommend_btn"
style="@style/Theme.Widget.Button.FilledAccent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="@string/az_recommends"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle"
tools:layout_constraintTop_toBottomOf="@+id/merge_btn"
tools:visibility="visible" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/recommend_btn">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clipToPadding="false"
android:descendantFocusability="blocksDescendants"
android:paddingBottom="@dimen/fab_list_padding"
tools:listitem="@layout/chapters_item" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout>
</eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout>
<eu.kanade.tachiyomi.ui.library.MaterialFastScroll
android:id="@+id/fast_scroller"

View File

@ -0,0 +1,381 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/manga_cover"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:background="@drawable/rounded_rectangle"
android:contentDescription="@string/description_cover"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="h,3:2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
app:layout_constraintStart_toEndOf="parent">
<TextView
android:id="@+id/manga_full_title"
style="@style/TextAppearance.Medium.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:text="@string/manga_info_full_title_label"
android:textIsSelectable="false"
app:autoSizeMaxTextSize="20sp"
app:autoSizeMinTextSize="12sp"
app:autoSizeStepGranularity="2sp"
app:autoSizeTextType="uniform"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/manga_author_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_author_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_full_title" />
<TextView
android:id="@+id/manga_author"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_author_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_author_label" />
<TextView
android:id="@+id/manga_artist_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_artist_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_author_label" />
<TextView
android:id="@+id/manga_artist"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_artist_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_artist_label" />
<TextView
android:id="@+id/manga_chapters_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_last_chapter_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_artist_label" />
<TextView
android:id="@+id/manga_chapters"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_chapters_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_chapters_label" />
<TextView
android:id="@+id/manga_last_update_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_latest_data_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_chapters_label" />
<TextView
android:id="@+id/manga_last_update"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_last_update_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_last_update_label" />
<TextView
android:id="@+id/manga_status_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_status_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_last_update_label" />
<TextView
android:id="@+id/manga_status"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_status_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_status_label" />
<TextView
android:id="@+id/manga_source_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_source_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_status_label" />
<TextView
android:id="@+id/manga_source"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_source_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_source_label" />
<HorizontalScrollView
android:id="@+id/actions_bar_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manga_source" >
<LinearLayout
android:id="@+id/actions_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="8dp"
android:paddingBottom="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_favorite"
style="@style/Theme.Widget.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_to_library"
app:icon="@drawable/ic_favorite_border_24dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_tracking"
style="@style/Theme.Widget.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/track"
android:visibility="gone"
tools:visibility="visible"
app:icon="@drawable/ic_cloud_off_24dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_categories"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_edit_categories"
android:visibility="gone"
app:icon="@drawable/ic_label_24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_share"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_share"
android:visibility="gone"
app:icon="@drawable/ic_share_24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_webview"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_open_in_web_view"
android:visibility="gone"
app:icon="@drawable/ic_public_24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_migrate"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/migrate"
android:visibility="gone"
app:icon="@drawable/baseline_swap_calls_24"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_smart_search"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/eh_merge_with_another_source"
android:visibility="gone"
app:icon="@drawable/eh_ic_find_replace_white_24dp"
tools:visibility="visible" />
</LinearLayout>
</HorizontalScrollView>
<TextView
android:id="@+id/manga_summary_label"
style="@style/TextAppearance.Regular.SubHeading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/manga_info_about_label"
android:textIsSelectable="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/actions_bar_scroll_view" />
<TextView
android:id="@+id/manga_summary"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:maxLines="3"
android:textIsSelectable="false"
app:layout_constraintBottom_toTopOf="@id/manga_genres_tags_wrapper"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manga_summary_label" />
<FrameLayout
android:id="@+id/manga_genres_tags_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constrainedHeight="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manga_summary">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_full_chips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:chipSpacingHorizontal="4dp" />
<HorizontalScrollView
android:id="@+id/manga_genres_tags_compact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_compact_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipSpacingHorizontal="4dp"
app:singleLine="true" />
</HorizontalScrollView>
</FrameLayout>
<Button
android:id="@+id/manga_info_toggle"
style="@style/Theme.Widget.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:text="@string/manga_info_expand"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manga_genres_tags_wrapper" />
<Button
android:id="@+id/merge_btn"
style="@style/Theme.Widget.Button.FilledAccent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="@string/merge"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle"
tools:visibility="visible" />
<Button
android:id="@+id/recommend_btn"
style="@style/Theme.Widget.Button.FilledAccent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="@string/az_recommends"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle"
tools:layout_constraintTop_toBottomOf="@+id/merge_btn"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -20,428 +20,22 @@
android:layout_height="match_parent"
tools:context=".ui.browse.source.browse.BrowseSourceController">
<androidx.core.widget.NestedScrollView
<FrameLayout
android:id="@+id/linear_recycler_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_height="match_parent"
android:clipToPadding="false"
android:descendantFocusability="blocksDescendants"
android:paddingBottom="@dimen/fab_list_padding"
tools:listitem="@layout/chapters_item" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.38" />
<ImageView
android:id="@+id/backdrop"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:background="@color/material_grey_700" />
<ImageView
android:id="@+id/manga_cover"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:background="@drawable/rounded_rectangle"
android:contentDescription="@string/description_cover"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="h,2:3"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/manga_info_section"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/manga_full_title"
style="@style/TextAppearance.Medium.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:paddingBottom="8dp"
android:text="@string/manga_info_full_title_label"
android:textIsSelectable="false"
app:autoSizeMaxTextSize="20sp"
app:autoSizeMinTextSize="12sp"
app:autoSizeStepGranularity="2sp"
app:autoSizeTextType="uniform"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/manga_author_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_author_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_full_title" />
<TextView
android:id="@+id/manga_author"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_author_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_author_label" />
<TextView
android:id="@+id/manga_artist_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_artist_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_author_label" />
<TextView
android:id="@+id/manga_artist"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_artist_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_artist_label" />
<TextView
android:id="@+id/manga_chapters_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_last_chapter_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_artist_label" />
<TextView
android:id="@+id/manga_chapters"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_chapters_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_chapters_label" />
<TextView
android:id="@+id/manga_last_update_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_latest_data_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_chapters_label" />
<TextView
android:id="@+id/manga_last_update"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_last_update_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_last_update_label" />
<TextView
android:id="@+id/manga_status_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_status_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_last_update_label" />
<TextView
android:id="@+id/manga_status"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_status_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_status_label" />
<TextView
android:id="@+id/manga_source_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_source_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_status_label" />
<TextView
android:id="@+id/manga_source"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_source_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_source_label" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<HorizontalScrollView
android:id="@+id/actions_bar_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal">
<LinearLayout
android:id="@+id/actions_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_favorite"
style="@style/Theme.Widget.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_to_library"
app:icon="@drawable/ic_favorite_border_24dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_tracking"
style="@style/Theme.Widget.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/track"
android:visibility="gone"
tools:visibility="visible"
app:icon="@drawable/ic_cloud_off_24dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_categories"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_edit_categories"
android:visibility="gone"
app:icon="@drawable/ic_label_24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_share"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_share"
android:visibility="gone"
app:icon="@drawable/ic_share_24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_webview"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_open_in_web_view"
android:visibility="gone"
app:icon="@drawable/ic_public_24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_migrate"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/migrate"
android:visibility="gone"
app:icon="@drawable/baseline_swap_calls_24"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_smart_search"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/eh_merge_with_another_source"
android:visibility="gone"
app:icon="@drawable/eh_ic_find_replace_white_24dp"
tools:visibility="visible" />
</LinearLayout>
</HorizontalScrollView>
<TextView
android:id="@+id/manga_summary_label"
style="@style/TextAppearance.Regular.SubHeading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="@string/manga_info_about_label"
android:textIsSelectable="false" />
<TextView
android:id="@+id/manga_summary"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:maxLines="3"
android:textIsSelectable="false" />
<FrameLayout
android:id="@+id/manga_genres_tags_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_full_chips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:chipSpacingHorizontal="4dp" />
<HorizontalScrollView
android:id="@+id/manga_genres_tags_compact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_compact_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:chipSpacingHorizontal="4dp"
app:singleLine="true" />
</HorizontalScrollView>
</FrameLayout>
<Button
android:id="@+id/manga_info_toggle"
style="@style/Theme.Widget.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:text="@string/manga_info_expand"
android:textSize="12sp" />
<Button
android:id="@+id/merge_btn"
style="@style/Theme.Widget.Button.FilledAccent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:text="@string/eh_merge_with_another_source"
android:visibility="gone"
tools:visibility="visible" />
<Button
android:id="@+id/recommend_btn"
style="@style/Theme.Widget.Button.FilledAccent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:text="@string/az_recommends"
android:visibility="gone"
tools:visibility="visible" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clipToPadding="false"
android:descendantFocusability="blocksDescendants"
android:paddingBottom="@dimen/fab_list_padding"
tools:listitem="@layout/chapters_item" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</FrameLayout>
</eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout>

View File

@ -0,0 +1,408 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".ui.browse.source.browse.BrowseSourceController">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.38" />
<ImageView
android:id="@+id/backdrop"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:background="@color/material_grey_700" />
<ImageView
android:id="@+id/manga_cover"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:background="@drawable/rounded_rectangle"
android:contentDescription="@string/description_cover"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="h,2:3"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/manga_info_section"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/manga_full_title"
style="@style/TextAppearance.Medium.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:paddingBottom="8dp"
android:text="@string/manga_info_full_title_label"
android:textIsSelectable="false"
app:autoSizeMaxTextSize="20sp"
app:autoSizeMinTextSize="12sp"
app:autoSizeStepGranularity="2sp"
app:autoSizeTextType="uniform"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/manga_author_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_author_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_full_title" />
<TextView
android:id="@+id/manga_author"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_author_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_author_label" />
<TextView
android:id="@+id/manga_artist_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_artist_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_author_label" />
<TextView
android:id="@+id/manga_artist"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_artist_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_artist_label" />
<TextView
android:id="@+id/manga_chapters_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_last_chapter_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_artist_label" />
<TextView
android:id="@+id/manga_chapters"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_chapters_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_chapters_label" />
<TextView
android:id="@+id/manga_last_update_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_latest_data_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_chapters_label" />
<TextView
android:id="@+id/manga_last_update"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_last_update_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_last_update_label" />
<TextView
android:id="@+id/manga_status_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_status_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_last_update_label" />
<TextView
android:id="@+id/manga_status"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_status_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_status_label" />
<TextView
android:id="@+id/manga_source_label"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manga_info_source_label"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_status_label" />
<TextView
android:id="@+id/manga_source"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_source_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/manga_source_label" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<HorizontalScrollView
android:id="@+id/actions_bar_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal">
<LinearLayout
android:id="@+id/actions_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_favorite"
style="@style/Theme.Widget.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_to_library"
app:icon="@drawable/ic_favorite_border_24dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_tracking"
style="@style/Theme.Widget.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/track"
android:visibility="gone"
tools:visibility="visible"
app:icon="@drawable/ic_cloud_off_24dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_categories"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_edit_categories"
android:visibility="gone"
app:icon="@drawable/ic_label_24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_share"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_share"
android:visibility="gone"
app:icon="@drawable/ic_share_24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_webview"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_open_in_web_view"
android:visibility="gone"
app:icon="@drawable/ic_public_24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_migrate"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/migrate"
android:visibility="gone"
app:icon="@drawable/baseline_swap_calls_24"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_smart_search"
style="@style/Theme.Widget.Button.Icon.Textless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/eh_merge_with_another_source"
android:visibility="gone"
app:icon="@drawable/eh_ic_find_replace_white_24dp"
tools:visibility="visible" />
</LinearLayout>
</HorizontalScrollView>
<TextView
android:id="@+id/manga_summary_label"
style="@style/TextAppearance.Regular.SubHeading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="@string/manga_info_about_label"
android:textIsSelectable="false" />
<TextView
android:id="@+id/manga_summary"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:maxLines="3"
android:textIsSelectable="false" />
<FrameLayout
android:id="@+id/manga_genres_tags_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_full_chips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:chipSpacingHorizontal="4dp" />
<HorizontalScrollView
android:id="@+id/manga_genres_tags_compact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_compact_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:chipSpacingHorizontal="4dp"
app:singleLine="true" />
</HorizontalScrollView>
</FrameLayout>
<Button
android:id="@+id/manga_info_toggle"
style="@style/Theme.Widget.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:text="@string/manga_info_expand"
android:textSize="12sp" />
<Button
android:id="@+id/merge_btn"
style="@style/Theme.Widget.Button.FilledAccent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:text="@string/eh_merge_with_another_source"
android:visibility="gone"
tools:visibility="visible" />
<Button
android:id="@+id/recommend_btn"
style="@style/Theme.Widget.Button.FilledAccent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:text="@string/az_recommends"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>