mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-15 13:37:29 +01:00
MangaController overhaul (#7244)
This commit is contained in:
@@ -83,7 +83,7 @@ class SearchController(
|
||||
binding.progress.isVisible = isReplacingManga
|
||||
if (!isReplacingManga) {
|
||||
router.popController(this)
|
||||
if (newManga != null) {
|
||||
if (newManga?.id != null) {
|
||||
val newMangaController = RouterTransaction.with(MangaController(newManga.id!!))
|
||||
if (router.backstack.lastOrNull()?.controller is MangaController) {
|
||||
// Replace old MangaController
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.library
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
|
||||
class ChangeMangaCoverDialog<T>(bundle: Bundle? = null) :
|
||||
DialogController(bundle) where T : Controller, T : ChangeMangaCoverDialog.Listener {
|
||||
|
||||
private lateinit var manga: Manga
|
||||
|
||||
constructor(target: T, manga: Manga) : this() {
|
||||
targetController = target
|
||||
this.manga = manga
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||
return MaterialAlertDialogBuilder(activity!!)
|
||||
.setTitle(R.string.action_edit_cover)
|
||||
.setPositiveButton(R.string.action_edit) { _, _ ->
|
||||
(targetController as? Listener)?.openMangaCoverPicker(manga)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setNeutralButton(R.string.action_delete) { _, _ ->
|
||||
(targetController as? Listener)?.deleteMangaCover(manga)
|
||||
}
|
||||
.create()
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun deleteMangaCover(manga: Manga)
|
||||
|
||||
fun openMangaCoverPicker(manga: Manga)
|
||||
}
|
||||
}
|
||||
@@ -420,7 +420,7 @@ class MainActivity : BaseActivity() {
|
||||
SHORTCUT_MANGA -> {
|
||||
val extras = intent.extras ?: return false
|
||||
val fgController = router.backstack.lastOrNull()?.controller as? MangaController
|
||||
if (fgController?.manga?.id != extras.getLong(MangaController.MANGA_EXTRA)) {
|
||||
if (fgController?.mangaId != extras.getLong(MangaController.MANGA_EXTRA)) {
|
||||
router.popToRoot()
|
||||
setSelectedNavItem(R.id.nav_library)
|
||||
router.pushController(RouterTransaction.with(MangaController(extras)))
|
||||
@@ -601,6 +601,9 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
val isFullComposeController = internalTo is FullComposeController<*>
|
||||
binding.appbar.isVisible = !isFullComposeController
|
||||
binding.controllerContainer.enableScrollingBehavior(!isFullComposeController)
|
||||
|
||||
if (!isTablet()) {
|
||||
// Save lift state
|
||||
if (isPush) {
|
||||
@@ -623,17 +626,6 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
binding.root.isLiftAppBarOnScroll = internalTo !is NoAppBarElevationController
|
||||
|
||||
binding.appbar.isVisible = !isFullComposeController
|
||||
binding.controllerContainer.enableScrollingBehavior(!isFullComposeController)
|
||||
|
||||
// TODO: Remove when MangaController is full compose
|
||||
if (!isFullComposeController) {
|
||||
binding.appbar.isTransparentWhenNotLifted = internalTo is MangaController
|
||||
binding.controllerContainer.overlapHeader = internalTo is MangaController
|
||||
}
|
||||
} else {
|
||||
binding.appbar.isVisible = !isFullComposeController
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,127 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.color
|
||||
import androidx.core.view.isVisible
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.databinding.ChaptersItemBinding
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChapterHolder
|
||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import java.util.Date
|
||||
|
||||
class ChapterHolder(
|
||||
view: View,
|
||||
private val adapter: ChaptersAdapter,
|
||||
) : BaseChapterHolder(view, adapter) {
|
||||
|
||||
private val binding = ChaptersItemBinding.bind(view)
|
||||
|
||||
init {
|
||||
binding.download.listener = downloadActionListener
|
||||
}
|
||||
|
||||
fun bind(item: ChapterItem, manga: Manga) {
|
||||
val chapter = item.chapter
|
||||
|
||||
binding.chapterTitle.text = when (manga.displayMode) {
|
||||
Manga.CHAPTER_DISPLAY_NUMBER -> {
|
||||
val number = adapter.decimalFormat.format(chapter.chapter_number.toDouble())
|
||||
itemView.context.getString(R.string.display_mode_chapter, number)
|
||||
}
|
||||
else -> chapter.name
|
||||
// TODO: show cleaned name consistently around the app
|
||||
// else -> cleanChapterName(chapter, manga)
|
||||
}
|
||||
|
||||
// Set correct text color
|
||||
val chapterTitleColor = when {
|
||||
chapter.read -> adapter.readColor
|
||||
chapter.bookmark -> adapter.bookmarkedColor
|
||||
else -> adapter.unreadColor
|
||||
}
|
||||
binding.chapterTitle.setTextColor(chapterTitleColor)
|
||||
|
||||
val chapterDescriptionColor = when {
|
||||
chapter.read -> adapter.readColor
|
||||
chapter.bookmark -> adapter.bookmarkedColor
|
||||
else -> adapter.unreadColorSecondary
|
||||
}
|
||||
binding.chapterDescription.setTextColor(chapterDescriptionColor)
|
||||
|
||||
binding.bookmarkIcon.isVisible = chapter.bookmark
|
||||
|
||||
val descriptions = mutableListOf<CharSequence>()
|
||||
|
||||
if (chapter.date_upload > 0) {
|
||||
descriptions.add(Date(chapter.date_upload).toRelativeString(itemView.context, adapter.relativeTime, adapter.dateFormat))
|
||||
}
|
||||
if (!chapter.read && chapter.last_page_read > 0) {
|
||||
val lastPageRead = buildSpannedString {
|
||||
color(adapter.readColor) {
|
||||
append(itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1))
|
||||
}
|
||||
}
|
||||
descriptions.add(lastPageRead)
|
||||
}
|
||||
if (!chapter.scanlator.isNullOrBlank()) {
|
||||
descriptions.add(chapter.scanlator!!)
|
||||
}
|
||||
|
||||
if (descriptions.isNotEmpty()) {
|
||||
binding.chapterDescription.text = descriptions.joinTo(SpannableStringBuilder(), " • ")
|
||||
} else {
|
||||
binding.chapterDescription.text = ""
|
||||
}
|
||||
|
||||
binding.download.isVisible = item.manga.source != LocalSource.ID
|
||||
binding.download.setState(item.status, item.progress)
|
||||
}
|
||||
|
||||
private fun cleanChapterName(chapter: Chapter, manga: Manga): String {
|
||||
return chapter.name
|
||||
.trim()
|
||||
.removePrefix(manga.title)
|
||||
.trim(*CHAPTER_TRIM_CHARS)
|
||||
}
|
||||
}
|
||||
|
||||
private val CHAPTER_TRIM_CHARS = arrayOf(
|
||||
// Whitespace
|
||||
' ',
|
||||
'\u0009',
|
||||
'\u000A',
|
||||
'\u000B',
|
||||
'\u000C',
|
||||
'\u000D',
|
||||
'\u0020',
|
||||
'\u0085',
|
||||
'\u00A0',
|
||||
'\u1680',
|
||||
'\u2000',
|
||||
'\u2001',
|
||||
'\u2002',
|
||||
'\u2003',
|
||||
'\u2004',
|
||||
'\u2005',
|
||||
'\u2006',
|
||||
'\u2007',
|
||||
'\u2008',
|
||||
'\u2009',
|
||||
'\u200A',
|
||||
'\u2028',
|
||||
'\u2029',
|
||||
'\u202F',
|
||||
'\u205F',
|
||||
'\u3000',
|
||||
|
||||
// Separators
|
||||
'-',
|
||||
'_',
|
||||
',',
|
||||
':',
|
||||
).toCharArray()
|
||||
@@ -1,33 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChapterItem
|
||||
|
||||
class ChapterItem(chapter: Chapter, val manga: Manga) :
|
||||
BaseChapterItem<ChapterHolder, AbstractHeaderItem<FlexibleViewHolder>>(chapter) {
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.chapters_item
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): ChapterHolder {
|
||||
return ChapterHolder(view, adapter as ChaptersAdapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: ChapterHolder,
|
||||
position: Int,
|
||||
payloads: List<Any?>?,
|
||||
) {
|
||||
holder.bind(this, manga)
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChaptersAdapter
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.DateFormat
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
|
||||
class ChaptersAdapter(
|
||||
controller: MangaController,
|
||||
context: Context,
|
||||
) : BaseChaptersAdapter<ChapterItem>(controller) {
|
||||
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
|
||||
var items: List<ChapterItem> = emptyList()
|
||||
|
||||
val readColor = context.getResourceColor(R.attr.colorOnSurface, 0.38f)
|
||||
val unreadColor = context.getResourceColor(R.attr.colorOnSurface)
|
||||
val unreadColorSecondary = context.getResourceColor(android.R.attr.textColorSecondary)
|
||||
|
||||
val bookmarkedColor = context.getResourceColor(R.attr.colorAccent)
|
||||
|
||||
val decimalFormat = DecimalFormat(
|
||||
"#.###",
|
||||
DecimalFormatSymbols()
|
||||
.apply { decimalSeparator = '.' },
|
||||
)
|
||||
|
||||
val relativeTime: Int = preferences.relativeTime().get()
|
||||
val dateFormat: DateFormat = preferences.dateFormat()
|
||||
|
||||
override fun updateDataSet(items: List<ChapterItem>?) {
|
||||
this.items = items ?: emptyList()
|
||||
super.updateDataSet(items)
|
||||
}
|
||||
|
||||
fun indexOf(item: ChapterItem): Int {
|
||||
return items.indexOf(item)
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,11 @@ import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.manga.model.toDbManga
|
||||
import eu.kanade.domain.manga.model.toTriStateGroupState
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainManga
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaPresenter
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
||||
import eu.kanade.tachiyomi.util.view.popupMenu
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
|
||||
@@ -18,6 +19,9 @@ import eu.kanade.tachiyomi.widget.sheet.TabbedBottomSheetDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ChaptersSettingsSheet(
|
||||
private val router: Router,
|
||||
@@ -28,7 +32,7 @@ class ChaptersSettingsSheet(
|
||||
|
||||
private var manga: Manga? = null
|
||||
|
||||
val filters = Filter(context)
|
||||
private val filters = Filter(context)
|
||||
private val sort = Sort(context)
|
||||
private val display = Display(context)
|
||||
|
||||
@@ -42,8 +46,14 @@ class ChaptersSettingsSheet(
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
scope = MainScope()
|
||||
// TODO: Listen to changes
|
||||
updateManga()
|
||||
scope.launch {
|
||||
presenter.state
|
||||
.filterIsInstance<MangaScreenState.Success>()
|
||||
.collectLatest {
|
||||
manga = it.manga
|
||||
getTabViews().forEach { settings -> (settings as Settings).updateView() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
@@ -63,17 +73,13 @@ class ChaptersSettingsSheet(
|
||||
R.string.action_display,
|
||||
)
|
||||
|
||||
private fun updateManga() {
|
||||
manga = presenter.manga.toDomainManga()
|
||||
}
|
||||
|
||||
private fun showPopupMenu(view: View) {
|
||||
view.popupMenu(
|
||||
menuRes = R.menu.default_chapter_filter,
|
||||
onMenuItemClick = {
|
||||
when (itemId) {
|
||||
R.id.set_as_default -> {
|
||||
SetChapterSettingsDialog(presenter.manga).showDialog(router)
|
||||
SetChapterSettingsDialog(presenter.manga!!.toDbManga()).showDialog(router)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -144,10 +150,6 @@ class ChaptersSettingsSheet(
|
||||
bookmarked -> presenter.setBookmarkedFilter(newState)
|
||||
else -> {}
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
updateManga()
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,16 +204,11 @@ class ChaptersSettingsSheet(
|
||||
|
||||
override fun onItemClicked(item: Item) {
|
||||
when (item) {
|
||||
source -> presenter.setSorting(Manga.CHAPTER_SORTING_SOURCE.toInt())
|
||||
chapterNum -> presenter.setSorting(Manga.CHAPTER_SORTING_NUMBER.toInt())
|
||||
uploadDate -> presenter.setSorting(Manga.CHAPTER_SORTING_UPLOAD_DATE.toInt())
|
||||
source -> presenter.setSorting(Manga.CHAPTER_SORTING_SOURCE)
|
||||
chapterNum -> presenter.setSorting(Manga.CHAPTER_SORTING_NUMBER)
|
||||
uploadDate -> presenter.setSorting(Manga.CHAPTER_SORTING_UPLOAD_DATE)
|
||||
else -> throw Exception("Unknown sorting")
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
presenter.reverseSortOrder()
|
||||
updateManga()
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,14 +254,10 @@ class ChaptersSettingsSheet(
|
||||
if (item.checked) return
|
||||
|
||||
when (item) {
|
||||
displayTitle -> presenter.setDisplayMode(Manga.CHAPTER_DISPLAY_NAME.toInt())
|
||||
displayChapterNum -> presenter.setDisplayMode(Manga.CHAPTER_DISPLAY_NUMBER.toInt())
|
||||
displayTitle -> presenter.setDisplayMode(Manga.CHAPTER_DISPLAY_NAME)
|
||||
displayChapterNum -> presenter.setDisplayMode(Manga.CHAPTER_DISPLAY_NUMBER)
|
||||
else -> throw NotImplementedError("Unknown display mode")
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
updateManga()
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
|
||||
class DeleteChaptersDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
||||
where T : Controller, T : DeleteChaptersDialog.Listener {
|
||||
|
||||
constructor(target: T) : this() {
|
||||
targetController = target
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||
return MaterialAlertDialogBuilder(activity!!)
|
||||
.setMessage(R.string.confirm_delete_chapters)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
(targetController as? Listener)?.deleteChapters()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun deleteChapters()
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.MangaChaptersHeaderBinding
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
|
||||
class MangaChaptersHeaderAdapter(
|
||||
private val controller: MangaController,
|
||||
) :
|
||||
RecyclerView.Adapter<MangaChaptersHeaderAdapter.HeaderViewHolder>() {
|
||||
|
||||
private var numChapters: Int? = null
|
||||
private var hasActiveFilters: Boolean = false
|
||||
|
||||
private lateinit var binding: MangaChaptersHeaderBinding
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
|
||||
binding = MangaChaptersHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return HeaderViewHolder(binding.root)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1
|
||||
|
||||
override fun getItemId(position: Int): Long = hashCode().toLong()
|
||||
|
||||
override fun onBindViewHolder(holder: HeaderViewHolder, position: Int) {
|
||||
holder.bind()
|
||||
}
|
||||
|
||||
fun setNumChapters(numChapters: Int) {
|
||||
this.numChapters = numChapters
|
||||
notifyItemChanged(0, this)
|
||||
}
|
||||
|
||||
fun setHasActiveFilters(hasActiveFilters: Boolean) {
|
||||
this.hasActiveFilters = hasActiveFilters
|
||||
notifyItemChanged(0, this)
|
||||
}
|
||||
|
||||
inner class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
|
||||
fun bind() {
|
||||
binding.chaptersLabel.text = if (numChapters == null) {
|
||||
view.context.getString(R.string.chapters)
|
||||
} else {
|
||||
view.context.resources.getQuantityString(R.plurals.manga_num_chapters, numChapters!!, numChapters)
|
||||
}
|
||||
|
||||
val filterColor = if (hasActiveFilters) {
|
||||
view.context.getResourceColor(R.attr.colorFilterActive)
|
||||
} else {
|
||||
view.context.getResourceColor(R.attr.colorOnBackground)
|
||||
}
|
||||
binding.btnChaptersFilter.drawable.setTint(filterColor)
|
||||
|
||||
merge(view.clicks(), binding.btnChaptersFilter.clicks())
|
||||
.onEach { controller.showSettingsSheet() }
|
||||
.launchIn(controller.viewScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.info
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.databinding.MangaInfoHeaderBinding
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.base.controller.getMainAppBarHeight
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import eu.kanade.tachiyomi.util.view.loadAutoPause
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
import reactivecircus.flowbinding.android.view.longClicks
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class MangaInfoHeaderAdapter(
|
||||
private val controller: MangaController,
|
||||
private val fromSource: Boolean,
|
||||
private val isTablet: Boolean,
|
||||
) :
|
||||
RecyclerView.Adapter<MangaInfoHeaderAdapter.HeaderViewHolder>() {
|
||||
|
||||
private val trackManager: TrackManager by injectLazy()
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
|
||||
private var manga: Manga = controller.presenter.manga
|
||||
private var source: Source = controller.presenter.source
|
||||
private var trackCount: Int = 0
|
||||
|
||||
private lateinit var binding: MangaInfoHeaderBinding
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
|
||||
binding = MangaInfoHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
updateCoverPosition()
|
||||
|
||||
// Expand manga info if navigated from source listing or explicitly set to
|
||||
// (e.g. on tablets)
|
||||
binding.mangaSummarySection.expanded = fromSource || isTablet
|
||||
|
||||
return HeaderViewHolder(binding.root)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1
|
||||
|
||||
override fun getItemId(position: Int): Long = hashCode().toLong()
|
||||
|
||||
override fun onBindViewHolder(holder: HeaderViewHolder, position: Int) {
|
||||
holder.bind()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the view with manga information.
|
||||
*
|
||||
* @param manga manga object containing information about manga.
|
||||
* @param source the source of the manga.
|
||||
*/
|
||||
fun update(manga: Manga, source: Source) {
|
||||
this.manga = manga
|
||||
this.source = source
|
||||
update()
|
||||
}
|
||||
|
||||
fun update() {
|
||||
notifyItemChanged(0, this)
|
||||
}
|
||||
|
||||
fun setTrackingCount(trackCount: Int) {
|
||||
this.trackCount = trackCount
|
||||
update()
|
||||
}
|
||||
|
||||
private fun updateCoverPosition() {
|
||||
if (isTablet) return
|
||||
val appBarHeight = controller.getMainAppBarHeight()
|
||||
binding.mangaCover.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin += appBarHeight
|
||||
}
|
||||
}
|
||||
|
||||
inner class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
|
||||
fun bind() {
|
||||
// For rounded corners
|
||||
binding.mangaCover.clipToOutline = true
|
||||
|
||||
binding.btnFavorite.clicks()
|
||||
.onEach { controller.onFavoriteClick() }
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
if (controller.presenter.manga.favorite) {
|
||||
binding.btnFavorite.longClicks()
|
||||
.onEach { controller.onCategoriesClick() }
|
||||
.launchIn(controller.viewScope)
|
||||
}
|
||||
|
||||
with(binding.btnTracking) {
|
||||
if (trackManager.hasLoggedServices()) {
|
||||
isVisible = true
|
||||
|
||||
if (trackCount > 0) {
|
||||
setIconResource(R.drawable.ic_done_24dp)
|
||||
text = view.context.resources.getQuantityString(
|
||||
R.plurals.num_trackers,
|
||||
trackCount,
|
||||
trackCount,
|
||||
)
|
||||
isActivated = true
|
||||
} else {
|
||||
setIconResource(R.drawable.ic_sync_24dp)
|
||||
text = view.context.getString(R.string.manga_tracking_tab)
|
||||
isActivated = false
|
||||
}
|
||||
|
||||
clicks()
|
||||
.onEach { controller.onTrackingClick() }
|
||||
.launchIn(controller.viewScope)
|
||||
} else {
|
||||
isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
if (controller.presenter.source is HttpSource) {
|
||||
binding.btnWebview.isVisible = true
|
||||
binding.btnWebview.clicks()
|
||||
.onEach { controller.openMangaInWebView() }
|
||||
.launchIn(controller.viewScope)
|
||||
}
|
||||
|
||||
binding.mangaFullTitle.longClicks()
|
||||
.onEach {
|
||||
controller.activity?.copyToClipboard(
|
||||
view.context.getString(R.string.title),
|
||||
binding.mangaFullTitle.text.toString(),
|
||||
)
|
||||
}
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
binding.mangaFullTitle.clicks()
|
||||
.onEach {
|
||||
controller.performGlobalSearch(binding.mangaFullTitle.text.toString())
|
||||
}
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
binding.mangaAuthor.longClicks()
|
||||
.onEach {
|
||||
controller.activity?.copyToClipboard(
|
||||
binding.mangaAuthor.text.toString(),
|
||||
binding.mangaAuthor.text.toString(),
|
||||
)
|
||||
}
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
binding.mangaAuthor.clicks()
|
||||
.onEach {
|
||||
controller.performGlobalSearch(binding.mangaAuthor.text.toString())
|
||||
}
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
binding.mangaArtist.longClicks()
|
||||
.onEach {
|
||||
controller.activity?.copyToClipboard(
|
||||
binding.mangaArtist.text.toString(),
|
||||
binding.mangaArtist.text.toString(),
|
||||
)
|
||||
}
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
binding.mangaArtist.clicks()
|
||||
.onEach {
|
||||
controller.performGlobalSearch(binding.mangaArtist.text.toString())
|
||||
}
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
binding.mangaCover.clicks()
|
||||
.onEach {
|
||||
controller.showFullCoverDialog()
|
||||
}
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
setMangaInfo()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the view with manga information.
|
||||
*
|
||||
* @param manga manga object containing information about manga.
|
||||
* @param source the source of the manga.
|
||||
*/
|
||||
private fun setMangaInfo() {
|
||||
// Update full title TextView.
|
||||
binding.mangaFullTitle.text = manga.title.ifBlank { view.context.getString(R.string.unknown) }
|
||||
|
||||
// Update author TextView.
|
||||
binding.mangaAuthor.text = if (manga.author.isNullOrBlank()) {
|
||||
view.context.getString(R.string.unknown_author)
|
||||
} else {
|
||||
manga.author
|
||||
}
|
||||
|
||||
// Update artist TextView.
|
||||
val hasArtist = !manga.artist.isNullOrBlank() && manga.artist != manga.author
|
||||
binding.mangaArtist.isVisible = hasArtist
|
||||
if (hasArtist) {
|
||||
binding.mangaArtist.text = manga.artist
|
||||
}
|
||||
|
||||
// If manga source is known update source TextView.
|
||||
binding.mangaMissingSourceIcon.isVisible = source is SourceManager.StubSource
|
||||
|
||||
with(binding.mangaSource) {
|
||||
text = source.getNameForMangaInfo()
|
||||
|
||||
setOnClickListener {
|
||||
controller.performSearch(sourceManager.getOrStub(source.id).name)
|
||||
}
|
||||
}
|
||||
|
||||
// Update manga status.
|
||||
val (statusDrawable, statusString) = when (manga.status) {
|
||||
SManga.ONGOING -> R.drawable.ic_status_ongoing_24dp to R.string.ongoing
|
||||
SManga.COMPLETED -> R.drawable.ic_status_completed_24dp to R.string.completed
|
||||
SManga.LICENSED -> R.drawable.ic_status_licensed_24dp to R.string.licensed
|
||||
SManga.PUBLISHING_FINISHED -> R.drawable.ic_done_24dp to R.string.publishing_finished
|
||||
SManga.CANCELLED -> R.drawable.ic_close_24dp to R.string.cancelled
|
||||
SManga.ON_HIATUS -> R.drawable.ic_pause_24dp to R.string.on_hiatus
|
||||
else -> R.drawable.ic_status_unknown_24dp to R.string.unknown
|
||||
}
|
||||
binding.mangaStatusIcon.setImageResource(statusDrawable)
|
||||
binding.mangaStatus.setText(statusString)
|
||||
|
||||
// Set the favorite drawable to the correct one.
|
||||
setFavoriteButtonState(manga.favorite)
|
||||
|
||||
// Set cover if changed.
|
||||
binding.backdrop.loadAutoPause(manga)
|
||||
binding.mangaCover.loadAutoPause(manga)
|
||||
|
||||
// Manga info section
|
||||
binding.mangaSummarySection.setTags(manga.getGenres(), controller::performGenreSearch)
|
||||
binding.mangaSummarySection.description = manga.description
|
||||
binding.mangaSummarySection.isVisible = !manga.description.isNullOrBlank() || !manga.genre.isNullOrBlank()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update favorite button with correct drawable and text.
|
||||
*
|
||||
* @param isFavorite determines if manga is favorite or not.
|
||||
*/
|
||||
private fun setFavoriteButtonState(isFavorite: Boolean) {
|
||||
// Set the Favorite drawable to the correct one.
|
||||
// Border drawable if false, filled drawable if true.
|
||||
val (iconResource, stringResource) = when (isFavorite) {
|
||||
true -> R.drawable.ic_favorite_24dp to R.string.in_library
|
||||
false -> R.drawable.ic_favorite_border_24dp to R.string.add_to_library
|
||||
}
|
||||
binding.btnFavorite.apply {
|
||||
setIconResource(iconResource)
|
||||
text = context.getString(stringResource)
|
||||
isActivated = isFavorite
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ class TrackSearchDialog : DialogController {
|
||||
|
||||
// Do an initial search based on the manga's title
|
||||
if (savedViewState == null) {
|
||||
currentlySearched = trackController.presenter.manga.title
|
||||
currentlySearched = trackController.presenter.manga!!.title
|
||||
binding!!.titleInput.editText?.append(currentlySearched)
|
||||
}
|
||||
search(currentlySearched)
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.google.android.material.datepicker.CalendarConstraints
|
||||
import com.google.android.material.datepicker.DateValidatorPointBackward
|
||||
import com.google.android.material.datepicker.DateValidatorPointForward
|
||||
import com.google.android.material.datepicker.MaterialDatePicker
|
||||
import eu.kanade.domain.manga.model.toDbManga
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
|
||||
import eu.kanade.tachiyomi.databinding.TrackControllerBinding
|
||||
@@ -25,7 +26,7 @@ import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
|
||||
|
||||
class TrackSheet(
|
||||
val controller: MangaController,
|
||||
val fragmentManager: FragmentManager,
|
||||
private val fragmentManager: FragmentManager,
|
||||
) : BaseBottomSheetDialog(controller.activity!!),
|
||||
TrackAdapter.OnClickListener,
|
||||
SetTrackStatusDialog.Listener,
|
||||
@@ -74,8 +75,8 @@ class TrackSheet(
|
||||
|
||||
override fun onSetClick(position: Int) {
|
||||
val item = adapter.getItem(position) ?: return
|
||||
val manga = controller.presenter.manga
|
||||
val source = controller.presenter.source
|
||||
val manga = controller.presenter.manga?.toDbManga() ?: return
|
||||
val source = controller.presenter.source ?: return
|
||||
|
||||
if (item.service is EnhancedTrackService) {
|
||||
if (item.track != null) {
|
||||
|
||||
@@ -34,7 +34,7 @@ class HistoryController : ComposeController<HistoryPresenter>(), RootController
|
||||
nestedScrollInterop = nestedScrollInterop,
|
||||
presenter = presenter,
|
||||
onClickCover = { history ->
|
||||
router.pushController(MangaController(history))
|
||||
router.pushController(MangaController(history.id))
|
||||
},
|
||||
onClickResume = { history ->
|
||||
presenter.getNextChapterForManga(history.mangaId, history.chapterId)
|
||||
|
||||
Reference in New Issue
Block a user