Bottom nav view animation on push/pop

Fade in and out on pop/push respectively
No longer refreshing cover if the url doesn't change
Fixing uniform layout
Fixes for categories
Manga controller has all chapter filter options working, shows current filters beside button
Manga details controller now has working toolbar menu as well
This commit is contained in:
Jay 2020-03-04 22:56:26 -08:00
parent 52e6a7fc95
commit ffb8a7bd17
28 changed files with 812 additions and 267 deletions

View File

@ -25,7 +25,7 @@ object Migrations {
if (BuildConfig.INCLUDE_UPDATER && preferences.automaticUpdates()) {
UpdaterJob.setupTask()
}
return false
return BuildConfig.DEBUG
}
if (oldVersion < 14) {

View File

@ -455,11 +455,12 @@ class LibraryUpdateService(
.concatMap { manga ->
val source = sourceManager.get(manga.source) as? HttpSource
?: return@concatMap Observable.empty<LibraryManga>()
source.fetchMangaDetails(manga)
.map { networkManga ->
val thumbnailUrl = manga.thumbnail_url
manga.copyFrom(networkManga)
db.insertManga(manga).executeAsBlocking()
if (thumbnailUrl != networkManga.thumbnail_url)
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
manga
}

View File

@ -28,10 +28,11 @@ import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
import eu.kanade.tachiyomi.ui.catalogue.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.catalogue_main_controller.*
import kotlinx.android.synthetic.main.main_activity.*
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -102,7 +103,7 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
*/
override fun onViewCreated(view: View) {
super.onViewCreated(view)
view.applyWindowInsetsForController()
view.applyWindowInsetsForRootController(activity!!.navigationView)
adapter = CatalogueAdapter(this)

View File

@ -53,8 +53,8 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorHint))
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable
.ic_add_white_24dp)
edit_button.gone()
image.gone()
edit_button.setImageDrawable(null)
edit_text.setText("")
edit_text.hint = title.text
}
@ -62,7 +62,6 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorPrimary))
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable
.ic_reorder_grey_24dp)
edit_button.visible()
image.visible()
edit_text.setText(title.text)
}
@ -94,12 +93,13 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
else {
if (!createCategory) {
setDragHandleView(reorder)
edit_button.setImageDrawable(ContextCompat.getDrawable(itemView.context, R.drawable.ic_edit_white_24dp))
}
else {
edit_button.setImageDrawable(null)
reorder.setOnTouchListener { _, _ -> true}
}
edit_text.clearFocus()
edit_button.setImageDrawable(ContextCompat.getDrawable(itemView.context, R.drawable.ic_edit_white_24dp))
edit_button.drawable.mutate().setTint(ContextCompat.getColor(itemView.context, R
.color.gray_button))
reorder.setImageDrawable(regularDrawable)

View File

@ -59,7 +59,7 @@ class CategoryPresenter(
*/
fun createCategory(name: String): Boolean {
// Do not allow duplicate categories.
if (categoryExists(name)) {
if (categoryExists(name, null)) {
controller.onCategoryExistsError()
return false
}
@ -116,7 +116,7 @@ class CategoryPresenter(
*/
fun renameCategory(category: Category, name: String): Boolean {
// Do not allow duplicate categories.
if (categoryExists(name)) {
if (categoryExists(name, category.id)) {
controller.onCategoryExistsError()
return false
}
@ -131,8 +131,8 @@ class CategoryPresenter(
/**
* Returns true if a category with the given name already exists.
*/
private fun categoryExists(name: String): Boolean {
return categories.any { it.name.equals(name, true) }
private fun categoryExists(name: String, id: Int?): Boolean {
return categories.any { it.name.equals(name, true) && id != it.id }
}
companion object {

View File

@ -51,7 +51,7 @@ import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.snack
import kotlinx.android.synthetic.main.filter_bottom_sheet.*
@ -176,7 +176,7 @@ open class LibraryController(
override fun onViewCreated(view: View) {
super.onViewCreated(view)
view.applyWindowInsetsForController()
view.applyWindowInsetsForRootController(activity!!.navigationView)
mangaPerRow = getColumnsPreferenceForCurrentOrientation().getOrDefault()
if (!::presenter.isInitialized)
presenter = LibraryPresenter(this)

View File

@ -69,7 +69,8 @@ class LibraryGridHolder(
var glide = GlideApp.with(adapter.recyclerView.context).load(item.manga)
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.signature(ObjectKey(MangaImpl.getLastCoverFetch(id).toString()))
glide = if (fixedSize) glide.centerCrop() else glide.override(cover_thumbnail.maxHeight)
glide = if (fixedSize) glide.centerCrop().override(cover_thumbnail.maxHeight)
else glide.override(cover_thumbnail.maxHeight)
glide.into(cover_thumbnail)
}
}

View File

@ -5,6 +5,7 @@ import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
@ -21,6 +22,7 @@ import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
class LibraryItem(val manga: LibraryManga,
@ -70,7 +72,9 @@ class LibraryItem(val manga: LibraryManga,
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
)
cover_thumbnail.maxHeight = (parent.itemWidth / 3f * 3.7f).toInt()
cover_thumbnail.minimumHeight = (parent.itemWidth / 3f * 3.7f).toInt()
constraint_layout.minHeight = 0
cover_thumbnail.scaleType = ImageView.ScaleType.CENTER_CROP
cover_thumbnail.adjustViewBounds = false
cover_thumbnail.layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,

View File

@ -1,5 +1,8 @@
package eu.kanade.tachiyomi.ui.main
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.app.SearchManager
import android.content.Intent
import android.content.res.Configuration
@ -7,7 +10,6 @@ import android.graphics.Color
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
@ -86,6 +88,10 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
private var extraViewForUndo:View? = null
private var canDismissSnackBar = false
private var animationSet: AnimatorSet? = null
private var bottomNavHeight = 0
fun setUndoSnackBar(snackBar: Snackbar?, extraViewToCheck: View? = null) {
this.snackBar = snackBar
canDismissSnackBar = false
@ -306,11 +312,12 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
}
navigationView.visibility = if (router.backstackSize > 1) View.GONE else View.VISIBLE
navigationView.alpha = if (router.backstackSize > 1) 0f else 1f
router.addChangeListener(object : ControllerChangeHandler.ControllerChangeListener {
override fun onChangeStarted(to: Controller?, from: Controller?, isPush: Boolean,
container: ViewGroup, handler: ControllerChangeHandler) {
syncActivityViewWithController(to, from)
syncActivityViewWithController(to, from, isPush)
}
override fun onChangeCompleted(to: Controller?, from: Controller?, isPush: Boolean,
@ -360,7 +367,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
return super.startSupportActionMode(callback)
}
override fun onSupportActionModeFinished(mode: androidx.appcompat.view.ActionMode) {
/* override fun onSupportActionModeFinished(mode: androidx.appcompat.view.ActionMode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) launchUI {
val scale = Settings.Global.getFloat(
contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f
@ -371,7 +378,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
window?.statusBarColor = getResourceColor(android.R.attr.statusBarColor)
}
super.onSupportActionModeFinished(mode)
}
}*/
private fun setExtensionsBadge() {
val updates = preferences.extensionUpdatesCount().getOrDefault()
@ -542,7 +549,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
return super.dispatchTouchEvent(ev)
}
protected open fun syncActivityViewWithController(to: Controller?, from: Controller? = null) {
protected open fun syncActivityViewWithController(to: Controller?, from: Controller? = null,
isPush: Boolean = false) {
if (from is DialogController || to is DialogController) {
return
}
@ -577,10 +585,37 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
appbar.enableElevation()
}
if (to !is DialogController)
if (to !is DialogController) {
navigationView.visibility = if (router.backstackSize == 0 ||
(router.backstackSize <= 1 && !isPush))
View.VISIBLE else navigationView.visibility
animationSet?.cancel()
animationSet = AnimatorSet()
val alphaAnimation = ValueAnimator.ofFloat(
navigationView.alpha,
if (router.backstackSize > 1) 0f else 1f
)
alphaAnimation.addUpdateListener { valueAnimator ->
navigationView.alpha = valueAnimator.animatedValue as Float
}
alphaAnimation.addListener(object : Animator.AnimatorListener {
override fun onAnimationEnd(animation: Animator?) {
navigationView.visibility = if (router.backstackSize > 1) View.GONE else View.VISIBLE
}
override fun onAnimationCancel(animation: Animator?) { }
override fun onAnimationRepeat(animation: Animator?) { }
override fun onAnimationStart(animation: Animator?) { }
})
alphaAnimation.duration = 200
alphaAnimation.startDelay = 50
animationSet?.playTogether(alphaAnimation)
animationSet?.start()
}
}
override fun downloadStatusChanged(downloading: Boolean) {
val downloadManager = Injekt.get<DownloadManager>()
val hasQueue = downloading || downloadManager.hasQueue()

View File

@ -39,7 +39,8 @@ class SearchActivity: MainActivity() {
}
}
override fun syncActivityViewWithController(to: Controller?, from: Controller?) {
override fun syncActivityViewWithController(to: Controller?, from: Controller?, isPush:
Boolean) {
if (from is DialogController || to is DialogController) {
return
}

View File

@ -6,34 +6,24 @@ 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
class ChaptersSortBottomSheet(controller: MangaChaptersController) : BottomSheetDialog
(controller.activity!!, R.style.BottomSheetDialogTheme) {
val activity = controller.activity!!
/**
* Preferences helper.
*/
private val preferences by injectLazy<PreferencesHelper>()
private var sheetBehavior: BottomSheetBehavior<*>
private val presenter = controller.presenter
init {
// Use activity theme for this layout
@ -45,7 +35,7 @@ class ChaptersSortBottomSheet(private val controller: MangaChaptersController) :
val height = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.window.decorView.rootWindowInsets.systemWindowInsetBottom
} else 0
sheetBehavior.peekHeight = 220.dpToPx + height
sheetBehavior.peekHeight = 380.dpToPx + height
sheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, progress: Float) { }
@ -62,7 +52,6 @@ class ChaptersSortBottomSheet(private val controller: MangaChaptersController) :
override fun onStart() {
super.onStart()
sheetBehavior.skipCollapsed = true
sheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
/**
@ -71,87 +60,79 @@ class ChaptersSortBottomSheet(private val controller: MangaChaptersController) :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initGeneralPreferences()
setBottomEdge(show_bookmark, activity)
close_button.setOnClickListener {
dismiss()
true
}
setBottomEdge(hide_titles, activity)
close_button.setOnClickListener { dismiss() }
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)
}
setOnDismissListener {
presenter.setFilters(
show_read.isChecked,
show_unread.isChecked,
show_download.isChecked,
show_bookmark.isChecked
)
}
}
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_read.isChecked = presenter.onlyRead()
show_unread.isChecked = presenter.onlyUnread()
show_download.isChecked = presenter.onlyDownloaded()
show_bookmark.isChecked = 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
sort_group.check(if (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
hide_titles.isChecked = presenter.manga.displayMode != Manga.DISPLAY_NAME
sort_method_group.check(if (presenter.manga.sorting == Manga.SORTING_SOURCE) R.id.sort_by_source else
R.id.sort_by_number)
sort_group.setOnCheckedChangeListener { _, checkedId ->
controller.presenter.setSortOrder(checkedId == R.id.sort_oldest)
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()
}*/
sort_method_group.setOnCheckedChangeListener { _, checkedId ->
presenter.setSortMethod(checkedId == R.id.sort_by_source)
}
/**
* 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()
}
hide_titles.setOnCheckedChangeListener { _, isChecked ->
presenter.hideTitle(isChecked)
}
/**
* 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()
show_all.setOnCheckedChangeListener(::checkedFilter)
show_read.setOnCheckedChangeListener(::checkedFilter)
show_unread.setOnCheckedChangeListener(::checkedFilter)
show_download.setOnCheckedChangeListener(::checkedFilter)
show_bookmark.setOnCheckedChangeListener(::checkedFilter)
}
private fun checkedFilter(checkBox: CompoundButton, isChecked: Boolean) {
if (isChecked) {
if (show_all == checkBox) {
show_read.isChecked = false
show_unread.isChecked = false
show_download.isChecked = false
show_bookmark.isChecked = false
}
else {
show_all.isChecked = false
if (show_read == checkBox) show_unread.isChecked = false
else if (show_unread == checkBox) show_read.isChecked = false
}
}
else if (!show_read.isChecked && !show_unread.isChecked &&
!show_download.isChecked && !show_bookmark.isChecked) {
show_all.isChecked = true
}
}
}

View File

@ -0,0 +1,43 @@
package eu.kanade.tachiyomi.ui.manga
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoController
/**
* Dialog to choose a shape for the icon.
*/
class ChooseShapeDialog(bundle: Bundle? = null) : DialogController(bundle) {
constructor(target: MangaInfoController) : this() {
targetController = target
}
constructor(target: MangaChaptersController) : this() {
targetController = target
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val modes = intArrayOf(
R.string.circular_icon,
R.string.rounded_icon,
R.string.square_icon,
R.string.star_icon)
return MaterialDialog(activity!!)
.title(R.string.icon_shape)
.negativeButton(android.R.string.cancel)
.listItemsSingleChoice (
items = modes.map { activity?.getString(it) as CharSequence },
waitForPositiveButton = false)
{ _, i, _ ->
(targetController as? MangaInfoController)?.createShortcutForShape(i)
(targetController as? MangaChaptersController)?.createShortcutForShape(i)
dismissDialog()
}
}
}

View File

@ -2,8 +2,13 @@ package eu.kanade.tachiyomi.ui.manga
import android.animation.ValueAnimator
import android.app.Activity
import android.app.PendingIntent
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
@ -16,16 +21,19 @@ import android.view.MenuInflater
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.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.ColorUtils
import androidx.core.graphics.drawable.IconCompat
import androidx.palette.graphics.Palette
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.vectordrawable.graphics.drawable.ArgbEvaluator
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.bumptech.glide.signature.ObjectKey
@ -38,12 +46,15 @@ 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
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download
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.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
@ -54,25 +65,31 @@ import eu.kanade.tachiyomi.ui.manga.MangaController.Companion.FROM_CATALOGUE_EXT
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.manga.info.EditMangaDialog
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.view.getText
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import jp.wasabeef.glide.transformations.CropSquareTransformation
import jp.wasabeef.glide.transformations.MaskTransformation
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
import java.io.File
class MangaChaptersController : BaseController,
ActionMode.Callback,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
ChaptersAdapter.MangaHeaderInterface,
@ -184,7 +201,7 @@ class MangaChaptersController : BaseController,
)
colorAnimator?.cancel()
colorAnimator = ValueAnimator.ofObject(
ArgbEvaluator(), colorFrom, colorTo
android.animation.ArgbEvaluator(), colorFrom, colorTo
)
colorAnimator?.duration = 250 // milliseconds
colorAnimator?.addUpdateListener { animator ->
@ -226,7 +243,7 @@ class MangaChaptersController : BaseController,
colorBack
)
else it?.getDarkMutedColor(colorBack)) ?: colorBack
onCoverLoaded(backDropColor)
coverColor = backDropColor
(recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)
?.setBackDrop(backDropColor)
if (toolbarIsColored) {
@ -264,11 +281,15 @@ class MangaChaptersController : BaseController,
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (type == ControllerChangeType.PUSH_ENTER || type == ControllerChangeType.POP_ENTER) {
if (type == ControllerChangeType.POP_ENTER)
return
(activity as MainActivity).appbar.setBackgroundColor(Color.TRANSPARENT)
(activity as MainActivity).toolbar.setBackgroundColor(Color.TRANSPARENT)
activity?.window?.statusBarColor = Color.TRANSPARENT
}
else if (type == ControllerChangeType.PUSH_EXIT || type == ControllerChangeType.POP_EXIT) {
if (router.backstack.lastOrNull()?.controller() is DialogController)
return
colorAnimator?.cancel()
(activity as MainActivity).toolbar.setBackgroundColor(activity?.getResourceColor(
@ -295,6 +316,7 @@ class MangaChaptersController : BaseController,
listOf(ChapterItem(presenter.headerItem, presenter.manga)) + presenter.chapters
)
}
activity?.invalidateOptionsMenu()
}
@ -305,7 +327,10 @@ class MangaChaptersController : BaseController,
presenter.fetchChaptersFromSource()
}
adapter?.updateDataSet(listOf(presenter.headerItem) + chapters)
}
activity?.invalidateOptionsMenu()
}
fun refreshAdapter() = adapter?.notifyDataSetChanged()
override fun onItemClick(view: View?, position: Int): Boolean {
val adapter = adapter ?: return false
@ -392,43 +417,231 @@ class MangaChaptersController : BaseController,
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.chapters, menu)
inflater.inflate(R.menu.manga_details, menu)
val editItem = menu.findItem(R.id.action_edit)
editItem.isVisible = presenter.manga.favorite && !presenter.isLockedFromSearch
menu.findItem(R.id.action_download).isVisible = !presenter.isLockedFromSearch
menu.findItem(R.id.action_mark_all_as_read).isVisible =
presenter.getNextUnreadChapter() != null && !presenter.isLockedFromSearch
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_edit -> EditMangaDialog(this, presenter.manga).showDialog(router)
R.id.action_open_in_web_view -> openInWebView()
R.id.action_share -> prepareToShareManga()
R.id.action_add_to_home_screen -> addToHomeScreen()
R.id.action_mark_all_as_read -> {
MaterialDialog(view!!.context)
.message(R.string.mark_all_as_read_message)
.positiveButton(R.string.action_mark_as_read) {
markAsRead(presenter.chapters)
}
.negativeButton(android.R.string.cancel)
.show()
}
R.id.download_next, R.id.download_next_5, R.id.download_next_10,
R.id.download_custom, R.id.download_unread, R.id.download_all
-> downloadChapters(item.itemId)
else -> return super.onOptionsItemSelected(item)
}
return true
}
/**
* Called to run Intent with [Intent.ACTION_SEND], which show share dialog.
*/
override fun prepareToShareManga() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && manga_cover.drawable != null)
GlideApp.with(activity!!).asBitmap().load(presenter.manga).into(object :
CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
presenter.shareManga(resource)
}
override fun onLoadCleared(placeholder: Drawable?) {}
override fun onLoadFailed(errorDrawable: Drawable?) {
shareManga()
}
})
else shareManga()
}
/**
* Called to run Intent with [Intent.ACTION_SEND], which show share dialog.
*/
fun shareManga(cover: File? = null) {
val context = view?.context ?: return
val source = presenter.source as? HttpSource ?: return
val stream = cover?.getUriCompat(context)
try {
val url = source.mangaDetailsRequest(presenter.manga).url.toString()
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/*"
putExtra(Intent.EXTRA_TEXT, url)
putExtra(Intent.EXTRA_TITLE, presenter.manga.currentTitle())
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
if (stream != null) {
clipData = ClipData.newRawUri(null, stream)
}
}
startActivity(Intent.createChooser(intent, context.getString(R.string.action_share)))
} catch (e: Exception) {
context.toast(e.message)
}
}
private fun openInWebView() {
val source = presenter.source as? HttpSource ?: return
val url = try {
source.mangaDetailsRequest(presenter.manga).url.toString()
} catch (e: Exception) {
return
}
val activity = activity ?: return
val intent = WebViewActivity.newIntent(activity.applicationContext, source.id, url, presenter.manga
.originalTitle())
startActivity(intent)
}
private fun downloadChapters(choice: Int) {
val chaptersToDownload = when (choice) {
R.id.download_next -> presenter.getUnreadChaptersSorted().take(1)
R.id.download_next_5 -> presenter.getUnreadChaptersSorted().take(5)
R.id.download_next_10 -> presenter.getUnreadChaptersSorted().take(10)
R.id.download_custom -> {
showCustomDownloadDialog()
return
}
R.id.download_unread -> presenter.chapters.filter { !it.read }
R.id.download_all -> presenter.chapters
else -> emptyList()
}
if (chaptersToDownload.isNotEmpty()) {
downloadChapters(chaptersToDownload)
}
}
private fun downloadChapters(chapters: List<ChapterItem>) {
val view = view
presenter.downloadChapters(chapters)
if (view != null && !presenter.manga.favorite && (snack == null ||
snack?.getText() != view.context.getString(R.string.snack_add_to_library))) {
snack = view.snack(view.context.getString(R.string.snack_add_to_library), Snackbar.LENGTH_INDEFINITE) {
setAction(R.string.action_add) {
presenter.setFavorite(true)
}
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
if (snack == transientBottomBar) snack = null
}
})
}
(activity as? MainActivity)?.setUndoSnackBar(snack)
}
}
/**
* Add a shortcut of the manga to the home screen
*/
private fun addToHomeScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// TODO are transformations really unsupported or is it just the Pixel Launcher?
createShortcutForShape()
} else {
ChooseShapeDialog(this).showDialog(router)
}
}
/**
* Retrieves the bitmap of the shortcut with the requested shape and calls [createShortcut] when
* the resource is available.
*
* @param i The shape index to apply. Defaults to circle crop transformation.
*/
fun createShortcutForShape(i: Int = 0) {
if (activity == null) return
GlideApp.with(activity!!)
.asBitmap()
.load(presenter.manga)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.apply {
when (i) {
0 -> circleCrop()
1 -> transform(RoundedCorners(5))
2 -> transform(CropSquareTransformation())
3 -> centerCrop().transform(MaskTransformation(R.drawable.mask_star))
}
}
.into(object : CustomTarget<Bitmap>(128, 128) {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
createShortcut(resource)
}
override fun onLoadCleared(placeholder: Drawable?) { }
override fun onLoadFailed(errorDrawable: Drawable?) {
activity?.toast(R.string.icon_creation_fail)
}
})
}
/**
* Create shortcut using ShortcutManager.
*
* @param icon The image of the shortcut.
*/
private fun createShortcut(icon: Bitmap) {
val activity = activity ?: return
// Create the shortcut intent.
val shortcutIntent = activity.intent
.setAction(MainActivity.SHORTCUT_MANGA)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(MangaController.MANGA_EXTRA, presenter.manga.id)
// Check if shortcut placement is supported
if (ShortcutManagerCompat.isRequestPinShortcutSupported(activity)) {
val shortcutId = "manga-shortcut-${presenter.manga.originalTitle()}-${presenter.source.name}"
// Create shortcut info
val shortcutInfo = ShortcutInfoCompat.Builder(activity, shortcutId)
.setShortLabel(presenter.manga.currentTitle())
.setIcon(IconCompat.createWithBitmap(icon))
.setIntent(shortcutIntent)
.build()
val successCallback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create the CallbackIntent.
val intent = ShortcutManagerCompat.createShortcutResultIntent(activity, shortcutInfo)
// Configure the intent so that the broadcast receiver gets the callback successfully.
PendingIntent.getBroadcast(activity, 0, intent, 0)
} else {
NotificationReceiver.shortcutCreatedBroadcast(activity)
}
// Request shortcut.
ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfo,
successCallback.intentSender)
}
}
private fun showCustomDownloadDialog() {
// DownloadCustomChaptersDialog(this, presenter.chapters.size).showDialog(router)
}
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 topCoverHeight(): Int = headerHeight
override fun nextChapter(): Chapter? = presenter.getNextUnreadChapter()
override fun mangaSource(): Source = presenter.source
override fun readNextChapter() {
if (activity is SearchActivity && presenter.isLockedFromSearch) {
SecureActivityDelegate.promptLockIfNeeded(activity)
@ -451,17 +664,17 @@ class MangaChaptersController : BaseController,
}
override fun downloadChapter(position: Int) {
val adapter = adapter ?: return
val chapter = adapter.getItem(position) ?: return
val view = view ?: return
val chapter = adapter?.getItem(position) ?: return
if (chapter.isHeader) return
if (chapter.status != Download.NOT_DOWNLOADED && chapter.status != Download.ERROR) {
presenter.deleteChapters(listOf(chapter))
}
else {
val isError = chapter.status == Download.ERROR
presenter.downloadChapters(listOf(chapter))
if (isError)
presenter.restartDownloads()
if (chapter.status == Download.ERROR)
DownloadService.start(view.context)
else
downloadChapters(listOf(chapter))
}
}
@ -477,8 +690,6 @@ class MangaChaptersController : BaseController,
ChaptersSortBottomSheet(this).show()
}
override fun chapterCount():Int = presenter.chapters.size
override fun favoriteManga(longPress: Boolean) {
if (presenter.isLockedFromSearch) {
SecureActivityDelegate.promptLockIfNeeded(activity)
@ -560,8 +771,29 @@ class MangaChaptersController : BaseController,
(activity as? MainActivity)?.setUndoSnackBar(snack, fab_favorite)
}
override fun mangaPresenter(): MangaPresenter = presenter
override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {
val manga = mangas.firstOrNull() ?: return
presenter.moveMangaToCategories(manga, categories)
}
/**
* Copies a string to clipboard
*
* @param label Label to show to the user describing the content
* @param content the actual text to copy to the board
*/
private fun copyToClipboard(label: String, content: String, resId: Int) {
if (content.isBlank()) return
val activity = activity ?: return
val view = view ?: return
val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText(label, content))
snack = view.snack(view.context.getString(R.string.copied_to_clipboard, view.context
.getString(resId)))
}
}

View File

@ -7,6 +7,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.signature.ObjectKey
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
@ -44,9 +45,9 @@ class MangaHeaderHolder(
manga_genres_tags.setOnTagClickListener {
adapter.coverListener?.tagClicked(it)
}
filter_button.setOnClickListener {
adapter.coverListener?.showChapterFilter()
}
filter_button.setOnClickListener { adapter.coverListener?.showChapterFilter() }
filters_text.setOnClickListener { adapter.coverListener?.showChapterFilter() }
share_button.setOnClickListener { adapter.coverListener?.prepareToShareManga() }
favorite_button.setOnClickListener {
adapter.coverListener?.favoriteManga(false)
}
@ -66,6 +67,7 @@ class MangaHeaderHolder(
}
override fun bind(item: ChapterItem, manga: Manga) {
val presenter = adapter.coverListener?.mangaPresenter() ?: return
manga_full_title.text = manga.currentTitle()
if (manga.currentGenres().isNullOrBlank().not())
@ -83,7 +85,8 @@ class MangaHeaderHolder(
.no_description)
manga_summary.post {
if (manga_summary.lineCount < 3 && manga.currentGenres().isNullOrBlank()) {
if ((manga_summary.lineCount < 3 && manga.currentGenres().isNullOrBlank())
|| less_button.visibility == View.VISIBLE) {
more_button_group.gone()
}
else
@ -127,11 +130,11 @@ class MangaHeaderHolder(
itemView.context.getResourceColor(R.attr
.colorOnSurface), 31))
}
true_backdrop.setBackgroundColor(adapter.coverListener?.coverColor() ?:
true_backdrop.setBackgroundColor(adapter.coverListener.coverColor() ?:
itemView.context.getResourceColor(android.R.attr.colorBackground))
with(start_reading_button) {
val nextChapter = adapter.coverListener?.nextChapter()
val nextChapter = presenter.getNextUnreadChapter()
visibleIf(nextChapter != null && !item.isLocked)
if (nextChapter != null) {
val number = adapter.decimalFormat.format(nextChapter.chapter_number.toDouble())
@ -147,11 +150,11 @@ class MangaHeaderHolder(
}
}
val count = adapter.coverListener?.chapterCount() ?: 0
val count = presenter.chapters.size
chapters_title.text = itemView.resources.getQuantityString(R.plurals.chapters, count, count)
top_view.updateLayoutParams<ConstraintLayout.LayoutParams> {
height = adapter.coverListener?.topCoverHeight() ?: 0
height = adapter.coverListener.topCoverHeight() ?: 0
}
manga_status.text = (itemView.context.getString( when (manga.status) {
@ -160,7 +163,9 @@ class MangaHeaderHolder(
SManga.LICENSED -> R.string.licensed
else -> R.string.unknown_status
}))
manga_source.text = adapter.coverListener?.mangaSource()?.toString()
manga_source.text = presenter.source.toString()
filters_text.text = presenter.currentFilters()
if (!manga.initialized) return
GlideApp.with(view.context).load(manga)
@ -171,6 +176,7 @@ class MangaHeaderHolder(
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString()))
.centerCrop()
.transition(DrawableTransitionOptions.withCrossFade())
.into(backdrop)
}

View File

@ -1,5 +1,8 @@
package eu.kanade.tachiyomi.ui.manga
import android.app.Application
import android.graphics.Bitmap
import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -18,9 +21,11 @@ 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.source.model.SManga
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.launchUI
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -30,6 +35,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
import java.util.Date
import kotlin.coroutines.CoroutineContext
@ -89,22 +97,24 @@ class MangaPresenter(private val controller: MangaChaptersController,
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)
// Store the last emission
this.chapters = applyChapterFilters(chapters)
}
private fun updateChapters(fetchedChapters: List<Chapter>? = null) {
val chapters = (fetchedChapters ?:
db.getChapters(manga).executeAsBlocking()).map { it.toModel() }
// Store the last emission
this.chapters = applyChapterFilters(chapters)
// Find downloaded chapters
setDownloadedChapters(chapters)
// Store the last emission
this.chapters = applyChapterFilters(chapters)
}
/**
@ -148,9 +158,10 @@ class MangaPresenter(private val controller: MangaChaptersController,
* Sets the active display mode.
* @param mode the mode to set.
*/
fun setDisplayMode(mode: Int) {
manga.displayMode = mode
fun hideTitle(hide: Boolean) {
manga.displayMode = if (hide) Manga.DISPLAY_NUMBER else Manga.DISPLAY_NAME
db.updateFlags(manga).executeAsBlocking()
controller.refreshAdapter()
}
/**
@ -240,6 +251,11 @@ class MangaPresenter(private val controller: MangaChaptersController,
return chapters.sortedByDescending { it.source_order }.find { !it.read }
}
fun getUnreadChaptersSorted() = chapters
.filter { !it.read && it.status == Download.NOT_DOWNLOADED }
.distinctBy { it.name }
.sortedByDescending { it.source_order }
/**
* Returns the next unread chapter or null if everything is read.
*/
@ -404,13 +420,50 @@ class MangaPresenter(private val controller: MangaChaptersController,
}
/**
* Reverses the sorting and requests an UI update.
* Sets the sorting order and requests an UI update.
*/
fun setSortOrder(desend: Boolean) {
manga.setChapterOrder(if (desend) Manga.SORT_ASC else Manga.SORT_DESC)
db.updateFlags(manga).executeAsBlocking()
asyncUpdateMangaAndChapters()
}
/**
* Sets the sorting method and requests an UI update.
*/
fun setSortMethod(bySource: Boolean) {
manga.sorting = if (bySource) Manga.SORTING_SOURCE else Manga.SORTING_NUMBER
asyncUpdateMangaAndChapters()
}
/**
* Removes all filters and requests an UI update.
*/
fun setFilters(read: Boolean, unread: Boolean, downloaded: Boolean, bookmarked: Boolean) {
manga.readFilter = when {
read -> Manga.SHOW_READ
unread -> Manga.SHOW_UNREAD
else -> Manga.SHOW_ALL
}
manga.downloadedFilter = if (downloaded) Manga.SHOW_DOWNLOADED else Manga.SHOW_ALL
manga.bookmarkedFilter = if (bookmarked) Manga.SHOW_BOOKMARKED else Manga.SHOW_ALL
asyncUpdateMangaAndChapters()
}
private fun asyncUpdateMangaAndChapters() {
launch {
withContext(Dispatchers.IO) { db.updateFlags(manga).executeAsBlocking() }
updateChapters()
controller.updateChapters(chapters)
withContext(Dispatchers.Main) { controller.updateChapters(chapters) }
}
}
fun currentFilters(): String {
val filtersId = mutableListOf<Int?>()
filtersId.add(if (onlyRead()) R.string.action_filter_read else null)
filtersId.add(if (onlyUnread()) R.string.action_filter_unread else null)
filtersId.add(if (onlyDownloaded()) R.string.action_filter_downloaded else null)
filtersId.add(if (onlyBookmarked()) R.string.action_filter_bookmarked else null)
return filtersId.filterNotNull().joinToString(", ") { preferences.context.getString(it) }
}
fun toggleFavorite(): Boolean {
@ -479,4 +532,122 @@ class MangaPresenter(private val controller: MangaChaptersController,
fetchChapters()
}
}
fun shareManga(cover: Bitmap) {
val context = Injekt.get<Application>()
val destDir = File(context.cacheDir, "shared_image")
launch(Dispatchers.IO) {
destDir.deleteRecursively()
try {
val image = saveImage(cover, destDir, manga)
if (image != null)
controller.shareManga(image)
else controller.shareManga()
}
catch (e:java.lang.Exception) { }
}
}
private fun saveImage(cover:Bitmap, directory: File, manga: Manga): File? {
directory.mkdirs()
// Build destination file.
val filename = DiskUtil.buildValidFilename("${manga.originalTitle()} - Cover.jpg")
val destFile = File(directory, filename)
val stream: OutputStream = FileOutputStream(destFile)
cover.compress(Bitmap.CompressFormat.JPEG, 75, stream)
stream.flush()
stream.close()
return destFile
}
fun updateManga(title:String?, author:String?, artist: String?, uri: Uri?,
description: String?, tags: Array<String>?) {
if (manga.source == LocalSource.ID) {
manga.title = if (title.isNullOrBlank()) manga.url else title.trim()
manga.author = author?.trim()
manga.artist = artist?.trim()
manga.description = description?.trim()
val tagsString = tags?.joinToString(", ") { it.capitalize() }
manga.genre = if (tags.isNullOrEmpty()) null else tagsString?.trim()
LocalSource(downloadManager.context).updateMangaInfo(manga)
db.updateMangaInfo(manga).executeAsBlocking()
}
else {
var changed = false
val title = title?.trim()
if (!title.isNullOrBlank() && manga.originalTitle().isBlank()) {
manga.title = title
changed = true
}
else if (title.isNullOrBlank() && manga.currentTitle() != manga.originalTitle()) {
manga.title = manga.originalTitle()
changed = true
} else if (!title.isNullOrBlank() && title != manga.currentTitle()) {
manga.title = "${title}${SManga.splitter}${manga.originalTitle()}"
changed = true
}
val author = author?.trim()
if (author.isNullOrBlank() && manga.currentAuthor() != manga.originalAuthor()) {
manga.author = manga.originalAuthor()
changed = true
} else if (!author.isNullOrBlank() && author != manga.currentAuthor()) {
manga.author = "${author}${SManga.splitter}${manga.originalAuthor() ?: ""}"
changed = true
}
val artist = artist?.trim()
if (artist.isNullOrBlank() && manga.currentArtist() != manga.originalArtist()) {
manga.artist = manga.originalArtist()
changed = true
} else if (!artist.isNullOrBlank() && artist != manga.currentArtist()) {
manga.artist = "${artist}${SManga.splitter}${manga.originalArtist() ?: ""}"
changed = true
}
val description = description?.trim()
if (description.isNullOrBlank() && manga.currentDesc() != manga.originalDesc()) {
manga.description = manga.originalDesc()
changed = true
} else if (!description.isNullOrBlank() && description != manga.currentDesc()) {
manga.description = "${description}${SManga.splitter}${manga.originalDesc() ?: ""}"
changed = true
}
var tagsString = tags?.joinToString(", ")
if ((tagsString.isNullOrBlank() && manga.currentGenres() != manga.originalGenres())
|| tagsString == manga.originalGenres()) {
manga.genre = manga.originalGenres()
changed = true
} else if (!tagsString.isNullOrBlank() && tagsString != manga.currentGenres()) {
tagsString = tags?.joinToString(", ") { it.capitalize() }
manga.genre = "${tagsString}${SManga.splitter}${manga.originalGenres() ?: ""}"
changed = true
}
if (changed) db.updateMangaInfo(manga).executeAsBlocking()
}
if (uri != null) editCoverWithStream(uri)
controller.updateHeader()
}
private fun editCoverWithStream(uri: Uri): Boolean {
val inputStream = downloadManager.context.contentResolver.openInputStream(uri) ?:
return false
if (manga.source == LocalSource.ID) {
LocalSource.updateCover(downloadManager.context, manga, inputStream)
return true
}
if (manga.thumbnail_url != null && manga.favorite) {
Injekt.get<PreferencesHelper>().refreshCoversToo().set(false)
coverCache.copyToCache(manga.thumbnail_url!!, inputStream)
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
return true
}
return false
}
}

View File

@ -5,11 +5,10 @@ import android.view.MenuItem
import androidx.fragment.app.FragmentActivity
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.manga.MangaPresenter
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.system.getResourceColor
import uy.kohesive.injekt.injectLazy
@ -60,13 +59,12 @@ class ChaptersAdapter(
interface MangaHeaderInterface {
fun coverColor(): Int?
fun nextChapter(): Chapter?
fun mangaPresenter(): MangaPresenter
fun prepareToShareManga()
fun readNextChapter()
fun downloadChapter(position: Int)
fun topCoverHeight(): Int
fun chapterCount(): Int
fun tagClicked(text: String)
fun mangaSource(): Source
fun showChapterFilter()
fun favoriteManga(longPress: Boolean)
}

View File

@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.manga.MangaChaptersController
import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.android.synthetic.main.edit_manga_dialog.view.*
@ -34,9 +35,9 @@ class EditMangaDialog : DialogController {
private var customCoverUri:Uri? = null
private val infoController
get() = targetController as MangaInfoController
get() = targetController as MangaChaptersController
constructor(target: MangaInfoController, manga: Manga) : super(Bundle()
constructor(target: MangaChaptersController, manga: Manga) : super(Bundle()
.apply {
putLong(KEY_MANGA, manga.id!!)
}) {
@ -168,7 +169,6 @@ class EditMangaDialog : DialogController {
dialogView?.manga_author?.text.toString(), dialogView?.manga_artist?.text.toString(),
customCoverUri, dialogView?.manga_description?.text.toString(),
dialogView?.manga_genres_tags?.tags)
infoController.updateTitle()
}
private companion object {

View File

@ -5,7 +5,6 @@ import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.app.Activity
import android.app.Dialog
import android.app.PendingIntent
import android.content.ClipData
import android.content.ClipboardManager
@ -15,7 +14,6 @@ import android.content.res.Configuration
import android.graphics.Bitmap
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
@ -31,8 +29,6 @@ import androidx.transition.ChangeBounds
import androidx.transition.ChangeImageTransform
import androidx.transition.TransitionManager
import androidx.transition.TransitionSet
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
@ -55,13 +51,13 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
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.manga.ChooseShapeDialog
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
@ -236,7 +232,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_edit -> EditMangaDialog(this, presenter.manga).showDialog(router)
//R.id.action_edit -> EditMangaDialog(this, presenter.manga).showDialog(router)
R.id.action_open_in_web_view -> openInWebView()
R.id.action_share -> prepareToShareManga()
R.id.action_add_to_home_screen -> addToHomeScreen()
@ -608,41 +604,13 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
}
}
/**
* Dialog to choose a shape for the icon.
*/
private class ChooseShapeDialog(bundle: Bundle? = null) : DialogController(bundle) {
constructor(target: MangaInfoController) : this() {
targetController = target
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val modes = intArrayOf(R.string.circular_icon,
R.string.rounded_icon,
R.string.square_icon,
R.string.star_icon)
return MaterialDialog(activity!!)
.title(R.string.icon_shape)
.negativeButton(android.R.string.cancel)
.listItemsSingleChoice (
items = modes.map { activity?.getString(it) as CharSequence },
waitForPositiveButton = false)
{ _, i, _ ->
(targetController as? MangaInfoController)?.createShortcutForShape(i)
dismissDialog()
}
}
}
/**
* Retrieves the bitmap of the shortcut with the requested shape and calls [createShortcut] when
* the resource is available.
*
* @param i The shape index to apply. Defaults to circle crop transformation.
*/
private fun createShortcutForShape(i: Int = 0) {
fun createShortcutForShape(i: Int = 0) {
if (activity == null) return
GlideApp.with(activity!!)
.asBitmap()

View File

@ -28,7 +28,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaChaptersController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
import eu.kanade.tachiyomi.util.view.snack
import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.recent_chapters_controller.*
@ -82,7 +82,8 @@ class RecentChaptersController : NucleusController<RecentChaptersPresenter>(),
*/
override fun onViewCreated(view: View) {
super.onViewCreated(view)
view.applyWindowInsetsForController()
view.applyWindowInsetsForRootController(activity!!.navigationView)
view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS)
// Init RecyclerView and adapter
val layoutManager = LinearLayoutManager(view.context)

View File

@ -26,8 +26,9 @@ import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.recently_read_controller.*
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -80,16 +81,7 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle),
*/
override fun onViewCreated(view: View) {
super.onViewCreated(view)
view.applyWindowInsetsForController()
/*view.updateLayoutParams<FrameLayout.LayoutParams> {
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
val array = view.context.obtainStyledAttributes(attrsArray)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
topMargin = activity!!.window.decorView.rootWindowInsets.systemWindowInsetTop + array
.getDimensionPixelSize(0, 0)
}
array.recycle()
}*/
view.applyWindowInsetsForRootController(activity!!.navigationView)
// Initialize adapter
adapter = RecentlyReadAdapter(this)
recycler.adapter = adapter

View File

@ -17,6 +17,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
import kotlinx.android.synthetic.main.main_activity.*
import rx.Observable
import rx.Subscription
import rx.subscriptions.CompositeSubscription
@ -35,24 +37,12 @@ abstract class SettingsController : PreferenceController() {
untilDestroySubscriptions = CompositeSubscription()
}
val view = super.onCreateView(inflater, container, savedInstanceState)
/*view.updateLayoutParams<FrameLayout.LayoutParams> {
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
val array = view.context.obtainStyledAttributes(attrsArray)
topMargin = array.getDimensionPixelSize(0, 0)
array.recycle()
}*/
if (this is SettingsMainController)
view.applyWindowInsetsForRootController(activity!!.navigationView)
else {
view.applyWindowInsetsForController()
/*view.updateLayoutParams<FrameLayout.LayoutParams> {
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
val array = view.context.obtainStyledAttributes(attrsArray)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
topMargin = activity!!.window.decorView.rootWindowInsets.systemWindowInsetTop + array
.getDimensionPixelSize(0, 0)
}
array.recycle()
}*/
listView.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
}
return view
}

View File

@ -12,6 +12,7 @@ import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.WindowInsets
import android.widget.Button
import android.widget.FrameLayout
@ -207,6 +208,28 @@ fun View.applyWindowInsetsForController() {
requestApplyInsetsWhenAttached()
}
fun View.applyWindowInsetsForRootController(bottomNav: View) {
viewTreeObserver.addOnGlobalLayoutListener(
object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (bottomNav.height > 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
setOnApplyWindowInsetsListener { view, insets ->
view.updateLayoutParams<FrameLayout.LayoutParams> {
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
val array = view.context.obtainStyledAttributes(attrsArray)
topMargin = insets.systemWindowInsetTop + array.getDimensionPixelSize(0, 0)
bottomMargin = bottomNav.height
array.recycle()
}
insets
}
requestApplyInsetsWhenAttached()
}
}
}
)
}
fun View.requestApplyInsetsWhenAttached() {
if (isAttachedToWindow) {

View File

@ -52,6 +52,7 @@
android:imeOptions="actionDone"
android:inputType="none"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/textColorPrimary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="@id/title"

View File

@ -30,7 +30,7 @@
<RadioGroup
android:id="@+id/sort_group"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="12dp"
@ -39,34 +39,19 @@
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/sort_newest"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="Newest first" />
android:text="@string/newest_first" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/sort_oldest"
android:layout_marginStart="12dp"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="Oldest first" />
android:text="@string/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"
@ -82,7 +67,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="Show All" />
android:text="@string/show_all" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/show_read"
@ -90,7 +75,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="Show read chapters" />
android:text="@string/show_read_chapters" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/show_unread"
@ -98,7 +83,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="Show unread chapters" />
android:text="@string/show_unread_chapters" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/show_download"
@ -106,7 +91,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="Show downloaded chapters" />
android:text="@string/show_downloaded_chapters" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/show_bookmark"
@ -114,7 +99,45 @@
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="Show bookmarked chapters" />
android:text="@string/show_bookmarked_chapters" />
<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/more" />
<RadioGroup
android:id="@+id/sort_method_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="12dp"
android:paddingEnd="12dp">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/sort_by_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sort_by_source_s_order" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/sort_by_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sort_by_chapter_number" />
</RadioGroup>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/hide_titles"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="@string/hide_chapter_titles" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -10,8 +10,8 @@
android:id="@+id/controller_container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/navigationView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

View File

@ -165,7 +165,7 @@
app:icon="@drawable/ic_sync_black_24dp" />
<ImageView
android:id="@+id/edit_button"
android:id="@+id/share_button"
style="@style/Theme.Widget.CustomImageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -173,8 +173,8 @@
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" />
android:contentDescription="@string/action_share"
android:src="@drawable/ic_share_white_24dp" />
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
@ -312,16 +312,17 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/chapters_title"
style="@style/TextAppearance.MaterialComponents.Headline6"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:layout_marginBottom="12dp"
android:maxLines="1"
android:text="@string/chapters"
android:textSize="17sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/filters_text"
app:layout_constraintStart_toStartOf="@id/manga_summary_label"
app:layout_constraintTop_toBottomOf="@id/start_reading_button"
app:layout_constraintEnd_toStartOf="@id/filters_text"/>
app:layout_constraintTop_toBottomOf="@id/start_reading_button" />
<ImageView
android:id="@+id/filter_button"
@ -332,17 +333,22 @@
android:padding="5dp"
android:src="@drawable/ic_filter_list_white_24dp"
app:layout_constraintBottom_toBottomOf="@id/chapters_title"
app:layout_constraintTop_toTopOf="@id/chapters_title"
app:layout_constraintEnd_toEndOf="parent" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/chapters_title" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/filters_text"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="6dp"
android:maxLines="2"
android:padding="5dp"
tools:text="Read"
app:layout_constraintTop_toTopOf="@id/filter_button"
android:textColor="?android:textColorHint"
android:textAlignment="textEnd"
app:layout_constraintBottom_toBottomOf="@id/filter_button"
app:layout_constraintEnd_toStartOf="@id/filter_button" />
app:layout_constraintEnd_toStartOf="@id/filter_button"
app:layout_constraintStart_toEndOf="@+id/chapters_title"
app:layout_constraintTop_toTopOf="@id/filter_button"
tools:text="Read, Unread, Bookmarked, Downloaded, All" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_edit"
android:icon="@drawable/ic_edit_white_24dp"
android:title="@string/action_edit"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_download"
android:icon="@drawable/ic_file_download_white_24dp"
android:title="@string/action_share"
app:showAsAction="ifRoom">
<menu>
<item
android:id="@+id/download_next"
android:title="@string/download_1" />
<item
android:id="@+id/download_next_5"
android:title="@string/download_5" />
<item
android:id="@+id/download_next_10"
android:title="@string/download_10" />
<item
android:id="@+id/download_custom"
android:title="@string/download_custom" />
<item
android:id="@+id/download_unread"
android:title="@string/download_unread" />
<item
android:id="@+id/download_all"
android:title="@string/download_all" />
</menu>
</item>
<item
android:id="@+id/action_mark_all_as_read"
android:icon="@drawable/ic_done_all_white_24dp"
android:title="@string/action_mark_all_as_read"
app:showAsAction="never" />
<item
android:id="@+id/action_open_in_web_view"
android:title="@string/action_open_in_web_view"
app:showAsAction="never" />
<item
android:id="@+id/action_add_to_home_screen"
android:title="@string/action_add_to_home_screen"
app:showAsAction="never" />
</menu>

View File

@ -68,6 +68,7 @@
<string name="action_skip_manga">Don\'t migrate</string>
<string name="action_select_all">Select all</string>
<string name="action_mark_as_read">Mark as read</string>
<string name="action_mark_all_as_read">Mark all 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>
@ -507,6 +508,7 @@
<item quantity="other">%1$d chapters</item>
</plurals>
<string name="no_description">No description</string>
<string name="mark_all_as_read_message">Mark all chapters as read?</string>
<!-- Manga chapters fragment -->
<string name="start_reading">Start reading</string>
@ -710,5 +712,16 @@
<string name="action_auto">Auto</string>
<string name="more">More</string>
<string name="less">Less</string>
<string name="more_options">More options</string>
<string name="show_bookmarked_chapters">Show bookmarked chapters</string>
<string name="show_downloaded_chapters">Show downloaded chapters</string>
<string name="show_unread_chapters">Show unread chapters</string>
<string name="show_read_chapters">Show read chapters</string>
<string name="show_all">Show All</string>
<string name="hide_chapter_titles">Hide chapter titles</string>
<string name="sort_by_source_s_order">Sort by source\'s order</string>
<string name="sort_by_chapter_number">Sort by chapter number</string>
<string name="newest_first">Newest to oldest</string>
<string name="oldest_first">Oldest to newest</string>
</resources>