parent
2d66185a02
commit
c2b1c3f63f
@ -0,0 +1,77 @@
|
||||
package eu.kanade.tachiyomi.ui.download
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.view.gone
|
||||
import eu.kanade.tachiyomi.util.view.visible
|
||||
import kotlinx.android.synthetic.main.download_button.view.*
|
||||
|
||||
class DownloadButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null)
|
||||
: FrameLayout(context, attrs) {
|
||||
|
||||
private val activeColor = context.getResourceColor(R.attr.colorAccent)
|
||||
private val disabledColor = ContextCompat.getColor(context,
|
||||
R.color.material_on_surface_disabled)
|
||||
private val downloadedColor = ContextCompat.getColor(context,
|
||||
R.color.material_green_800)
|
||||
private val errorColor = ContextCompat.getColor(context,
|
||||
R.color.red_error)
|
||||
private val filledCircle = ContextCompat.getDrawable(context,
|
||||
R.drawable.filled_circle)?.mutate()
|
||||
private val borderCircle = ContextCompat.getDrawable(context,
|
||||
R.drawable.border_circle)?.mutate()
|
||||
|
||||
|
||||
fun setDownoadStatus(state: Int, progress: Int = 0) {
|
||||
when (state) {
|
||||
Download.NOT_DOWNLOADED -> {
|
||||
download_border.visible()
|
||||
download_progress.gone()
|
||||
download_progress_indeterminate.gone()
|
||||
download_border.setImageDrawable(borderCircle)
|
||||
download_border.drawable.setTint(activeColor)
|
||||
download_icon.drawable.setTint(activeColor)
|
||||
}
|
||||
Download.QUEUE -> {
|
||||
download_border.gone()
|
||||
download_progress.gone()
|
||||
download_progress_indeterminate.visible()
|
||||
download_progress.isIndeterminate = true
|
||||
download_icon.drawable.setTint(disabledColor)
|
||||
}
|
||||
Download.DOWNLOADING -> {
|
||||
download_border.visible()
|
||||
download_progress.visible()
|
||||
download_progress_indeterminate.gone()
|
||||
download_border.setImageDrawable(borderCircle)
|
||||
download_progress.isIndeterminate = false
|
||||
download_progress.progress = progress
|
||||
download_border.drawable.setTint(disabledColor)
|
||||
download_progress.progressDrawable?.setTint(downloadedColor)
|
||||
download_icon.drawable.setTint(disabledColor)
|
||||
}
|
||||
Download.DOWNLOADED -> {
|
||||
download_progress.gone()
|
||||
download_border.visible()
|
||||
download_progress_indeterminate.gone()
|
||||
download_border.setImageDrawable(filledCircle)
|
||||
download_border.drawable.setTint(downloadedColor)
|
||||
download_icon.drawable.setTint(Color.WHITE)
|
||||
}
|
||||
Download.ERROR -> {
|
||||
download_progress.gone()
|
||||
download_border.visible()
|
||||
download_progress_indeterminate.gone()
|
||||
download_border.setImageDrawable(borderCircle)
|
||||
download_border.drawable.setTint(errorColor)
|
||||
download_icon.drawable.setTint(errorColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package eu.kanade.tachiyomi.ui.manga
|
||||
|
||||
import android.view.View
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter
|
||||
|
||||
abstract class MangaChapterHolder(
|
||||
private val view: View,
|
||||
private val adapter: ChaptersAdapter
|
||||
) : BaseFlexibleViewHolder(view, adapter) {
|
||||
/**
|
||||
* Method called from [ChaptersAdapter.onBindViewHolder]. It updates the data for this
|
||||
* holder with the given manga.
|
||||
*
|
||||
* @param item the manga item to bind.
|
||||
*/
|
||||
abstract fun bind(item: ChapterItem, manga: Manga)
|
||||
}
|
@ -0,0 +1,363 @@
|
||||
package eu.kanade.tachiyomi.ui.manga
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.palette.graphics.Palette
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.vectordrawable.graphics.drawable.ArgbEvaluator
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BaseController
|
||||
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.main.SearchActivity
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.view.getText
|
||||
import eu.kanade.tachiyomi.util.view.snack
|
||||
import kotlinx.android.synthetic.main.big_manga_controller.*
|
||||
import kotlinx.android.synthetic.main.main_activity.*
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class MangaChaptersController : BaseController,
|
||||
ActionMode.Callback,
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
ChaptersAdapter.MangaHeaderInterface {
|
||||
|
||||
constructor(manga: Manga?,
|
||||
fromCatalogue: Boolean = false,
|
||||
smartSearchConfig: CatalogueController.SmartSearchConfig? = null,
|
||||
update: Boolean = false) : super(Bundle().apply {
|
||||
putLong(MangaController.MANGA_EXTRA, manga?.id ?: 0)
|
||||
putBoolean(MangaController.FROM_CATALOGUE_EXTRA, fromCatalogue)
|
||||
putParcelable(MangaController.SMART_SEARCH_CONFIG_EXTRA, smartSearchConfig)
|
||||
putBoolean(MangaController.UPDATE_EXTRA, update)
|
||||
}) {
|
||||
this.manga = manga
|
||||
if (manga != null) {
|
||||
source = Injekt.get<SourceManager>().getOrStub(manga.source)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(mangaId: Long) : this(
|
||||
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking())
|
||||
|
||||
constructor(bundle: Bundle) : this(bundle.getLong(MangaController.MANGA_EXTRA)) {
|
||||
val notificationId = bundle.getInt("notificationId", -1)
|
||||
val context = applicationContext ?: return
|
||||
if (notificationId > -1) NotificationReceiver.dismissNotification(
|
||||
context, notificationId, bundle.getInt("groupId", 0)
|
||||
)
|
||||
}
|
||||
|
||||
private var manga: Manga? = null
|
||||
private var source: Source? = null
|
||||
var colorAnimator:ValueAnimator? = null
|
||||
lateinit var presenter:MangaPresenter
|
||||
var coverColor:Int? = null
|
||||
var toolbarIsColored = false
|
||||
private var snack: Snackbar? = null
|
||||
|
||||
/**
|
||||
* Adapter containing a list of chapters.
|
||||
*/
|
||||
private var adapter: ChaptersAdapter? = null
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun getTitle(): String? {
|
||||
return if (toolbarIsColored) manga?.currentTitle() else null
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
coverColor = null
|
||||
if (!::presenter.isInitialized) presenter = MangaPresenter(this, manga!!, source!!)
|
||||
|
||||
// Init RecyclerView and adapter
|
||||
adapter = ChaptersAdapter(this, view.context)
|
||||
//setReadingDrawable()
|
||||
|
||||
recycler.adapter = adapter
|
||||
recycler.layoutManager = LinearLayoutManager(view.context)
|
||||
recycler.addItemDecoration(
|
||||
DividerItemDecoration(
|
||||
view.context,
|
||||
DividerItemDecoration.VERTICAL
|
||||
)
|
||||
)
|
||||
recycler.setHasFixedSize(true)
|
||||
|
||||
presenter.onCreate()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
recycler.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
|
||||
val atTop =
|
||||
((recycler.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() == 0)
|
||||
if ((!atTop && !toolbarIsColored) || (atTop && toolbarIsColored)) {
|
||||
toolbarIsColored = !atTop
|
||||
colorAnimator?.cancel()
|
||||
val color =
|
||||
coverColor ?: activity!!.getResourceColor(android.R.attr.colorPrimary)
|
||||
val colorFrom = ColorUtils.setAlphaComponent(
|
||||
color, if (toolbarIsColored) 0 else 255
|
||||
)
|
||||
val colorTo = ColorUtils.setAlphaComponent(
|
||||
color, if (toolbarIsColored) 255 else 0
|
||||
)
|
||||
colorAnimator = ValueAnimator.ofObject(
|
||||
ArgbEvaluator(), colorFrom, colorTo
|
||||
)
|
||||
colorAnimator?.duration = 250 // milliseconds
|
||||
//colorAnimation.startDelay = 150
|
||||
colorAnimator?.addUpdateListener { animator ->
|
||||
(activity as MainActivity).toolbar.setBackgroundColor(animator.animatedValue as Int)
|
||||
//activity?.window?.statusBarColor = (animator.animatedValue as Int)
|
||||
}
|
||||
colorAnimator?.start()
|
||||
val isCurrentController = router?.backstack?.lastOrNull()?.controller() == this
|
||||
if (isCurrentController) setTitle()
|
||||
}
|
||||
}
|
||||
}
|
||||
GlideApp.with(view.context).load(manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga!!.id!!).toString()))
|
||||
.into(object : CustomTarget<Drawable>() {
|
||||
override fun onResourceReady(resource: Drawable,
|
||||
transition: Transition<in Drawable>?
|
||||
) {
|
||||
Palette.from(
|
||||
(resource as BitmapDrawable).bitmap).generate {
|
||||
if (recycler == null) return@generate
|
||||
val currentNightMode =
|
||||
recycler.resources!!.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
||||
val colorBack = view.context.getResourceColor(
|
||||
android.R.attr.colorBackground
|
||||
)
|
||||
val backDropColor =
|
||||
(if (currentNightMode == Configuration.UI_MODE_NIGHT_NO) it?.getLightMutedColor(
|
||||
colorBack
|
||||
)
|
||||
else it?.getDarkMutedColor(colorBack)) ?: colorBack
|
||||
onCoverLoaded(backDropColor)
|
||||
(recycler.findViewHolderForItemId(-1) as? MangaHeaderHolder)
|
||||
?.setBackDrop(backDropColor)
|
||||
if (toolbarIsColored)
|
||||
(activity as MainActivity).toolbar.setBackgroundColor(backDropColor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) { }
|
||||
})
|
||||
//adapter?.fastScroller = fast_scroller
|
||||
}
|
||||
|
||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||
super.onChangeStarted(handler, type)
|
||||
if (type == ControllerChangeType.PUSH_ENTER || type == ControllerChangeType.POP_ENTER) {
|
||||
(activity as MainActivity).appbar.setBackgroundColor(Color.TRANSPARENT)
|
||||
(activity as MainActivity).toolbar.setBackgroundColor(Color.TRANSPARENT)
|
||||
/* val colorFrom = ((activity as MainActivity).toolbar.background as ColorDrawable).color
|
||||
val colorTo = Color.TRANSPARENT
|
||||
colorAnimator = ValueAnimator.ofObject(
|
||||
ArgbEvaluator(), colorFrom, colorTo)
|
||||
colorAnimator?.duration = 250 // milliseconds
|
||||
//colorAnimation.startDelay = 150
|
||||
colorAnimator?.addUpdateListener { animator ->
|
||||
(activity as MainActivity).toolbar.setBackgroundColor(animator.animatedValue as Int)
|
||||
//activity?.window?.statusBarColor = (animator.animatedValue as Int)
|
||||
}
|
||||
colorAnimator?.start()*/
|
||||
|
||||
/*activity!!.window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
|
||||
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val insetTop = activity!!.window.decorView.rootWindowInsets.systemWindowInsetTop
|
||||
val insetBottom = activity!!.window.decorView.rootWindowInsets.stableInsetBottom
|
||||
(activity)?.appbar?.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
topMargin = insetTop
|
||||
}
|
||||
|
||||
(activity)?.navigationView?.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
bottomMargin = insetBottom
|
||||
}
|
||||
}*/
|
||||
|
||||
//
|
||||
//(activity as MainActivity).toolbar.setBackgroundColor(Color.TRANSPARENT)
|
||||
//(activity as MainActivity).appbar.gone()
|
||||
}
|
||||
else if (type == ControllerChangeType.PUSH_EXIT || type == ControllerChangeType.POP_EXIT) {
|
||||
colorAnimator?.cancel()
|
||||
|
||||
(activity as MainActivity).toolbar.setBackgroundColor(activity?.getResourceColor(
|
||||
android.R.attr.colorPrimary
|
||||
) ?: Color.BLACK)
|
||||
|
||||
// activity!!.window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||
|
||||
activity?.window?.statusBarColor = activity?.getResourceColor(
|
||||
android.R.attr.colorPrimary
|
||||
) ?: Color.BLACK
|
||||
/*(activity as MainActivity).appbar.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
topMargin = 0
|
||||
}
|
||||
(activity as MainActivity).navigationView.updateLayoutParams<ConstraintLayout
|
||||
.LayoutParams> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
bottomMargin = 0
|
||||
}
|
||||
}*/
|
||||
//(activity as MainActivity).appbar.background = null
|
||||
// (activity as AppCompatActivity).supportActionBar?.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun updateChapters(chapters: List<ChapterItem>) {
|
||||
if (presenter.chapters.isEmpty()) {
|
||||
//initialFetchChapters()
|
||||
}
|
||||
adapter?.updateDataSet(listOf(ChapterItem(Chapter.create(), manga!!)) + chapters)
|
||||
}
|
||||
|
||||
override fun onItemClick(view: View?, position: Int): Boolean {
|
||||
val adapter = adapter ?: return false
|
||||
val chapter = adapter.getItem(position)?.chapter ?: return false
|
||||
if (!chapter.isRecognizedNumber) return false
|
||||
/*if (actionMode != null && adapter.mode == SelectableAdapter.Mode.MULTI) {
|
||||
lastClickPosition = position
|
||||
toggleSelection(position)
|
||||
return true
|
||||
} else {*/
|
||||
openChapter(chapter)
|
||||
return false
|
||||
//}
|
||||
}
|
||||
|
||||
fun openChapter(chapter: Chapter, hasAnimation: Boolean = false) {
|
||||
val activity = activity ?: return
|
||||
val intent = ReaderActivity.newIntent(activity, manga!!, chapter)
|
||||
if (hasAnimation) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
fun getStatusBarHeight(): Int {
|
||||
var result = 0
|
||||
val resourceId = resources!!.getIdentifier("status_bar_height", "dimen", "android")
|
||||
if (resourceId > 0) {
|
||||
result = resources!!.getDimensionPixelSize(resourceId)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.chapters, menu)
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
return inflater.inflate(R.layout.big_manga_controller, container, false)
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
fun onCoverLoaded(color: Int) {
|
||||
if (view == null) return
|
||||
coverColor = color
|
||||
activity?.window?.statusBarColor = color
|
||||
}
|
||||
|
||||
override fun coverColor(): Int? = coverColor
|
||||
|
||||
override fun nextChapter(): Chapter? {
|
||||
return presenter.getNextUnreadChapter()
|
||||
}
|
||||
|
||||
override fun readNextChapter() {
|
||||
if (activity is SearchActivity && presenter.isLockedFromSearch) {
|
||||
SecureActivityDelegate.promptLockIfNeeded(activity)
|
||||
return
|
||||
}
|
||||
val item = presenter.getNextUnreadChapter()
|
||||
if (item != null) {
|
||||
openChapter(item.chapter)
|
||||
} else if (snack == null || snack?.getText() != view?.context?.getString(
|
||||
R.string.no_next_chapter)) {
|
||||
snack = view?.snack(R.string.no_next_chapter, Snackbar.LENGTH_LONG) {
|
||||
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
|
||||
super.onDismissed(transientBottomBar, event)
|
||||
if (snack == transientBottomBar) snack = null
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun downloadChapter(position: Int) {
|
||||
val adapter = adapter ?: return
|
||||
val chapter = adapter.getItem(position) ?: return
|
||||
if (!chapter.isRecognizedNumber) return
|
||||
if (chapter.isDownloaded) {
|
||||
presenter.deleteChapters(listOf(chapter))
|
||||
}
|
||||
else presenter.downloadChapters(listOf(chapter))
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package eu.kanade.tachiyomi.ui.manga
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.view.visibleIf
|
||||
import kotlinx.android.synthetic.main.manga_header_item.*
|
||||
|
||||
class MangaHeaderHolder(
|
||||
private val view: View,
|
||||
private val adapter: ChaptersAdapter
|
||||
) : MangaChapterHolder(view, adapter) {
|
||||
|
||||
init {
|
||||
start_reading_button.setOnClickListener { adapter.coverListener?.readNextChapter() }
|
||||
}
|
||||
|
||||
override fun bind(item: ChapterItem, manga: Manga) {
|
||||
manga_title.text = manga.currentTitle()
|
||||
if (manga.currentAuthor() == manga.currentArtist() ||
|
||||
manga.currentArtist().isNullOrBlank())
|
||||
manga_author.text = manga.currentAuthor()
|
||||
else {
|
||||
manga_author.text = "${manga.currentAuthor()?.trim()}, ${manga.currentArtist()}"
|
||||
}
|
||||
manga_summary.text = manga.currentDesc()
|
||||
manga_summary_label.text = "About this ${if (manga.mangaType() == Manga.TYPE_MANGA) "Manga"
|
||||
else "Manhwa"}"
|
||||
with(favorite_button) {
|
||||
icon = ContextCompat.getDrawable(
|
||||
itemView.context, when {
|
||||
item.isLocked -> R.drawable.ic_lock_white_24dp
|
||||
manga.favorite -> R.drawable.ic_bookmark_white_24dp
|
||||
else -> R.drawable.ic_add_to_library_24dp
|
||||
}
|
||||
)
|
||||
text = itemView.resources.getString(
|
||||
when {
|
||||
item.isLocked -> R.string.unlock
|
||||
manga.favorite -> R.string.in_library
|
||||
else -> R.string.add_to_library
|
||||
}
|
||||
)
|
||||
backgroundTintList =
|
||||
ContextCompat.getColorStateList(context, android.R.color.transparent)
|
||||
if (!item.isLocked && manga.favorite) {
|
||||
backgroundTintList =
|
||||
ColorStateList.valueOf(
|
||||
ColorUtils.setAlphaComponent(
|
||||
context.getResourceColor(R.attr.colorAccent), 75))
|
||||
strokeColor = ColorStateList.valueOf(Color.TRANSPARENT)
|
||||
}
|
||||
}
|
||||
true_backdrop.setBackgroundColor(adapter.coverListener?.coverColor() ?:
|
||||
itemView.context.getResourceColor(android.R.attr.colorBackground))
|
||||
|
||||
with(start_reading_button) {
|
||||
val nextChapter = adapter.coverListener?.nextChapter()
|
||||
visibleIf(nextChapter != null && !item.isLocked)
|
||||
if (nextChapter != null) {
|
||||
val number = adapter.decimalFormat.format(nextChapter.chapter_number.toDouble())
|
||||
text = resources.getString(if (nextChapter.last_page_read > 0)
|
||||
R.string.continue_reader_chapter
|
||||
else R.string.start_reader_chapter, number)
|
||||
}
|
||||
}
|
||||
|
||||
GlideApp.with(view.context).load(manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString()))
|
||||
.into(manga_cover)
|
||||
GlideApp.with(view.context).load(manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString()))
|
||||
.centerCrop()
|
||||
.into(backdrop)
|
||||
}
|
||||
|
||||
fun setBackDrop(color: Int) {
|
||||
true_backdrop.setBackgroundColor(color)
|
||||
}
|
||||
}
|
@ -0,0 +1,258 @@
|
||||
package eu.kanade.tachiyomi.ui.manga
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class MangaPresenter(private val controller: MangaChaptersController,
|
||||
val manga: Manga,
|
||||
val source: Source,
|
||||
val preferences: PreferencesHelper = Injekt.get(),
|
||||
private val db: DatabaseHelper = Injekt.get(),
|
||||
private val downloadManager: DownloadManager = Injekt.get()) {
|
||||
|
||||
|
||||
var isLockedFromSearch = false
|
||||
|
||||
var chapters:List<ChapterItem> = emptyList()
|
||||
private set
|
||||
fun onCreate() {
|
||||
isLockedFromSearch = SecureActivityDelegate.shouldBeLocked()
|
||||
|
||||
val chapters = db.getChapters(manga).executeAsBlocking().map { it.toModel() }
|
||||
|
||||
|
||||
// Store the last emission
|
||||
this.chapters = applyChapterFilters(chapters)
|
||||
|
||||
// Find downloaded chapters
|
||||
setDownloadedChapters(chapters)
|
||||
|
||||
controller.updateChapters(this.chapters)
|
||||
|
||||
// Listen for download status changes
|
||||
//observeDownloads()
|
||||
|
||||
// Emit the number of chapters to the info tab.
|
||||
//chapterCountRelay.call(chapters.maxBy { it.chapter_number }?.chapter_number ?: 0f)
|
||||
|
||||
// Emit the upload date of the most recent chapter
|
||||
/*lastUpdateRelay.call(
|
||||
Date(chapters.maxBy { it.date_upload }?.date_upload ?: 0)
|
||||
)*/
|
||||
|
||||
/* // Prepare the relay.
|
||||
chaptersRelay.flatMap { applyChapterFilters(it) }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeLatestCache(
|
||||
ChaptersController::onNextChapters
|
||||
) { _, error -> Timber.e(error) }
|
||||
|
||||
// Add the subscription that retrieves the chapters from the database, keeps subscribed to
|
||||
// changes, and sends the list of chapters to the relay.
|
||||
add(db.getChapters(manga).asRxObservable()
|
||||
.map { chapters ->
|
||||
// Convert every chapter to a model.
|
||||
chapters.map { it.toModel() }
|
||||
}
|
||||
.doOnNext { chapters ->
|
||||
// Find downloaded chapters
|
||||
setDownloadedChapters(chapters)
|
||||
|
||||
// Store the last emission
|
||||
this.chapters = chapters
|
||||
|
||||
// Listen for download status changes
|
||||
observeDownloads()
|
||||
|
||||
// Emit the number of chapters to the info tab.
|
||||
chapterCountRelay.call(chapters.maxBy { it.chapter_number }?.chapter_number
|
||||
?: 0f)
|
||||
|
||||
// Emit the upload date of the most recent chapter
|
||||
lastUpdateRelay.call(
|
||||
Date(chapters.maxBy { it.date_upload }?.date_upload
|
||||
?: 0)
|
||||
)
|
||||
|
||||
}
|
||||
.subscribe { chaptersRelay.call(it) })*/
|
||||
}
|
||||
|
||||
/*private fun observeDownloads() {
|
||||
observeDownloadsSubscription?.let { remove(it) }
|
||||
observeDownloadsSubscription = downloadManager.queue.getStatusObservable()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.filter { download -> download.manga.id == manga.id }
|
||||
.doOnNext { onDownloadStatusChange(it) }
|
||||
.subscribeLatestCache(ChaptersController::onChapterStatusChange) {
|
||||
_, error -> Timber.e(error)
|
||||
}
|
||||
}*/
|
||||
/**
|
||||
* Finds and assigns the list of downloaded chapters.
|
||||
*
|
||||
* @param chapters the list of chapter from the database.
|
||||
*/
|
||||
private fun setDownloadedChapters(chapters: List<ChapterItem>) {
|
||||
for (chapter in chapters) {
|
||||
if (downloadManager.isChapterDownloaded(chapter, manga)) {
|
||||
chapter.status = Download.DOWNLOADED
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Converts a chapter from the database to an extended model, allowing to store new fields.
|
||||
*/
|
||||
private fun Chapter.toModel(): ChapterItem {
|
||||
// Create the model object.
|
||||
val model = ChapterItem(this, manga)
|
||||
model.isLocked = isLockedFromSearch
|
||||
|
||||
// Find an active download for this chapter.
|
||||
val download = downloadManager.queue.find { it.chapter.id == id }
|
||||
|
||||
if (download != null) {
|
||||
// If there's an active download, assign it.
|
||||
model.download = download
|
||||
}
|
||||
return model
|
||||
}
|
||||
/**
|
||||
* Sets the active display mode.
|
||||
* @param mode the mode to set.
|
||||
*/
|
||||
fun setDisplayMode(mode: Int) {
|
||||
manga.displayMode = mode
|
||||
db.updateFlags(manga).executeAsBlocking()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sorting method and requests an UI update.
|
||||
* @param sort the sorting mode.
|
||||
*/
|
||||
fun setSorting(sort: Int) {
|
||||
manga.sorting = sort
|
||||
db.updateFlags(manga).executeAsBlocking()
|
||||
// refreshChapters()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the display only downloaded filter is enabled.
|
||||
*/
|
||||
fun onlyDownloaded(): Boolean {
|
||||
return manga.downloadedFilter == Manga.SHOW_DOWNLOADED
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the display only downloaded filter is enabled.
|
||||
*/
|
||||
fun onlyBookmarked(): Boolean {
|
||||
return manga.bookmarkedFilter == Manga.SHOW_BOOKMARKED
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the display only unread filter is enabled.
|
||||
*/
|
||||
fun onlyUnread(): Boolean {
|
||||
return manga.readFilter == Manga.SHOW_UNREAD
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the display only read filter is enabled.
|
||||
*/
|
||||
fun onlyRead(): Boolean {
|
||||
return manga.readFilter == Manga.SHOW_READ
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the sorting method is descending or ascending.
|
||||
*/
|
||||
fun sortDescending(): Boolean {
|
||||
return manga.sortDescending()
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the view filters to the list of chapters obtained from the database.
|
||||
* @param chapterList the list of chapters from the database
|
||||
* @return an observable of the list of chapters filtered and sorted.
|
||||
*/
|
||||
private fun applyChapterFilters(chapterList: List<ChapterItem>): List<ChapterItem> {
|
||||
var chapters = chapterList
|
||||
if (onlyUnread()) {
|
||||
chapters = chapters.filter { !it.read }
|
||||
} else if (onlyRead()) {
|
||||
chapters = chapters.filter { it.read }
|
||||
}
|
||||
if (onlyDownloaded()) {
|
||||
chapters = chapters.filter { it.isDownloaded || it.manga.source == LocalSource.ID }
|
||||
}
|
||||
if (onlyBookmarked()) {
|
||||
chapters = chapters.filter { it.bookmark }
|
||||
}
|
||||
val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
|
||||
Manga.SORTING_SOURCE -> when (sortDescending()) {
|
||||
true -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) }
|
||||
false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
|
||||
}
|
||||
Manga.SORTING_NUMBER -> when (sortDescending()) {
|
||||
true -> { c1, c2 -> c2.chapter_number.compareTo(c1.chapter_number) }
|
||||
false -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) }
|
||||
}
|
||||
else -> throw NotImplementedError("Unimplemented sorting method")
|
||||
}
|
||||
chapters = chapters.sortedWith(Comparator(sortFunction))
|
||||
//if (sortDescending())
|
||||
// chapters = chapters.reversed()
|
||||
return chapters
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next unread chapter or null if everything is read.
|
||||
*/
|
||||
fun getNextUnreadChapter(): ChapterItem? {
|
||||
return chapters.sortedByDescending { it.source_order }.find { !it.read }
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the given list of chapters with the manager.
|
||||
* @param chapters the list of chapters to download.
|
||||
*/
|
||||
fun downloadChapters(chapters: List<ChapterItem>) {
|
||||
downloadManager.downloadChapters(manga, chapters)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given list of chapter.
|
||||
* @param chapters the list of chapters to delete.
|
||||
*/
|
||||
fun deleteChapters(chapters: List<ChapterItem>) {
|
||||
deleteChaptersInternal(chapters)
|
||||
|
||||
setDownloadedChapters(chapters)
|
||||
|
||||
controller.updateChapters(this.chapters)
|
||||
// if (onlyDownloaded()) refreshChapters() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a list of chapters from disk. This method is called in a background thread.
|
||||
* @param chapters the chapters to delete.
|
||||
*/
|
||||
private fun deleteChaptersInternal(chapters: List<ChapterItem>) {
|
||||
downloadManager.deleteChapters(chapters, manga, source)
|
||||
chapters.forEach {
|
||||
it.status = Download.NOT_DOWNLOADED
|
||||
it.download = null
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaChapterHolder
|
||||
import eu.kanade.tachiyomi.util.view.gone
|
||||
import eu.kanade.tachiyomi.util.view.invisible
|
||||
import eu.kanade.tachiyomi.util.view.visible
|
||||
import kotlinx.android.synthetic.main.chapters_mat_item.*
|
||||
import kotlinx.android.synthetic.main.download_button.*
|
||||
|
||||
class ChapterMatHolder(
|
||||
private val view: View,
|
||||
private val adapter: ChaptersAdapter
|
||||
) : MangaChapterHolder(view, adapter) {
|
||||
|
||||
init {
|
||||
// We need to post a Runnable to show the popup to make sure that the PopupMenu is
|
||||
// correctly positioned. The reason being that the view may change position before the
|
||||
// PopupMenu is shown.
|
||||
//chapter_menu.setOnClickListener { it.post { showPopupMenu(it) } }
|
||||
download_button.setOnClickListener { downloadOrRemoveMenu() }
|
||||
}
|
||||
|
||||
private fun downloadOrRemoveMenu() {
|
||||
val chapter = adapter.getItem(adapterPosition) ?: return
|
||||
if (chapter.status != Download.NOT_DOWNLOADED) {
|
||||
download_button.post {
|
||||
// Create a PopupMenu, giving it the clicked view for an anchor
|
||||
val popup = PopupMenu(download_button.context, download_button)
|
||||
|
||||
// Inflate our menu resource into the PopupMenu's Menu
|
||||
popup.menuInflater.inflate(R.menu.chapter_download, popup.menu)
|
||||
|
||||
// Hide download and show delete if the chapter is downloaded
|
||||
if (chapter.status != Download.DOWNLOADED) popup.menu.findItem(R.id.action_delete)
|
||||
.title = download_button.context.getString(
|
||||
R.string.action_cancel
|
||||
)
|
||||
|
||||
// Set a listener so we are notified if a menu item is clicked
|
||||
popup.setOnMenuItemClickListener { _ ->
|
||||
adapter.coverListener?.downloadChapter(adapterPosition)
|
||||
true
|
||||
}
|
||||
|
||||
// Finally show the PopupMenu
|
||||
popup.show()
|
||||
}
|
||||
}
|
||||
else {
|
||||
adapter.coverListener?.downloadChapter(adapterPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun bind(item: ChapterItem, manga: Manga) {
|
||||
val chapter = item.chapter
|
||||
val isLocked = item.isLocked
|
||||
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
|
||||
}
|
||||
|
||||
//chapter_menu.visible()
|
||||
// Set the correct drawable for dropdown and update the tint to match theme.
|
||||
//chapter_menu.setVectorCompat(R.drawable.ic_more_vert_black_24dp, view.context
|
||||
// .getResourceColor(R.attr.icon_color))
|
||||
|
||||
if (isLocked) download_button.invisible()
|
||||
|
||||
// Set correct text color
|
||||
chapter_title.setTextColor(if (chapter.read && !isLocked)
|
||||
adapter.readColor else adapter.unreadColor)
|
||||
if (chapter.bookmark && !isLocked) chapter_title.setTextColor(adapter.bookmarkedColor)
|
||||
|
||||
/*if (chapter.date_upload > 0) {
|
||||
chapter_date.text = adapter.dateFormat.format(Date(chapter.date_upload))
|
||||
chapter_date.setTextColor(if (chapter.read) adapter.readColor else adapter.unreadColor)
|
||||
} else {
|
||||
chapter_date.text = ""
|
||||
}*/
|
||||
|
||||
//add scanlator if exists
|
||||
chapter_scanlator.text = chapter.scanlator ?: " "
|
||||
//allow longer titles if there is no scanlator (most sources)
|
||||
/*if (chapter_scanlator.text.isNullOrBlank()) {
|
||||
chapter_title.maxLines = 2
|
||||
//chapter_scanlator.gone()
|
||||
} else {
|
||||
chapter_title.maxLines = 1
|
||||
}*/
|
||||
|
||||
/* chapter_pages.text = if (!chapter.read && chapter.last_page_read > 0 && !isLocked) {
|
||||
itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1)
|
||||
} else {
|
||||
""
|
||||
}*/
|
||||
|
||||
notifyStatus(item.status, item.isLocked, item.progress)
|
||||
}
|
||||
|
||||
fun notifyStatus(status: Int, locked: Boolean, progress: Int) = with(download_button) {
|
||||
if (locked) {
|
||||
gone()
|
||||
return
|
||||
}
|
||||
visible()
|
||||
setDownoadStatus(status, progress)
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
|
||||
android:shape="oval"
|
||||
android:thicknessRatio="2">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<size
|
||||
android:height="25dp"
|
||||
android:width="25dp" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="?colorAccent" />
|
||||
</shape>
|
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item android:id="@android:id/progress">
|
||||
<rotate
|
||||
android:fromDegrees="270"
|
||||
android:toDegrees="270"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%" >
|
||||
<shape
|
||||
android:shape="ring"
|
||||
android:thickness="2dp">
|
||||
<gradient
|
||||
android:centerColor="@color/gray_button"
|
||||
android:endColor="@color/gray_button"
|
||||
android:startColor="@color/gray_button"
|
||||
android:type="sweep" />
|
||||
</shape>
|
||||
</rotate>
|
||||
</item>
|
||||
</layer-list>
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
|
||||
android:shape="oval"
|
||||
android:thicknessRatio="2">
|
||||
<solid android:color="?colorAccent" />
|
||||
<size
|
||||
android:height="25dp"
|
||||
android:width="25dp" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="?colorAccent" />
|
||||
</shape>
|
@ -0,0 +1,9 @@
|
||||
<!-- drawable/earth.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?actionBarTintColor">
|
||||
<path android:fillColor="#000" android:pathData="M17.9,17.39C17.64,16.59 16.89,16 16,16H15V13A1,1 0 0,0 14,12H8V10H10A1,1 0 0,0 11,9V7H13A2,2 0 0,0 15,5V4.59C17.93,5.77 20,8.64 20,12C20,14.08 19.2,15.97 17.9,17.39M11,19.93C7.05,19.44 4,16.08 4,12C4,11.38 4.08,10.78 4.21,10.21L9,15V16A2,2 0 0,0 11,18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
||||
</vector>
|
@ -1,11 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
||||
android:id="@+id/catalogue_recycler"
|
||||
<eu.kanade.tachiyomi.widget.AutofitRecyclerView android:id="@+id/catalogue_recycler"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/Theme.Widget.GridView.Catalogue"
|
||||
android:layout_width="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_height="match_parent"
|
||||
android:columnWidth="120dp"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/catalogue_grid_item" />
|
||||
tools:listitem="@layout/catalogue_grid_item"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto" />
|
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectable_list_drawable">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chapter_title"
|
||||
style="@style/TextAppearance.Regular.Body1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
app:layout_constraintBottom_toTopOf="@id/chapter_scanlator"
|
||||
app:layout_constraintEnd_toStartOf="@id/download_button"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Chapter 123 - The Real One" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chapter_scanlator"
|
||||
style="@style/TextAppearance.Regular.Caption.Hint"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:maxLines="1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/download_button"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chapter_title"
|
||||
tools:text="3 days ago • On page 45 • Scanlator" />
|
||||
|
||||
<include layout="@layout/download_button"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="50dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<eu.kanade.tachiyomi.ui.download.DownloadButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/download_button"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/download_border"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/border_circle"
|
||||
android:padding="3dp"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/download_progress_indeterminate"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:visibility="gone"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/material_on_surface_disabled"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/download_progress"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:progressDrawable="@drawable/circle_progress"
|
||||
android:layout_gravity="center"
|
||||
android:progressTint="@color/material_green_800"
|
||||
android:progressBackgroundTint="@color/material_on_surface_disabled"/>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/download_icon"
|
||||
android:tint="?colorAccent"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:background="@drawable/round_ripple"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_arrow_down_white_24dp" />
|
||||
</eu.kanade.tachiyomi.ui.download.DownloadButton>
|
@ -0,0 +1,348 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/manga_header_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<View
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="52dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/true_backdrop" />
|
||||
|
||||
<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.35" />
|
||||
|
||||
<View
|
||||
android:id="@+id/true_backdrop"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="200dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:background="@color/material_red_400" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/backdrop"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="200dp"
|
||||
android:alpha="0.1"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@mipmap/ic_launcher" />
|
||||
|
||||
<View
|
||||
android:id="@+id/backdrop_gradient"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="300dp"
|
||||
android:background="@drawable/gradient_shape"
|
||||
android:backgroundTint="?android:attr/colorBackground"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/true_backdrop" />
|
||||
|
||||
<View
|
||||
android:id="@+id/top_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/top_line"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/top_line"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:barrierDirection="top"
|
||||
app:constraint_referenced_ids="cover_card,manga_layout" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/manga_layout"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/guideline"
|
||||
app:layout_constraintDimensionRatio="h,7:10"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/top_line">
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/cover_card"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/manga_layout"
|
||||
app:layout_constraintEnd_toEndOf="@id/manga_layout"
|
||||
app:layout_constraintStart_toStartOf="@id/manga_layout"
|
||||
app:layout_constraintTop_toTopOf="@id/top_line"
|
||||
app:layout_constraintVertical_bias="1.0">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/manga_cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@string/description_cover"
|
||||
android:maxHeight="300dp"
|
||||
tools:background="@color/material_grey_700"
|
||||
tools:src="@mipmap/ic_launcher" />
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/manga_title"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="4"
|
||||
android:text="@string/manga_info_full_title_label"
|
||||
android:textIsSelectable="false"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/manga_layout"
|
||||
app:layout_constraintTop_toTopOf="@id/cover_card"
|
||||
tools:text="Title Example" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/manga_author"
|
||||
style="@style/TextAppearance.Regular.Body1.Secondary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/manga_info_author_label"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/manga_title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/manga_title" />
|
||||
|
||||
<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="@id/manga_title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/manga_author" />
|
||||
|
||||
<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" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/manga_status_source"
|
||||
style="@style/TextAppearance.Regular.Body1.Secondary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/manga_title"
|
||||
app:layout_constraintTop_toBottomOf="@id/manga_last_update_label"
|
||||
tools:text="Completed • Mangadex (EN)" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/bottom_line"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="6dp"
|
||||
android:orientation="horizontal"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="manga_status_source,manga_layout" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/button_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="14dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/manga_summary_label"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/bottom_line">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Theme.Widget.Button.RounededOutline"
|
||||
android:id="@+id/favorite_button"
|
||||
android:text="@string/add_to_library"
|
||||
app:icon="@drawable/ic_add_to_library_24dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Theme.Widget.Button.RounededOutline"
|
||||
android:layout_marginStart="6dp"
|
||||
android:id="@+id/track_button"
|
||||
android:text="@string/manga_tracking_tab"
|
||||
app:icon="@drawable/ic_sync_black_24dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/download_button"
|
||||
style="@style/Theme.Widget.CustomImageButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_edit_white_24dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/chapters_title"
|
||||
app:layout_constraintEnd_toStartOf="@id/sort_button" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/manga_summary_label"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/description"
|
||||
android:textIsSelectable="false"
|
||||
android:textSize="17sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/button_layout"
|
||||
tools:text="About this manga" />
|
||||
|
||||
<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:maxLines="3"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@id/start_reading_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/manga_summary_label"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." />
|
||||
|
||||
<View
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:background="@drawable/full_gradient"
|
||||
android:backgroundTint="?android:attr/colorBackground"
|
||||
app:layout_constraintBottom_toBottomOf="@id/manga_summary"
|
||||
app:layout_constraintEnd_toEndOf="@id/more_button" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:background="?android:attr/colorBackground"
|
||||
app:layout_constraintBottom_toBottomOf="@id/manga_summary"
|
||||
app:layout_constraintEnd_toEndOf="@id/more_button"
|
||||
app:layout_constraintStart_toStartOf="@id/more_button" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/more_button"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20sp"
|
||||
android:text="More"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="?colorAccent"
|
||||
app:layout_constraintEnd_toEndOf="@id/manga_summary"
|
||||
app:layout_constraintTop_toTopOf="@id/manga_summary"
|
||||
app:rippleColor="@color/gray_button" />
|
||||
|
||||
<me.gujun.android.taggroup.TagGroup
|
||||
android:id="@+id/manga_genres_tags"
|
||||
style="@style/TagGroup"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:visibility="gone"
|
||||
app:atg_backgroundColor="@android:color/transparent"
|
||||
app:atg_borderColor="@color/md_blue_A400"
|
||||
app:atg_borderStrokeWidth="1dp"
|
||||
app:atg_textColor="@color/md_blue_A400"
|
||||
app:layout_constrainedHeight="true"
|
||||
app:layout_constraintBottom_toTopOf="@id/start_reading_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/manga_summary" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/start_reading_button"
|
||||
style="@style/Theme.Widget.Button.Primary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/start_reading"
|
||||
app:layout_constraintBottom_toTopOf="@id/chapters_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/manga_genres_tags"
|
||||
tools:text="Continue Reading Chapter 17.1" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/chapters_title"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="18dp"
|
||||
android:text="@string/chapters"
|
||||
android:textSize="17sp"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/manga_summary_label"
|
||||
app:layout_constraintTop_toBottomOf="@id/start_reading_button" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sort_button"
|
||||
style="@style/Theme.Widget.CustomImageButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_swap_vert_white_24dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/chapters_title"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/filter_button"
|
||||
style="@style/Theme.Widget.CustomImageButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_filter_list_white_24dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/chapters_title"
|
||||
app:layout_constraintEnd_toStartOf="@id/sort_button" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/action_delete"
|
||||
android:title="@string/action_remove_download"
|
||||
android:icon="@drawable/ic_delete_white_24dp"/>
|
||||
</menu>
|
Loading…
Reference in new issue