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.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
@ -15,11 +13,9 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.gson.Gson import com.google.gson.Gson
import eu.davidea.flexibleadapter.FlexibleAdapter 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download 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.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.databinding.MangaAllInOneControllerBinding import eu.kanade.tachiyomi.databinding.MangaAllInOneControllerBinding
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager 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.HttpSource
import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.source.SourceController 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.library.LibraryController
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.main.offsetAppbarHeight 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.ChaptersPresenter
import eu.kanade.tachiyomi.ui.manga.chapter.DeleteChaptersDialog import eu.kanade.tachiyomi.ui.manga.chapter.DeleteChaptersDialog
import eu.kanade.tachiyomi.ui.manga.chapter.DownloadCustomChaptersDialog 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.manga.track.TrackController
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity 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.shrinkOnScroll
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.util.view.visibleIf
import exh.EH_SOURCE_ID import exh.EH_SOURCE_ID
import exh.EXH_SOURCE_ID import exh.EXH_SOURCE_ID
import exh.MERGED_SOURCE_ID
import exh.util.setChipsExtended
import java.text.DateFormat import java.text.DateFormat
import java.text.DecimalFormat
import java.util.Date import java.util.Date
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
@ -86,11 +72,8 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import reactivecircus.flowbinding.android.view.clicks import reactivecircus.flowbinding.android.view.clicks
import reactivecircus.flowbinding.android.view.longClicks
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -110,7 +93,8 @@ class MangaAllInOneController :
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemLongClickListener,
DownloadCustomChaptersDialog.Listener, DownloadCustomChaptersDialog.Listener,
DeleteChaptersDialog.Listener { DeleteChaptersDialog.Listener,
MangaAllInOneAdapter.MangaAllInOneInterface {
constructor(manga: Manga?, fromSource: Boolean = false, smartSearchConfig: SourceController.SmartSearchConfig? = null, update: Boolean = false) : super( constructor(manga: Manga?, fromSource: Boolean = false, smartSearchConfig: SourceController.SmartSearchConfig? = null, update: Boolean = false) : super(
Bundle().apply { Bundle().apply {
@ -158,7 +142,7 @@ class MangaAllInOneController :
/** /**
* Adapter containing a list of chapters. * Adapter containing a list of chapters.
*/ */
private var adapter: ChaptersAdapter? = null private var adapter: MangaAllInOneAdapter? = null
/** /**
* Action mode for multiple selection. * Action mode for multiple selection.
@ -168,7 +152,7 @@ class MangaAllInOneController :
/** /**
* Selected items. Used to restore selections after a rotation. * Selected items. Used to restore selections after a rotation.
*/ */
private val selectedItems = mutableSetOf<ChapterItem>() private val selectedItems = mutableSetOf<MangaAllInOneChapterItem>()
private var lastClickPosition = -1 private var lastClickPosition = -1
@ -192,6 +176,8 @@ class MangaAllInOneController :
var update = args.getBoolean(UPDATE_EXTRA, false) var update = args.getBoolean(UPDATE_EXTRA, false)
override val controllerScope = scope
init { init {
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
@ -214,178 +200,10 @@ class MangaAllInOneController :
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(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 if (manga == null || source == null) return
// Init RecyclerView and adapter // Init RecyclerView and adapter
adapter = ChaptersAdapter(this, view.context) adapter = MangaAllInOneAdapter(this, view.context)
binding.recycler.adapter = adapter binding.recycler.adapter = adapter
binding.recycler.layoutManager = LinearLayoutManager(view.context) binding.recycler.layoutManager = LinearLayoutManager(view.context)
@ -421,8 +239,28 @@ class MangaAllInOneController :
binding.fab.offsetAppbarHeight(activity!!) 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 --> // EXH -->
private fun openSmartSearch() { override fun openSmartSearch() {
val smartSearchConfig = SourceController.SmartSearchConfig(presenter.manga.title, presenter.manga.id!!) val smartSearchConfig = SourceController.SmartSearchConfig(presenter.manga.title, presenter.manga.id!!)
router?.pushController( router?.pushController(
@ -433,10 +271,48 @@ class MangaAllInOneController :
).withFadeTransaction() ).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 <-- // EXH <--
// AZ --> // AZ -->
private fun openRecommends() { override fun openRecommends() {
val recommendsConfig = BrowseSourceController.RecommendsConfig(presenter.manga) val recommendsConfig = BrowseSourceController.RecommendsConfig(presenter.manga)
router?.pushController( router?.pushController(
@ -449,7 +325,7 @@ class MangaAllInOneController :
} }
// AZ <-- // AZ <--
private fun openTracking() { override fun openTracking() {
router?.pushController( router?.pushController(
TrackController(fromAllInOne = true, manga = manga).withFadeTransaction() TrackController(fromAllInOne = true, manga = manga).withFadeTransaction()
) )
@ -463,7 +339,7 @@ class MangaAllInOneController :
* @param manga manga object containing information about manga. * @param manga manga object containing information about manga.
* @param source the source of the 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) { if (manga.initialized) {
// Update view. // Update view.
setMangaInfo(manga, source, chapters) setMangaInfo(manga, source, chapters)
@ -482,121 +358,9 @@ class MangaAllInOneController :
* @param manga manga object containing information about manga. * @param manga manga object containing information about manga.
* @param source the source of the 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 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 || if (update ||
// Auto-update old format galleries // Auto-update old format galleries
( (
@ -610,6 +374,7 @@ class MangaAllInOneController :
val adapter = adapter ?: return val adapter = adapter ?: return
adapter.updateDataSet(chapters) adapter.updateDataSet(chapters)
addMangaHeader()
if (selectedItems.isNotEmpty()) { if (selectedItems.isNotEmpty()) {
adapter.clearSelection() // we need to start from a clean state, index may have changed adapter.clearSelection() // we need to start from a clean state, index may have changed
@ -624,90 +389,7 @@ class MangaAllInOneController :
} }
} }
private fun hideMangaInfo() { override fun openInWebView() {
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() {
val source = presenter.source as? HttpSource ?: return val source = presenter.source as? HttpSource ?: return
val url = try { val url = try {
@ -724,7 +406,7 @@ class MangaAllInOneController :
/** /**
* Called to run Intent with [Intent.ACTION_SEND], which show share dialog. * Called to run Intent with [Intent.ACTION_SEND], which show share dialog.
*/ */
private fun shareManga() { override fun shareManga() {
val context = view?.context ?: return val context = view?.context ?: return
val source = presenter.source as? HttpSource ?: 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. * 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) setRefreshing(true)
// Call presenter and start fetching manga information // Call presenter and start fetching manga information
presenter.fetchMangaFromSource(manualFetch, fetchManga, fetchChapters) presenter.fetchMangaFromSource(manualFetch, fetchManga, fetchChapters)
} }
fun onFetchMangaDone() { override fun onFetchMangaDone() {
setRefreshing(false) setRefreshing(false)
} }
/** /**
* Update swipe refresh to start showing refresh in progress spinner. * Update swipe refresh to start showing refresh in progress spinner.
*/ */
fun onFetchMangaError(error: Throwable) { override fun onFetchMangaError(error: Throwable) {
setRefreshing(false) setRefreshing(false)
activity?.toast(error.message) activity?.toast(error.message)
} }
@ -781,15 +448,15 @@ class MangaAllInOneController :
* *
* @param value whether it should be refreshing or not. * @param value whether it should be refreshing or not.
*/ */
fun setRefreshing(value: Boolean) { override fun setRefreshing(value: Boolean) {
binding.swipeRefresh.isRefreshing = value binding.swipeRefresh.isRefreshing = value
} }
private fun onFavoriteClick() { override fun onFavoriteClick() {
val manga = presenter.manga val manga = presenter.manga
if (manga.favorite) { if (manga.favorite) {
toggleFavorite() getHeader()?.toggleFavorite()
activity?.toast(activity?.getString(R.string.manga_removed_library)) activity?.toast(activity?.getString(R.string.manga_removed_library))
} else { } else {
val categories = presenter.getCategories() val categories = presenter.getCategories()
@ -799,14 +466,14 @@ class MangaAllInOneController :
when { when {
// Default category set // Default category set
defaultCategory != null -> { defaultCategory != null -> {
toggleFavorite() getHeader()?.toggleFavorite()
presenter.moveMangaToCategory(manga, defaultCategory) presenter.moveMangaToCategory(manga, defaultCategory)
activity?.toast(activity?.getString(R.string.manga_added_library)) activity?.toast(activity?.getString(R.string.manga_added_library))
} }
// Automatic 'Default' or no categories // Automatic 'Default' or no categories
defaultCategoryId == 0 || categories.isEmpty() -> { defaultCategoryId == 0 || categories.isEmpty() -> {
toggleFavorite() getHeader()?.toggleFavorite()
presenter.moveMangaToCategory(manga, null) presenter.moveMangaToCategory(manga, null)
activity?.toast(activity?.getString(R.string.manga_added_library)) 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 manga = presenter.manga
val categories = presenter.getCategories() val categories = presenter.getCategories()
@ -842,7 +509,7 @@ class MangaAllInOneController :
val manga = mangas.firstOrNull() ?: return val manga = mangas.firstOrNull() ?: return
if (!manga.favorite) { if (!manga.favorite) {
toggleFavorite() getHeader()?.toggleFavorite()
activity?.toast(activity?.getString(R.string.manga_added_library)) 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 * @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 val router = router ?: return
router.pushController(GlobalSearchController(query).withFadeTransaction()) 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 // --> EH
private fun wrapTag(namespace: String, tag: String) = override fun wrapTag(namespace: String, tag: String) =
if (tag.contains(' ')) { if (tag.contains(' ')) {
"$namespace:\"$tag$\"" "$namespace:\"$tag$\""
} else { } else {
@ -869,7 +554,7 @@ class MangaAllInOneController :
private fun parseTag(tag: String) = tag.substringBefore(':').trim() to tag.substringAfter(':').trim() 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 val sourceId = presenter.source.id
return sourceId == EH_SOURCE_ID || return sourceId == EH_SOURCE_ID ||
sourceId == EXH_SOURCE_ID sourceId == EXH_SOURCE_ID
@ -881,7 +566,7 @@ class MangaAllInOneController :
* *
* @param query the search query to the previous controller * @param query the search query to the previous controller
*/ */
private fun performSearch(query: String) { override fun performSearch(query: String) {
val router = router ?: return val router = router ?: return
if (router.backstackSize < 2) { if (router.backstackSize < 2) {
@ -1039,8 +724,8 @@ class MangaAllInOneController :
getHolder(download.chapter)?.notifyStatus(download.status) getHolder(download.chapter)?.notifyStatus(download.status)
} }
private fun getHolder(chapter: Chapter): ChapterHolder? { private fun getHolder(chapter: Chapter): MangaAllInOneChapterHolder? {
return binding.recycler.findViewHolderForItemId(chapter.id!!) as? ChapterHolder return binding.recycler.findViewHolderForItemId(chapter.id!!) as? MangaAllInOneChapterHolder
} }
fun openChapter(chapter: Chapter, hasAnimation: Boolean = false) { fun openChapter(chapter: Chapter, hasAnimation: Boolean = false) {
@ -1054,7 +739,7 @@ class MangaAllInOneController :
override fun onItemClick(view: View?, position: Int): Boolean { override fun onItemClick(view: View?, position: Int): Boolean {
val adapter = adapter ?: return false 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) { return if (actionMode != null && adapter.mode == SelectableAdapter.Mode.MULTI) {
lastClickPosition = position lastClickPosition = position
toggleSelection(position) toggleSelection(position)
@ -1089,7 +774,7 @@ class MangaAllInOneController :
adapter.toggleSelection(position) adapter.toggleSelection(position)
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
if (adapter.isSelected(position)) { if (adapter.isSelected(position)) {
selectedItems.add(item) selectedItems.add(item as MangaAllInOneChapterItem)
} else { } else {
selectedItems.remove(item) selectedItems.remove(item)
} }
@ -1101,14 +786,14 @@ class MangaAllInOneController :
val item = adapter.getItem(position) ?: return val item = adapter.getItem(position) ?: return
if (!adapter.isSelected(position)) { if (!adapter.isSelected(position)) {
adapter.toggleSelection(position) adapter.toggleSelection(position)
selectedItems.add(item) selectedItems.add(item as MangaAllInOneChapterItem)
actionMode?.invalidate() actionMode?.invalidate()
} }
} }
private fun getSelectedChapters(): List<ChapterItem> { private fun getSelectedChapters(): List<MangaAllInOneChapterItem> {
val adapter = adapter ?: return emptyList() val adapter = adapter ?: return emptyList()
return adapter.selectedPositions.mapNotNull { adapter.getItem(it) } return adapter.selectedPositions.mapNotNull { adapter.getItem(it) as MangaAllInOneChapterItem }
} }
private fun createActionModeIfNeeded() { private fun createActionModeIfNeeded() {
@ -1209,24 +894,24 @@ class MangaAllInOneController :
for (i in 0..adapter.itemCount) { for (i in 0..adapter.itemCount) {
adapter.toggleSelection(i) adapter.toggleSelection(i)
} }
selectedItems.addAll(adapter.selectedPositions.mapNotNull { adapter.getItem(it) }) selectedItems.addAll(adapter.selectedPositions.mapNotNull { adapter.getItem(it) as MangaAllInOneChapterItem })
actionMode?.invalidate() actionMode?.invalidate()
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
} }
private fun markAsRead(chapters: List<ChapterItem>) { private fun markAsRead(chapters: List<MangaAllInOneChapterItem>) {
presenter.markChaptersRead(chapters, true) presenter.markChaptersRead(chapters, true)
if (presenter.preferences.removeAfterMarkedAsRead()) { if (presenter.preferences.removeAfterMarkedAsRead()) {
deleteChapters(chapters) deleteChapters(chapters)
} }
} }
private fun markAsUnread(chapters: List<ChapterItem>) { private fun markAsUnread(chapters: List<MangaAllInOneChapterItem>) {
presenter.markChaptersRead(chapters, false) presenter.markChaptersRead(chapters, false)
} }
private fun downloadChapters(chapters: List<ChapterItem>) { private fun downloadChapters(chapters: List<MangaAllInOneChapterItem>) {
val view = view val view = view
presenter.downloadChapters(chapters) presenter.downloadChapters(chapters)
if (view != null && !presenter.manga.favorite) { if (view != null && !presenter.manga.favorite) {
@ -1246,7 +931,7 @@ class MangaAllInOneController :
deleteChapters(getSelectedChapters()) deleteChapters(getSelectedChapters())
} }
private fun markPreviousAsRead(chapters: List<ChapterItem>) { private fun markPreviousAsRead(chapters: List<MangaAllInOneChapterItem>) {
val adapter = adapter ?: return val adapter = adapter ?: return
val prevChapters = if (presenter.sortDescending()) adapter.items.reversed() else adapter.items val prevChapters = if (presenter.sortDescending()) adapter.items.reversed() else adapter.items
val chapterPos = prevChapters.indexOf(chapters.last()) 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) presenter.bookmarkChapters(chapters, bookmarked)
} }
fun deleteChapters(chapters: List<ChapterItem>) { fun deleteChapters(chapters: List<MangaAllInOneChapterItem>) {
if (chapters.isEmpty()) return if (chapters.isEmpty()) return
presenter.deleteChapters(chapters) 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 // this is needed so the downloaded text gets removed from the item
chapters.forEach { chapters.forEach {
adapter?.updateItem(it) 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.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.source.SourceController 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.ChaptersPresenter
import eu.kanade.tachiyomi.ui.manga.chapter.MangaAllInOneChapterItem
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.prepUpdateCover import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
@ -64,7 +64,7 @@ class MangaAllInOnePresenter(
/** /**
* List of chapters of the manga. It's always unfiltered and unsorted. * 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 set
private val scope = CoroutineScope(Job() + Dispatchers.Default) private val scope = CoroutineScope(Job() + Dispatchers.Default)
@ -86,6 +86,8 @@ class MangaAllInOnePresenter(
private val redirectUserRelay = BehaviorRelay.create<ChaptersPresenter.EXHRedirect>() private val redirectUserRelay = BehaviorRelay.create<ChaptersPresenter.EXHRedirect>()
// EXH <-- // EXH <--
var headerItem = MangaAllInOneHeaderItem(manga, source, smartSearchConfig)
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
@ -385,9 +387,9 @@ class MangaAllInOnePresenter(
/** /**
* Converts a chapter from the database to an extended model, allowing to store new fields. * 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. // Create the model object.
val model = ChapterItem(this, manga) val model = MangaAllInOneChapterItem(this, manga)
// Find an active download for this chapter. // Find an active download for this chapter.
val download = downloadManager.queue.find { it.chapter.id == id } val download = downloadManager.queue.find { it.chapter.id == id }
@ -404,7 +406,7 @@ class MangaAllInOnePresenter(
* *
* @param chapters the list of chapter from the database. * @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) { for (chapter in chapters) {
if (downloadManager.isChapterDownloaded(chapter, manga)) { if (downloadManager.isChapterDownloaded(chapter, manga)) {
chapter.status = Download.DOWNLOADED chapter.status = Download.DOWNLOADED
@ -417,7 +419,7 @@ class MangaAllInOnePresenter(
* @param chapters the list of chapters from the database * @param chapters the list of chapters from the database
* @return an observable of the list of chapters filtered and sorted. * @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 var chapters = chapterList
if (onlyUnread()) { if (onlyUnread()) {
chapters = chapters.filter { !it.read } chapters = chapters.filter { !it.read }
@ -468,7 +470,7 @@ class MangaAllInOnePresenter(
/** /**
* Returns the next unread chapter or null if everything is read. * 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 } return chapters.sortedByDescending { it.source_order }.find { !it.read }
} }
@ -477,7 +479,7 @@ class MangaAllInOnePresenter(
* @param selectedChapters the list of selected chapters. * @param selectedChapters the list of selected chapters.
* @param read whether to mark chapters as read or unread. * @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) Observable.from(selectedChapters)
.doOnNext { chapter -> .doOnNext { chapter ->
chapter.read = read chapter.read = read
@ -498,7 +500,7 @@ class MangaAllInOnePresenter(
* Downloads the given list of chapters with the manager. * Downloads the given list of chapters with the manager.
* @param chapters the list of chapters to download. * @param chapters the list of chapters to download.
*/ */
fun downloadChapters(chapters: List<ChapterItem>) { fun downloadChapters(chapters: List<MangaAllInOneChapterItem>) {
downloadManager.downloadChapters(manga, chapters) downloadManager.downloadChapters(manga, chapters)
} }
@ -506,7 +508,7 @@ class MangaAllInOnePresenter(
* Bookmarks the given list of chapters. * Bookmarks the given list of chapters.
* @param selectedChapters the list of chapters to bookmark. * @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) Observable.from(selectedChapters)
.doOnNext { chapter -> .doOnNext { chapter ->
chapter.bookmark = bookmarked chapter.bookmark = bookmarked
@ -521,7 +523,7 @@ class MangaAllInOnePresenter(
* Deletes the given list of chapter. * Deletes the given list of chapter.
* @param chapters the list of chapters to delete. * @param chapters the list of chapters to delete.
*/ */
fun deleteChapters(chapters: List<ChapterItem>) { fun deleteChapters(chapters: List<MangaAllInOneChapterItem>) {
Observable.just(chapters) Observable.just(chapters)
.doOnNext { deleteChaptersInternal(chapters) } .doOnNext { deleteChaptersInternal(chapters) }
.doOnNext { if (onlyDownloaded()) updateChaptersView() } .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. * Deletes a list of chapters from disk. This method is called in a background thread.
* @param chapters the chapters to delete. * @param chapters the chapters to delete.
*/ */
private fun deleteChaptersInternal(chapters: List<ChapterItem>) { private fun deleteChaptersInternal(chapters: List<MangaAllInOneChapterItem>) {
downloadManager.deleteChapters(chapters, manga, source) downloadManager.deleteChapters(chapters, manga, source)
chapters.forEach { chapters.forEach {
it.status = Download.NOT_DOWNLOADED 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.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.ui.manga.MangaAllInOneAdapter
import eu.kanade.tachiyomi.util.view.visibleIf import eu.kanade.tachiyomi.util.view.visibleIf
import java.util.Date import java.util.Date
import kotlinx.android.synthetic.main.chapters_item.bookmark_icon 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.manga.MangaAllInOneAdapter
class ChapterItem(val chapter: Chapter, val manga: Manga) : class ChapterItem(val chapter: Chapter, val manga: Manga) :
AbstractFlexibleItem<ChapterHolder>(), AbstractFlexibleItem<ChapterHolder>(),
@ -57,3 +58,51 @@ class ChapterItem(val chapter: Chapter, val manga: Manga) :
return chapter.id!!.hashCode() 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

@ -20,417 +20,21 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.browse.source.browse.BrowseSourceController"> tools:context=".ui.browse.source.browse.BrowseSourceController">
<androidx.constraintlayout.widget.ConstraintLayout <FrameLayout
android:id="@+id/linear_recycler_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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.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
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<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" />
<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 <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler" android:id="@+id/recycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clipToPadding="false" android:clipToPadding="false"
android:descendantFocusability="blocksDescendants" android:descendantFocusability="blocksDescendants"
android:paddingBottom="@dimen/fab_list_padding" android:paddingBottom="@dimen/fab_list_padding"
tools:listitem="@layout/chapters_item" /> tools:listitem="@layout/chapters_item" />
</LinearLayout> </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout> </eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout>

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" android:layout_height="match_parent"
tools:context=".ui.browse.source.browse.BrowseSourceController"> tools:context=".ui.browse.source.browse.BrowseSourceController">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<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 <FrameLayout
android:id="@+id/manga_genres_tags_wrapper" android:id="@+id/linear_recycler_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent">
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 <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler" android:id="@+id/recycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clipToPadding="false" android:clipToPadding="false"
android:descendantFocusability="blocksDescendants" android:descendantFocusability="blocksDescendants"
android:paddingBottom="@dimen/fab_list_padding" android:paddingBottom="@dimen/fab_list_padding"
tools:listitem="@layout/chapters_item" /> tools:listitem="@layout/chapters_item" />
</LinearLayout> </FrameLayout>
</androidx.core.widget.NestedScrollView>
</eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout> </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>