More work on new manga controller

Synced download badge  for chapters items with downloads
Download arrow now pulses while it's downloading
Started work on the filter/sort bottom sheet for chapters
Expanding description
This commit is contained in:
Jay 2020-03-03 00:26:17 -08:00
parent c3620b74f1
commit d58923acbf
23 changed files with 1009 additions and 207 deletions

View File

@ -294,4 +294,6 @@ class DownloadManager(val context: Context) {
}
}
fun addListener(listener: DownloadQueue.DownloadListener) = queue.addListener(listener)
fun removeListener(listener: DownloadQueue.DownloadListener) = queue.removeListener(listener)
}

View File

@ -18,14 +18,27 @@ class Download(val source: HttpSource, val manga: Manga, val chapter: Chapter) {
set(status) {
field = status
statusSubject?.onNext(this)
statusCallback?.invoke(this)
}
@Transient private var statusSubject: PublishSubject<Download>? = null
@Transient private var statusCallback: ((Download) -> Unit)? = null
val progress: Int
get() {
val pages = pages ?: return 0
return pages.map(Page::progress).average().toInt()
}
fun setStatusSubject(subject: PublishSubject<Download>?) {
statusSubject = subject
}
fun setStatusCallback(f: ((Download) -> Unit)?) {
statusCallback = f
}
companion object {
const val NOT_DOWNLOADED = 0

View File

@ -18,9 +18,12 @@ class DownloadQueue(
private val updatedRelay = PublishRelay.create<Unit>()
private val downloadListeners = mutableListOf<DownloadListener>()
fun addAll(downloads: List<Download>) {
downloads.forEach { download ->
download.setStatusSubject(statusSubject)
download.setStatusCallback(::setPagesFor)
download.status = Download.QUEUE
}
queue.addAll(downloads)
@ -32,6 +35,10 @@ class DownloadQueue(
val removed = queue.remove(download)
store.remove(download)
download.setStatusSubject(null)
download.setStatusCallback(null)
if (download.status == Download.DOWNLOADING || download.status == Download.QUEUE)
download.status = Download.NOT_DOWNLOADED
downloadListeners.forEach { it.updateDownload(download) }
if (removed) {
updatedRelay.call(Unit)
}
@ -52,6 +59,10 @@ class DownloadQueue(
fun clear() {
queue.forEach { download ->
download.setStatusSubject(null)
download.setStatusCallback(null)
if (download.status == Download.DOWNLOADING || download.status == Download.QUEUE)
download.status = Download.NOT_DOWNLOADED
downloadListeners.forEach { it.updateDownload(download) }
}
queue.clear()
store.clear()
@ -67,6 +78,27 @@ class DownloadQueue(
.startWith(Unit)
.map { this }
private fun setPagesFor(download: Download) {
if (download.status == Download.DOWNLOADING) {
if (download.pages != null)
for (page in download.pages!!)
page.setStatusCallback {
callListeners(download)
}
downloadListeners.forEach { it.updateDownload(download) }
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
setPagesSubject(download.pages, null)
downloadListeners.forEach { it.updateDownload(download) }
}
else {
downloadListeners.forEach { it.updateDownload(download) }
}
}
private fun callListeners(download: Download) {
downloadListeners.forEach { it.updateDownload(download) }
}
fun getProgressObservable(): Observable<Download> {
return statusSubject.onBackpressureBuffer()
.startWith(getActiveDownloads())
@ -74,6 +106,7 @@ class DownloadQueue(
if (download.status == Download.DOWNLOADING) {
val pageStatusSubject = PublishSubject.create<Int>()
setPagesSubject(download.pages, pageStatusSubject)
downloadListeners.forEach { it.updateDownload(download) }
return@flatMap pageStatusSubject
.onBackpressureBuffer()
.filter { it == Page.READY }
@ -81,6 +114,7 @@ class DownloadQueue(
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
setPagesSubject(download.pages, null)
downloadListeners.forEach { it.updateDownload(download) }
}
Observable.just(download)
}
@ -95,4 +129,16 @@ class DownloadQueue(
}
}
}
fun addListener(listener: DownloadListener) {
downloadListeners.add(listener)
}
fun removeListener(listener: DownloadListener) {
downloadListeners.remove(listener)
}
interface DownloadListener {
fun updateDownload(download: Download)
}
}

View File

@ -18,12 +18,19 @@ open class Page(
set(value) {
field = value
statusSubject?.onNext(value)
statusCallback?.invoke(this)
}
@Transient @Volatile var progress: Int = 0
set(value) {
field = value
statusCallback?.invoke(this)
}
@Transient private var statusSubject: Subject<Int, Int>? = null
@Transient private var statusCallback: ((Page) -> Unit)? = null
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
progress = if (contentLength > 0) {
(100 * bytesRead / contentLength).toInt()
@ -36,6 +43,10 @@ open class Page(
this.statusSubject = subject
}
fun setStatusCallback(f: ((Page) -> Unit)?) {
statusCallback = f
}
companion object {
const val QUEUE = 0
const val LOAD_PAGE = 1

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.ui.download
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
@ -26,9 +27,16 @@ class DownloadButton @JvmOverloads constructor(context: Context, attrs: Attribut
R.drawable.filled_circle)?.mutate()
private val borderCircle = ContextCompat.getDrawable(context,
R.drawable.border_circle)?.mutate()
private var isAnimating = false
private var iconAnimation:ObjectAnimator? = null
fun setDownoadStatus(state: Int, progress: Int = 0) {
fun setDownloadStatus(state: Int, progress: Int = 0) {
if (state != Download.DOWNLOADING) {
iconAnimation?.cancel()
download_icon.alpha = 1f
isAnimating = false
}
when (state) {
Download.NOT_DOWNLOADED -> {
download_border.visible()
@ -55,6 +63,15 @@ class DownloadButton @JvmOverloads constructor(context: Context, attrs: Attribut
download_border.drawable.setTint(disabledColor)
download_progress.progressDrawable?.setTint(downloadedColor)
download_icon.drawable.setTint(disabledColor)
if (!isAnimating) {
iconAnimation = ObjectAnimator.ofFloat(download_icon, "alpha", 1f, 0f).apply {
duration = 1000
repeatCount = ObjectAnimator.INFINITE
repeatMode = ObjectAnimator.REVERSE
}
iconAnimation?.start()
isAnimating = true
}
}
Download.DOWNLOADED -> {
download_progress.gone()

View File

@ -86,6 +86,8 @@ open class LibraryController(
*/
protected var query = ""
var customQuery = ""
/**
* Currently selected mangas.
*/
@ -256,6 +258,24 @@ open class LibraryController(
}
}
override fun onChangeEnded(
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
super.onChangeEnded(changeHandler, changeType)
if (changeType.isEnter) {
if (customQuery.isNotEmpty()) {
query = customQuery
((activity as MainActivity).toolbar.menu.findItem(
R.id.action_search
)?.actionView as? SearchView)?.setQuery(
customQuery, true
)
}
customQuery = ""
}
}
override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity)
if (observeLater && ::presenter.isInitialized) {
@ -477,15 +497,19 @@ open class LibraryController(
menu.findItem(R.id.action_library_filter).icon.mutate()
setOnQueryTextChangeListener(searchView) {
query = it ?: ""
searchRelay.call(query)
true
onSearch(it)
}
searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() })
}
fun search(query:String) {
this.query = query
open fun onSearch(query: String?): Boolean {
this.query = query ?: ""
searchRelay.call(query)
return true
}
open fun search(query: String) {
this.customQuery = query
}
override fun handleRootBack(): Boolean {

View File

@ -5,15 +5,12 @@ import android.os.Bundle
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView
import androidx.core.math.MathUtils.clamp
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
@ -36,10 +33,8 @@ import eu.kanade.tachiyomi.ui.main.SwipeGestureInterface
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.android.synthetic.main.filter_bottom_sheet.*
import kotlinx.android.synthetic.main.library_grid_recycler.*
import kotlinx.android.synthetic.main.library_list_controller.*
@ -302,27 +297,21 @@ class LibraryListController(bundle: Bundle? = null) : LibraryController(bundle),
}
override fun reattachAdapter() {
if (libraryLayout == 0)recycler.spanCount = 1
if (libraryLayout == 0) recycler.spanCount = 1
else recycler.columnWidth = (90 + (preferences.gridSize().getOrDefault() * 30)).dpToPx
val position =
(recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
libraryLayout = preferences.libraryLayout().getOrDefault()
recycler.adapter = adapter
(recycler as? AutofitRecyclerView)?.spanCount = if (libraryLayout == 0) 1 else mangaPerRow
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(position, 0)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
setOnQueryTextChangeListener(searchView) {
query = it ?: ""
adapter.setFilter(it)
adapter.performFilter()
true
}
override fun onSearch(query: String?): Boolean {
this.query = query ?: ""
adapter.setFilter(query)
adapter.performFilter()
return true
}
override fun onDestroyActionMode(mode: ActionMode?) {

View File

@ -12,6 +12,7 @@ import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.webkit.WebView
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
@ -54,8 +55,10 @@ import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePadding
import eu.kanade.tachiyomi.util.view.visible
import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@ -198,18 +201,19 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION*/
updateRecentsIcon()
content.viewTreeObserver.addOnGlobalLayoutListener {
/*val heightDiff: Int = content.rootView.height - content.height
val heightDiff: Int = content.rootView.height - content.height
if (heightDiff > 200 &&
window.attributes.softInputMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
//keyboard is open, hide layout
navigationView.gone()
} else if (navigationView.visibility == View.GONE) {
} else if (navigationView.visibility == View.GONE
&& window.attributes.softInputMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
//keyboard is hidden, show layout
// use coroutine to delay so the bottom bar doesn't flash on top of the keyboard
launchUI {
navigationView.visible()
}
}*/
}
}
supportActionBar?.setDisplayShowCustomEnabled(true)

View File

@ -0,0 +1,157 @@
package eu.kanade.tachiyomi.ui.manga
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.RadioButton
import android.widget.RadioGroup
import com.f2prateek.rx.preferences.Preference
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
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.preference.getOrDefault
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.view.setBottomEdge
import eu.kanade.tachiyomi.util.view.setEdgeToEdge
import eu.kanade.tachiyomi.util.view.visibleIf
import kotlinx.android.synthetic.main.chapter_sort_bottom_sheet.*
import uy.kohesive.injekt.injectLazy
class ChaptersSortBottomSheet(private val controller: MangaChaptersController) : BottomSheetDialog
(controller.activity!!, R.style.BottomSheetDialogTheme) {
val activity = controller.activity!!
/**
* Preferences helper.
*/
private val preferences by injectLazy<PreferencesHelper>()
private var sheetBehavior: BottomSheetBehavior<*>
init {
// Use activity theme for this layout
val view = activity.layoutInflater.inflate(R.layout.chapter_sort_bottom_sheet, null)
setContentView(view)
sheetBehavior = BottomSheetBehavior.from(view.parent as ViewGroup)
setEdgeToEdge(activity, bottom_sheet, view, false)
val height = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.window.decorView.rootWindowInsets.systemWindowInsetBottom
} else 0
sheetBehavior.peekHeight = 220.dpToPx + height
sheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, progress: Float) { }
override fun onStateChanged(p0: View, state: Int) {
if (state == BottomSheetBehavior.STATE_EXPANDED) {
sheetBehavior.skipCollapsed = true
}
}
})
}
override fun onStart() {
super.onStart()
sheetBehavior.skipCollapsed = true
sheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
/**
* Called when the sheet is created. It initializes the listeners and values of the preferences.
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initGeneralPreferences()
setBottomEdge(show_bookmark, activity)
close_button.setOnClickListener {
dismiss()
true
}
settings_scroll_view.viewTreeObserver.addOnGlobalLayoutListener {
val isScrollable =
settings_scroll_view!!.height < bottom_sheet.height +
settings_scroll_view.paddingTop + settings_scroll_view.paddingBottom
close_button.visibleIf(isScrollable)
}
}
private fun initGeneralPreferences() {
show_read.isChecked = controller.presenter.onlyRead()
show_unread.isChecked = controller.presenter.onlyUnread()
show_download.isChecked = controller.presenter.onlyDownloaded()
show_bookmark.isChecked = controller.presenter.onlyBookmarked()
show_all.isChecked = !(show_read.isChecked || show_unread.isChecked ||
show_download.isChecked || show_bookmark.isChecked)
if (controller.presenter.onlyRead())
//Disable unread filter option if read filter is enabled.
show_unread.isEnabled = false
if (controller.presenter.onlyUnread())
//Disable read filter option if unread filter is enabled.
show_read.isEnabled = false
sort_group.check(if (controller.presenter.manga.sortDescending()) R.id.sort_newest else
R.id.sort_oldest)
show_titles.isChecked = controller.presenter.manga.displayMode == Manga.DISPLAY_NAME
sort_by_source.isChecked = controller.presenter.manga.sorting == Manga.SORTING_SOURCE
sort_group.setOnCheckedChangeListener { _, checkedId ->
controller.presenter.setSortOrder(checkedId == R.id.sort_oldest)
dismiss()
}
/*sort_group.bindToPreference(preferences.libraryLayout()) {
controller.reattachAdapter()
if (sheetBehavior.state == BottomSheetBehavior.STATE_COLLAPSED)
dismiss()
}
uniform_grid.bindToPreference(preferences.uniformGrid()) {
controller.reattachAdapter()
}
grid_size_toggle_group.bindToPreference(preferences.gridSize()) {
controller.reattachAdapter()
}
download_badge.bindToPreference(preferences.downloadBadge()) {
controller.presenter.requestDownloadBadgesUpdate()
}
unread_badge_group.bindToPreference(preferences.unreadBadgeType()) {
controller.presenter.requestUnreadBadgesUpdate()
}*/
}
/**
* Binds a checkbox or switch view with a boolean preference.
*/
private fun CompoundButton.bindToPreference(pref: Preference<Boolean>, block: () -> Unit) {
isChecked = pref.getOrDefault()
setOnCheckedChangeListener { _, isChecked ->
pref.set(isChecked)
block()
}
}
/**
* Binds a radio group with a int preference.
*/
private fun RadioGroup.bindToPreference(pref: Preference<Int>, block: () -> Unit) {
(getChildAt(pref.getOrDefault()) as RadioButton).isChecked = true
setOnCheckedChangeListener { _, checkedId ->
val index = indexOfChild(findViewById(checkedId))
pref.set(index)
block()
}
}
}

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.ui.manga
import android.animation.ValueAnimator
import android.app.Activity
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
@ -8,6 +9,7 @@ import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
@ -15,6 +17,7 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.ColorUtils
import androidx.palette.graphics.Palette
import androidx.recyclerview.widget.DividerItemDecoration
@ -31,6 +34,7 @@ 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.Category
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl
@ -41,10 +45,13 @@ 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.library.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.main.SearchActivity
import eu.kanade.tachiyomi.ui.manga.MangaController.Companion.FROM_CATALOGUE_EXTRA
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterMatHolder
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
@ -56,14 +63,18 @@ import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import kotlinx.android.synthetic.main.big_manga_controller.*
import kotlinx.android.synthetic.main.big_manga_controller.swipe_refresh
import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.manga_info_controller.*
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MangaChaptersController : BaseController,
ActionMode.Callback,
FlexibleAdapter.OnItemClickListener,
ChaptersAdapter.MangaHeaderInterface {
FlexibleAdapter.OnItemLongClickListener,
ChaptersAdapter.MangaHeaderInterface,
ChangeMangaCategoriesDialog.Listener {
constructor(manga: Manga?,
fromCatalogue: Boolean = false,
@ -121,7 +132,6 @@ class MangaChaptersController : BaseController,
// Init RecyclerView and adapter
adapter = ChaptersAdapter(this, view.context)
//setReadingDrawable()
recycler.adapter = adapter
recycler.layoutManager = LinearLayoutManager(view.context)
@ -133,9 +143,6 @@ class MangaChaptersController : BaseController,
)
recycler.setHasFixedSize(true)
adapter?.fastScroller = fast_scroller
/*activity?.controller_container?.updateLayoutParams<ConstraintLayout.LayoutParams> {
topMargin = 0
}*/
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
val array = view.context.obtainStyledAttributes(attrsArray)
val appbarHeight = array.getDimensionPixelSize(0, 0)
@ -144,6 +151,7 @@ class MangaChaptersController : BaseController,
recycler.doOnApplyWindowInsets { v, insets, _ ->
headerHeight = appbarHeight + insets.systemWindowInsetTop + offset
swipe_refresh.setProgressViewOffset(false, (-40).dpToPx, headerHeight)
(recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)
?.setTopHeight(headerHeight)
fast_scroller?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
@ -156,7 +164,7 @@ class MangaChaptersController : BaseController,
presenter.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
recycler.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
recycler.setOnScrollChangeListener { _, _, _, _, _ ->
val atTop = !recycler.canScrollVertically(-1)
if ((!atTop && !toolbarIsColored) || (atTop && toolbarIsColored)) {
toolbarIsColored = !atTop
@ -173,7 +181,6 @@ class MangaChaptersController : BaseController,
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)
@ -184,8 +191,6 @@ class MangaChaptersController : BaseController,
}
}
}
// recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
// recycler.requestApplyInsetsWhenAttached()
GlideApp.with(view.context).load(manga)
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga!!.id!!).toString()))
@ -222,7 +227,11 @@ class MangaChaptersController : BaseController,
swipe_refresh.setOnRefreshListener {
presenter.refreshAll()
}
//adapter?.fastScroller = fast_scroller
}
override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity)
presenter.fetchChapters()
}
fun showError(message: String) {
@ -230,43 +239,21 @@ class MangaChaptersController : BaseController,
view?.snack(message)
}
fun updateChapterDownload(download: Download) {
getHolder(download.chapter)?.notifyStatus(download.status, presenter.isLockedFromSearch,
download.progress)
}
private fun getHolder(chapter: Chapter): ChapterMatHolder? {
return recycler?.findViewHolderForItemId(chapter.id!!) as? ChapterMatHolder
}
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)
activity?.window?.statusBarColor = 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()
@ -278,22 +265,6 @@ class MangaChaptersController : BaseController,
activity?.window?.statusBarColor = 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()
}
}
@ -305,9 +276,12 @@ class MangaChaptersController : BaseController,
if (presenter.chapters.isEmpty()) {
adapter?.updateDataSet(listOf(ChapterItem(Chapter.createH(), presenter.manga)))
}
else
adapter?.updateDataSet(listOf(ChapterItem(Chapter.createH(), presenter.manga))
+ presenter.chapters)
else {
swipe_refresh.isRefreshing = false
adapter?.updateDataSet(
listOf(ChapterItem(Chapter.createH(), presenter.manga)) + presenter.chapters
)
}
}
@ -333,7 +307,62 @@ class MangaChaptersController : BaseController,
//}
}
fun openChapter(chapter: Chapter, hasAnimation: Boolean = false) {
override fun onItemLongClick(position: Int) {
val adapter = adapter ?: return
val item = adapter.getItem(position) ?: return
val itemView = getHolder(item)?.itemView ?: return
val popup = PopupMenu(itemView.context, itemView, Gravity.END)
// Inflate our menu resource into the PopupMenu's Menu
popup.menuInflater.inflate(R.menu.chapters_mat_single, popup.menu)
// Hide bookmark if bookmark
popup.menu.findItem(R.id.action_bookmark).isVisible = !item.bookmark
popup.menu.findItem(R.id.action_remove_bookmark).isVisible = item.bookmark
// Hide mark as unread when the chapter is unread
if (!item.read && item.last_page_read == 0) {
popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false
}
// Hide mark as read when the chapter is read
if (item.read) {
popup.menu.findItem(R.id.action_mark_as_read).isVisible = false
}
// Set a listener so we are notified if a menu item is clicked
popup.setOnMenuItemClickListener { menuItem ->
val chapters = listOf(item)
when (menuItem.itemId) {
R.id.action_bookmark -> bookmarkChapters(chapters, true)
R.id.action_remove_bookmark -> bookmarkChapters(chapters, false)
R.id.action_mark_as_read -> markAsRead(chapters)
R.id.action_mark_as_unread -> markAsUnread(chapters)
}
true
}
// Finally show the PopupMenu
popup.show()
}
private fun bookmarkChapters(chapters: List<ChapterItem>, bookmarked: Boolean) {
//destroyActionModeIfNeeded()
presenter.bookmarkChapters(chapters, bookmarked)
}
private fun markAsRead(chapters: List<ChapterItem>) {
presenter.markChaptersRead(chapters, true)
if (presenter.preferences.removeAfterMarkedAsRead()) {
presenter.deleteChapters(chapters)
}
}
private fun markAsUnread(chapters: List<ChapterItem>) {
presenter.markChaptersRead(chapters, false)
}
private fun openChapter(chapter: Chapter, hasAnimation: Boolean = false) {
val activity = activity ?: return
val intent = ReaderActivity.newIntent(activity, manga!!, chapter)
if (hasAnimation) {
@ -342,13 +371,10 @@ class MangaChaptersController : BaseController,
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 onDestroyView(view: View) {
snack?.dismiss()
presenter.onDestroy()
super.onDestroyView(view)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -387,8 +413,6 @@ class MangaChaptersController : BaseController,
override fun topCoverHeight(): Int = headerHeight
override fun nextChapter(): Chapter? = presenter.getNextUnreadChapter()
override fun newestChapterDate(): Long? = presenter.getNewestChapterTime()
override fun lastChapter(): Float? = presenter.getLatestChapter()
override fun mangaSource(): Source = presenter.source
override fun readNextChapter() {
@ -421,4 +445,100 @@ class MangaChaptersController : BaseController,
}
else presenter.downloadChapters(listOf(chapter))
}
override fun tagClicked(text: String) {
val firstController = router.backstack.first()?.controller()
if (firstController is LibraryController && router.backstack.size == 2) {
router.handleBack()
firstController.search(text)
}
}
override fun showChapterFilter() {
ChaptersSortBottomSheet(this).show()
}
override fun chapterCount():Int = presenter.chapters.size
override fun favoriteManga(longPress: Boolean) {
val manga = presenter.manga
if (longPress) {
if (!manga.favorite) {
presenter.toggleFavorite()
showAddedSnack()
}
val categories = presenter.getCategories()
if (categories.isEmpty()) {
// no categories exist, display a message about adding categories
snack = view?.snack(R.string.action_add_category)
} else {
val ids = presenter.getMangaCategoryIds(manga)
val preselected = ids.mapNotNull { id ->
categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
}.toTypedArray()
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
.showDialog(router)
}
}
else {
if (presenter.toggleFavorite()) {
val categories = presenter.getCategories()
val defaultCategoryId = presenter.preferences.defaultCategory()
val defaultCategory = categories.find { it.id == defaultCategoryId }
when {
defaultCategory != null -> presenter.moveMangaToCategory(manga, defaultCategory)
defaultCategoryId == 0 || categories.isEmpty() -> // 'Default' or no category
presenter.moveMangaToCategory(manga, null)
else -> {
val ids = presenter.getMangaCategoryIds(manga)
val preselected = ids.mapNotNull { id ->
categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
}.toTypedArray()
ChangeMangaCategoriesDialog(
this,
listOf(manga),
categories,
preselected
).showDialog(router)
}
}
showAddedSnack()
} else {
showRemovedSnack()
}
}
}
private fun showAddedSnack() {
val view = view ?: return
snack?.dismiss()
snack = view.snack(view.context.getString(R.string.manga_added_library))
}
private fun showRemovedSnack() {
val view = view ?: return
snack?.dismiss()
snack = view.snack(
view.context.getString(R.string.manga_removed_library),
Snackbar.LENGTH_INDEFINITE
) {
setAction(R.string.action_undo) {
presenter.setFavorite(true)
}
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
if (!presenter.manga.favorite) presenter.confirmDeletion()
}
})
}
(activity as? MainActivity)?.setUndoSnackBar(snack, fab_favorite)
}
override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {
val manga = mangas.firstOrNull() ?: return
presenter.moveMangaToCategories(manga, categories)
}
}

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.manga
import android.content.res.ColorStateList
import android.graphics.Color
import android.text.format.DateUtils
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
@ -17,10 +16,11 @@ import eu.kanade.tachiyomi.source.model.SManga
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.gone
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.util.view.visibleIf
import kotlinx.android.synthetic.main.manga_header_item.*
import java.util.Date
import java.util.Locale
class MangaHeaderHolder(
@ -33,17 +33,60 @@ class MangaHeaderHolder(
top_view.updateLayoutParams<ConstraintLayout.LayoutParams> {
topMargin = adapter.coverListener?.topCoverHeight() ?: 0
}
more_button.setOnClickListener { expandDesc() }
manga_summary.setOnClickListener { expandDesc() }
less_button.setOnClickListener {
manga_summary.maxLines = 3
manga_genres_tags.gone()
less_button.gone()
more_button_group.visible()
}
manga_genres_tags.setOnTagClickListener {
adapter.coverListener?.tagClicked(it)
}
filter_button.setOnClickListener {
adapter.coverListener?.showChapterFilter()
}
favorite_button.setOnClickListener {
adapter.coverListener?.favoriteManga(false)
}
favorite_button.setOnLongClickListener {
adapter.coverListener?.favoriteManga(true)
true
}
}
private fun expandDesc() {
if (more_button.visibility == View.VISIBLE) {
manga_summary.maxLines = Integer.MAX_VALUE
manga_genres_tags.visible()
less_button.visible()
more_button_group.gone()
}
}
override fun bind(item: ChapterItem, manga: Manga) {
manga_full_title.text = manga.currentTitle()
if (manga.currentGenres().isNullOrBlank().not())
manga_genres_tags.setTags(manga.currentGenres()?.split(", ")?.map(String::trim))
else
manga_genres_tags.setTags(emptyList())
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.text = manga.currentDesc() ?: itemView.context.getString(R.string
.no_description)
manga_summary.post {
if (manga_summary.lineCount < 3 && manga.currentGenres().isNullOrBlank()) {
more_button_group.gone()
}
}
manga_summary_label.text = itemView.context.getString(R.string.about_this,
itemView.context.getString(
when {
@ -77,6 +120,10 @@ class MangaHeaderHolder(
context.getResourceColor(R.attr.colorAccent), 75))
strokeColor = ColorStateList.valueOf(Color.TRANSPARENT)
}
else strokeColor = ColorStateList.valueOf(
ColorUtils.setAlphaComponent(
itemView.context.getResourceColor(R.attr
.colorOnSurface), 31))
}
true_backdrop.setBackgroundColor(adapter.coverListener?.coverColor() ?:
itemView.context.getResourceColor(android.R.attr.colorBackground))
@ -98,31 +145,20 @@ class MangaHeaderHolder(
}
}
val count = adapter.coverListener?.chapterCount() ?: 0
chapters_title.text = itemView.resources.getQuantityString(R.plurals.chapters, count, count)
top_view.updateLayoutParams<ConstraintLayout.LayoutParams> {
topMargin = adapter.coverListener?.topCoverHeight() ?: 0
}
val lastUpdated = adapter.coverListener?.newestChapterDate()
if (lastUpdated != null) {
manga_last_update.text = itemView.context.getString(
R.string.last_updated, DateUtils.getRelativeTimeSpanString(
lastUpdated, Date().time, DateUtils.HOUR_IN_MILLIS
).toString()
)
}
else {
manga_last_update.text = itemView.context.getString(R.string.last_update_unknown)
}
val sourceAndStatus = mutableListOf<String>()
sourceAndStatus.add(itemView.context.getString( when (manga.status) {
manga_status.text = (itemView.context.getString( when (manga.status) {
SManga.ONGOING -> R.string.ongoing
SManga.COMPLETED -> R.string.completed
SManga.LICENSED -> R.string.licensed
else -> R.string.unknown_status
}))
val sourceName = adapter.coverListener?.mangaSource()?.toString()
if (sourceName != null) sourceAndStatus.add(sourceName)
manga_status_source.text = sourceAndStatus.joinToString("")
manga_source.text = adapter.coverListener?.mangaSource()?.toString()
GlideApp.with(view.context).load(manga)
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)

View File

@ -1,46 +1,65 @@
package eu.kanade.tachiyomi.ui.manga
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Date
import kotlin.coroutines.CoroutineContext
class MangaPresenter(private val controller: MangaChaptersController,
val manga: Manga,
val source: Source,
val preferences: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(),
private val db: DatabaseHelper = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get()) {
private val downloadManager: DownloadManager = Injekt.get()):
CoroutineScope,
DownloadQueue.DownloadListener {
override var coroutineContext:CoroutineContext = Job() + Dispatchers.Default
var isLockedFromSearch = false
var hasRequested = false
var chapters:List<ChapterItem> = emptyList()
private set
fun onCreate() {
isLockedFromSearch = SecureActivityDelegate.shouldBeLocked()
downloadManager.addListener(this)
if (!manga.initialized)
fetchMangaFromSource()
updateChapters()
controller.updateChapters(this.chapters)
}
fun onDestroy() {
downloadManager.removeListener(this)
}
fun fetchMangaFromSource() {
GlobalScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
@ -66,6 +85,24 @@ class MangaPresenter(private val controller: MangaChaptersController,
}
}
fun fetchChapters() {
launch {
getChapters()
withContext(Dispatchers.Main) { controller.updateChapters(chapters) }
}
}
private suspend fun getChapters() {
val chapters = withContext(Dispatchers.IO) {
db.getChapters(manga).executeAsBlocking().map { it.toModel() }
}
// Store the last emission
this.chapters = applyChapterFilters(chapters)
// Find downloaded chapters
setDownloadedChapters(chapters)
}
private fun updateChapters(fetchedChapters: List<Chapter>? = null) {
val chapters = (fetchedChapters ?:
db.getChapters(manga).executeAsBlocking()).map { it.toModel() }
@ -75,17 +112,6 @@ class MangaPresenter(private val controller: MangaChaptersController,
// Find downloaded chapters
setDownloadedChapters(chapters)
/*
// 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)
)*/
}
/**
@ -100,6 +126,14 @@ class MangaPresenter(private val controller: MangaChaptersController,
}
}
}
override fun updateDownload(download: Download) {
chapters.find { it.id == download.chapter.id }?.download = download
launch(Dispatchers.Main) {
controller.updateChapterDownload(download)
}
}
/**
* Converts a chapter from the database to an extended model, allowing to store new fields.
*/
@ -237,9 +271,11 @@ class MangaPresenter(private val controller: MangaChaptersController,
* @param chapters the list of chapters to delete.
*/
fun deleteChapters(chapters: List<ChapterItem>) {
deleteChaptersInternal(chapters)
deleteChaptersInternal(chapters)
setDownloadedChapters(chapters)
chapters.forEach { chapter ->
this.chapters.find { it.id == chapter.id }?.download?.status = Download.NOT_DOWNLOADED
}
controller.updateChapters(this.chapters)
// if (onlyDownloaded()) refreshChapters() }
@ -258,8 +294,46 @@ class MangaPresenter(private val controller: MangaChaptersController,
}
fun refreshAll() {
fetchMangaFromSource()
fetchChaptersFromSource()
launch {
var mangaError: java.lang.Exception? = null
var chapterError: java.lang.Exception? = null
val chapters = async(Dispatchers.IO) {
try {
source.fetchChapterList(manga).toBlocking().single()
} catch (e: Exception) {
chapterError = e
emptyList<SChapter>()
} ?: emptyList()
}
val thumbnailUrl = manga.thumbnail_url
val nManga = async(Dispatchers.IO) {
try {
source.fetchMangaDetails(manga).toBlocking().single()
} catch (e: java.lang.Exception) {
mangaError = e
null
}
}
val networkManga = nManga.await()
if (networkManga != null) {
manga.copyFrom(networkManga)
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
if (thumbnailUrl != networkManga.thumbnail_url)
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
}
val finChapters = chapters.await()
if (finChapters.isNotEmpty()) {
syncChaptersWithSource(db, finChapters, manga, source)
withContext(Dispatchers.IO) { updateChapters() }
}
if (chapterError == null)
withContext(Dispatchers.Main) { controller.updateChapters(this@MangaPresenter.chapters) }
if (mangaError != null)
withContext(Dispatchers.Main) { controller.showError(trimException(mangaError!!)) }
}
}
/**
@ -268,12 +342,12 @@ class MangaPresenter(private val controller: MangaChaptersController,
fun fetchChaptersFromSource() {
hasRequested = true
GlobalScope.launch(Dispatchers.IO) {
launch(Dispatchers.IO) {
val chapters = try {
source.fetchChapterList(manga).toBlocking().single()
}
catch(e: Exception) {
controller.showError(trimException(e))
withContext(Dispatchers.Main) { controller.showError(trimException(e)) }
return@launch
} ?: listOf()
try {
@ -291,4 +365,108 @@ class MangaPresenter(private val controller: MangaChaptersController,
private fun trimException(e: java.lang.Exception): String {
return e.message?.split(": ")?.drop(1)?.joinToString(": ") ?: "Error"
}
/**
* Bookmarks the given list of chapters.
* @param selectedChapters the list of chapters to bookmark.
*/
fun bookmarkChapters(selectedChapters: List<ChapterItem>, bookmarked: Boolean) {
launch(Dispatchers.IO) {
selectedChapters.forEach {
it.bookmark = bookmarked
}
db.updateChaptersProgress(selectedChapters).executeAsBlocking()
withContext(Dispatchers.Main) { controller.updateChapters(chapters) }
}
}
/**
* Mark the selected chapter list as read/unread.
* @param selectedChapters the list of selected chapters.
* @param read whether to mark chapters as read or unread.
*/
fun markChaptersRead(selectedChapters: List<ChapterItem>, read: Boolean) {
launch(Dispatchers.IO) {
selectedChapters.forEach {
it.read = read
if (!read) {
it.last_page_read = 0
it.pages_left = 0
}
}
db.updateChaptersProgress(selectedChapters).executeAsBlocking()
withContext(Dispatchers.Main) { controller.updateChapters(chapters) }
}
}
/**
* Reverses the sorting and requests an UI update.
*/
fun setSortOrder(desend: Boolean) {
manga.setChapterOrder(if (desend) Manga.SORT_ASC else Manga.SORT_DESC)
db.updateFlags(manga).executeAsBlocking()
updateChapters()
controller.updateChapters(chapters)
}
fun toggleFavorite(): Boolean {
manga.favorite = !manga.favorite
db.insertManga(manga).executeAsBlocking()
controller.updateHeader()
return manga.favorite
}
/**
* Get user categories.
*
* @return List of categories, not including the default category
*/
fun getCategories(): List<Category> {
return db.getCategories().executeAsBlocking()
}
/**
* Move the given manga to the category.
*
* @param manga the manga to move.
* @param category the selected category, or null for default category.
*/
fun moveMangaToCategory(manga: Manga, category: Category?) {
moveMangaToCategories(manga, listOfNotNull(category))
}
/**
* Move the given manga to categories.
*
* @param manga the manga to move.
* @param categories the selected categories.
*/
fun moveMangaToCategories(manga: Manga, categories: List<Category>) {
val mc = categories.filter { it.id != 0 }.map { MangaCategory.create(manga, it) }
db.setMangaCategories(mc, listOf(manga))
}
/**
* Gets the category id's the manga is in, if the manga is not in a category, returns the default id.
*
* @param manga the manga to get categories from.
* @return Array of category ids the manga is in, if none returns default id
*/
fun getMangaCategoryIds(manga: Manga): Array<Int> {
val categories = db.getCategoriesForManga(manga).executeAsBlocking()
return categories.mapNotNull { it.id }.toTypedArray()
}
fun confirmDeletion() {
coverCache.deleteFromCache(manga.thumbnail_url)
db.resetMangaInfo(manga).executeAsBlocking()
downloadManager.deleteManga(manga, source)
}
fun setFavorite(favorite: Boolean) {
if (manga.favorite == favorite) {
return
}
toggleFavorite()
}
}

View File

@ -29,7 +29,9 @@ class ChapterMatHolder(
private fun downloadOrRemoveMenu() {
val chapter = adapter.getItem(adapterPosition) ?: return
if (chapter.status != Download.NOT_DOWNLOADED) {
if (chapter.status == Download.NOT_DOWNLOADED || chapter.status == Download.ERROR) {
adapter.coverListener?.downloadChapter(adapterPosition)
} else {
download_button.post {
// Create a PopupMenu, giving it the clicked view for an anchor
val popup = PopupMenu(download_button.context, download_button)
@ -38,8 +40,7 @@ class ChapterMatHolder(
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(
if (chapter.status != Download.DOWNLOADED) popup.menu.findItem(R.id.action_delete).title = download_button.context.getString(
R.string.action_cancel
)
@ -53,9 +54,6 @@ class ChapterMatHolder(
popup.show()
}
}
else {
adapter.coverListener?.downloadChapter(adapterPosition)
}
}
override fun bind(item: ChapterItem, manga: Manga) {
@ -113,6 +111,6 @@ class ChapterMatHolder(
return
}
visible()
setDownoadStatus(status, progress)
setDownloadStatus(status, progress)
}
}

View File

@ -64,8 +64,10 @@ class ChaptersAdapter(
fun readNextChapter()
fun downloadChapter(position: Int)
fun topCoverHeight(): Int
fun newestChapterDate(): Long?
fun lastChapter(): Float?
fun chapterCount(): Int
fun tagClicked(text: String)
fun mangaSource(): Source
fun showChapterFilter()
fun favoriteManga(longPress: Boolean)
}
}

View File

@ -386,9 +386,9 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
fun setLastUpdateDate(date: Date) {
if (date.time != 0L) {
manga_last_update?.text = dateFormat.format(date)
manga_status?.text = dateFormat.format(date)
} else {
manga_last_update?.text = resources?.getString(R.string.unknown)
manga_status?.text = resources?.getString(R.string.unknown)
}
}

View File

@ -150,7 +150,7 @@
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/manga_last_update"
android:id="@+id/manga_status"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -160,7 +160,7 @@
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="@+id/manga_last_update"
android:id="@+id/manga_status"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -168,8 +168,8 @@
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_last_update"
app:layout_constraintStart_toEndOf="@+id/manga_last_update"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_status"
app:layout_constraintStart_toEndOf="@+id/manga_status"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
@ -179,7 +179,7 @@
android:layout_height="wrap_content"
android:text="@string/manga_info_status_label"
android:textIsSelectable="false"
app:layout_constraintTop_toBottomOf="@+id/manga_last_update"
app:layout_constraintTop_toBottomOf="@+id/manga_status"
app:layout_constraintStart_toStartOf="parent"/>
<TextView

View File

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/diplay_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.core.widget.NestedScrollView
android:id="@+id/settings_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/bottom_sheet"
style="@style/BottomSheetDialogTheme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_bottom_sheet_dialog_fragment"
android:orientation="vertical"
android:paddingTop="12dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.MaterialComponents.Headline6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="12dp"
android:text="@string/action_sort" />
<RadioGroup
android:id="@+id/sort_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/sort_newest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Newest first" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/sort_oldest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="Oldest first" />
</RadioGroup>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/sort_by_source"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="Sort by source's order" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/show_titles"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="Hide chapter titles" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.MaterialComponents.Headline6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:paddingStart="16dp"
android:paddingEnd="12dp"
android:text="@string/action_filter" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/show_all"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="Show All" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/show_read"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="Show read chapters" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/show_unread"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="Show unread chapters" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/show_download"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="Show downloaded chapters" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/show_bookmark"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="Show bookmarked chapters" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<ImageView
android:id="@+id/close_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="end"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:background="@drawable/round_ripple"
android:clickable="true"
android:contentDescription="@string/action_close"
android:focusable="true"
android:src="@drawable/ic_close_white_24dp"
android:tint="@color/gray_button" />
</FrameLayout>

View File

@ -28,6 +28,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginBottom="12dp"
android:ellipsize="end"
android:maxLines="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/download_button"

View File

@ -25,11 +25,11 @@
android:id="@+id/true_backdrop"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintHeight_min="200dp"
app:layout_constraintBottom_toBottomOf="@id/bottom_line"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_min="200dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="@id/bottom_line"
app:layout_constraintVertical_bias="0.0"
tools:background="@color/material_red_400" />
@ -39,10 +39,10 @@
android:layout_height="0dp"
android:alpha="0.1"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="@+id/true_backdrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/true_backdrop"
app:layout_constraintBottom_toBottomOf="@+id/true_backdrop"
tools:src="@mipmap/ic_launcher" />
<View
@ -76,9 +76,9 @@
android:id="@+id/manga_layout"
android:layout_width="100dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@id/guideline"
app:layout_constraintDimensionRatio="h,7:10"
app:layout_constraintStart_toStartOf="parent">
@ -89,13 +89,13 @@
android:id="@+id/cover_card"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/true_backdrop"
app:layout_constraintEnd_toEndOf="@id/manga_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/top_view"
app:layout_constraintTop_toTopOf="@id/true_backdrop"
app:layout_constraintVertical_bias="1.0">
<ImageView
@ -112,7 +112,7 @@
<TextView
android:id="@+id/manga_full_title"
style="@style/TextAppearance.MaterialComponents.Headline5"
style="@style/TextAppearance.MaterialComponents.Headline6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
@ -129,9 +129,10 @@
<TextView
android:id="@+id/manga_author"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:textAppearance="@style/TextAppearance.Regular.Body1.SemiBold"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/manga_info_author_label"
@ -141,19 +142,19 @@
app:layout_constraintTop_toBottomOf="@+id/manga_full_title" />
<TextView
android:id="@+id/manga_last_update"
android:id="@+id/manga_status"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_marginTop="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/manga_info_latest_data_label"
tools:text="Last updated 3 days ago"
android:textIsSelectable="false"
app:layout_constraintStart_toStartOf="@id/manga_full_title"
app:layout_constraintTop_toBottomOf="@+id/manga_author" />
app:layout_constraintTop_toBottomOf="@+id/manga_author"
tools:text="Completed" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/manga_status_source"
android:id="@+id/manga_source"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -162,8 +163,8 @@
android:textIsSelectable="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/manga_full_title"
app:layout_constraintTop_toBottomOf="@id/manga_last_update"
tools:text="Completed • Mangadex (EN)" />
app:layout_constraintTop_toBottomOf="@id/manga_status"
tools:text="Mangadex (EN)" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/bottom_line"
@ -172,7 +173,7 @@
android:layout_margin="6dp"
android:orientation="horizontal"
app:barrierDirection="bottom"
app:constraint_referenced_ids="manga_status_source,manga_layout,cover_card" />
app:constraint_referenced_ids="manga_source,manga_layout,cover_card" />
<LinearLayout
@ -181,26 +182,27 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="14dp"
android:orientation="horizontal"
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"
style="@style/Theme.Widget.Button.RounededOutline"
android:text="@string/add_to_library"
app:icon="@drawable/ic_add_to_library_24dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/track_button"
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"
android:id="@+id/edit_button"
style="@style/Theme.Widget.CustomImageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -209,8 +211,7 @@
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" />
app:layout_constraintBottom_toBottomOf="@id/chapters_title" />
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
@ -235,6 +236,8 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:clickable="true"
android:focusable="true"
android:layout_marginEnd="16dp"
android:maxLines="3"
android:textIsSelectable="false"
@ -245,7 +248,8 @@
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:id="@+id/more_bg_gradient"
android:layout_width="50dp"
android:layout_height="20dp"
android:layout_marginEnd="30dp"
android:background="@drawable/full_gradient"
@ -254,26 +258,39 @@
app:layout_constraintEnd_toEndOf="@id/more_button" />
<View
android:id="@+id/more_bg_solid"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginStart="20dp"
android:layout_marginStart="45dp"
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" />
<View
android:id="@+id/more_guide"
android:layout_width="1dp"
android:layout_height="15sp"
app:layout_constraintEnd_toEndOf="@id/manga_summary"
app:layout_constraintTop_toBottomOf="@id/manga_summary"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/more_button"
style="@style/Widget.MaterialComponents.Button.TextButton"
style="@style/Theme.Widget.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="19.5sp"
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" />
android:text="@string/more"
android:layout_marginEnd="8dp"
android:textAlignment="textEnd"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/more_guide"
app:rippleColor="@null" />
<androidx.constraintlayout.widget.Group
android:id="@+id/more_button_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="more_button,more_bg_gradient,more_bg_solid" />
<me.gujun.android.taggroup.TagGroup
android:id="@+id/manga_genres_tags"
@ -289,58 +306,74 @@
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_constraintBottom_toTopOf="@id/less_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/less_button"
style="@style/Theme.Widget.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/less"
android:visibility="gone"
android:layout_marginEnd="8dp"
android:textAlignment="textEnd"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/start_reading_button"
app:layout_constraintTop_toBottomOf="@id/manga_genres_tags"
app:rippleColor="@null" />
<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_marginTop="12dp"
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"
app:layout_constraintTop_toBottomOf="@+id/less_button"
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_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:layout_marginBottom="12dp"
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" />
app:layout_constraintTop_toBottomOf="@id/start_reading_button"
app:layout_constraintEnd_toStartOf="@id/filters_text"/>
<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:layout_marginEnd="16dp"
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" />
app:layout_constraintTop_toTopOf="@id/chapters_title"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/filters_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:padding="5dp"
tools:text="Read"
app:layout_constraintTop_toTopOf="@id/filter_button"
app:layout_constraintBottom_toBottomOf="@id/filter_button"
app:layout_constraintEnd_toStartOf="@id/filter_button" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -172,7 +172,7 @@
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/manga_last_update"
android:id="@+id/manga_status"
style="@style/TextAppearance.Medium.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -182,7 +182,7 @@
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="@+id/manga_last_update"
android:id="@+id/manga_status"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -190,8 +190,8 @@
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_last_update"
app:layout_constraintStart_toEndOf="@+id/manga_last_update"
app:layout_constraintBaseline_toBaselineOf="@+id/manga_status"
app:layout_constraintStart_toEndOf="@+id/manga_status"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
@ -201,7 +201,7 @@
android:layout_height="wrap_content"
android:text="@string/manga_info_status_label"
android:textIsSelectable="false"
app:layout_constraintTop_toBottomOf="@+id/manga_last_update"
app:layout_constraintTop_toBottomOf="@+id/manga_status"
app:layout_constraintStart_toStartOf="parent"/>
<TextView

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/action_bookmark"
android:title="@string/action_bookmark"
android:visible="true" />
<item android:id="@+id/action_remove_bookmark"
android:title="@string/action_remove_bookmark"
android:visible="true" />
<item android:id="@+id/action_mark_as_read"
android:title="@string/action_mark_as_read" />
<item android:id="@+id/action_mark_as_unread"
android:title="@string/action_mark_as_unread" />
<item android:id="@+id/action_mark_multiple"
android:title="@string/action_mark_multiple"/>
</menu>

View File

@ -70,6 +70,7 @@
<string name="action_mark_as_read">Mark as read</string>
<string name="action_mark_as_unread">Mark as unread</string>
<string name="action_mark_previous_as_read">Mark previous as read</string>
<string name="action_mark_multiple">Mark multiple</string>
<string name="action_download">Download</string>
<string name="action_bookmark">Bookmark</string>
<string name="action_remove_bookmark">Remove bookmark</string>
@ -224,7 +225,7 @@
<string name="lock_always">Always</string>
<string name="lock_never">Never</string>
<plurals name="lock_after_mins">
<item quantity="one">After %1$s minutes</item>
<item quantity="one">After %1$s minute</item>
<item quantity="other">After %1$s minutes</item>
</plurals>
<string name="search_hint">Search title, tags, source</string>
@ -500,6 +501,11 @@
<string name="copied_to_clipboard">%1$s copied to clipboard</string>
<string name="source_not_installed">Source not installed: %1$s</string>
<string name="about_this">About this %1$s</string>
<plurals name="chapters">
<item quantity="one">%1$d chapter</item>
<item quantity="other">%1$d chapters</item>
</plurals>
<string name="no_description">No description</string>
<!-- Manga chapters fragment -->
<string name="start_reading">Start reading</string>
@ -697,5 +703,7 @@
<string name="reset_tags">Reset Tags</string>
<string name="display_as">Display as</string>
<string name="action_auto">Auto</string>
<string name="more">More</string>
<string name="less">Less</string>
</resources>

View File

@ -252,6 +252,12 @@
<item name="android:letterSpacing">0.0</item>
</style>
<style name="Theme.Widget.Button.TextButton" parent="Widget.MaterialComponents.Button.TextButton">
<item name="android:textAllCaps">false</item>
<item name="rippleColor">@color/fullRippleColor</item>
<item name="android:textColor">?colorAccent</item>
</style>
<style name="Theme.Widget.CustomImageButton">
<item name="android:background">@drawable/round_ripple</item>
<item name="android:clickable">true</item>