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()) { if (BuildConfig.INCLUDE_UPDATER && preferences.automaticUpdates()) {
UpdaterJob.setupTask() UpdaterJob.setupTask()
} }
return false return BuildConfig.DEBUG
} }
if (oldVersion < 14) { if (oldVersion < 14) {

View File

@ -455,11 +455,12 @@ class LibraryUpdateService(
.concatMap { manga -> .concatMap { manga ->
val source = sourceManager.get(manga.source) as? HttpSource val source = sourceManager.get(manga.source) as? HttpSource
?: return@concatMap Observable.empty<LibraryManga>() ?: return@concatMap Observable.empty<LibraryManga>()
source.fetchMangaDetails(manga) source.fetchMangaDetails(manga)
.map { networkManga -> .map { networkManga ->
val thumbnailUrl = manga.thumbnail_url
manga.copyFrom(networkManga) manga.copyFrom(networkManga)
db.insertManga(manga).executeAsBlocking() db.insertManga(manga).executeAsBlocking()
if (thumbnailUrl != networkManga.thumbnail_url)
MangaImpl.setLastCoverFetch(manga.id!!, Date().time) MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
manga 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.catalogue.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener 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 eu.kanade.tachiyomi.widget.preference.SourceLoginDialog
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.catalogue_main_controller.* import kotlinx.android.synthetic.main.catalogue_main_controller.*
import kotlinx.android.synthetic.main.main_activity.*
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -102,7 +103,7 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
*/ */
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
view.applyWindowInsetsForController() view.applyWindowInsetsForRootController(activity!!.navigationView)
adapter = CatalogueAdapter(this) 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)) title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorHint))
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable
.ic_add_white_24dp) .ic_add_white_24dp)
edit_button.gone()
image.gone() image.gone()
edit_button.setImageDrawable(null)
edit_text.setText("") edit_text.setText("")
edit_text.hint = title.text 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)) title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorPrimary))
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable
.ic_reorder_grey_24dp) .ic_reorder_grey_24dp)
edit_button.visible()
image.visible() image.visible()
edit_text.setText(title.text) edit_text.setText(title.text)
} }
@ -94,12 +93,13 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
else { else {
if (!createCategory) { if (!createCategory) {
setDragHandleView(reorder) setDragHandleView(reorder)
edit_button.setImageDrawable(ContextCompat.getDrawable(itemView.context, R.drawable.ic_edit_white_24dp))
} }
else { else {
edit_button.setImageDrawable(null)
reorder.setOnTouchListener { _, _ -> true} reorder.setOnTouchListener { _, _ -> true}
} }
edit_text.clearFocus() 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 edit_button.drawable.mutate().setTint(ContextCompat.getColor(itemView.context, R
.color.gray_button)) .color.gray_button))
reorder.setImageDrawable(regularDrawable) reorder.setImageDrawable(regularDrawable)

View File

@ -59,7 +59,7 @@ class CategoryPresenter(
*/ */
fun createCategory(name: String): Boolean { fun createCategory(name: String): Boolean {
// Do not allow duplicate categories. // Do not allow duplicate categories.
if (categoryExists(name)) { if (categoryExists(name, null)) {
controller.onCategoryExistsError() controller.onCategoryExistsError()
return false return false
} }
@ -116,7 +116,7 @@ class CategoryPresenter(
*/ */
fun renameCategory(category: Category, name: String): Boolean { fun renameCategory(category: Category, name: String): Boolean {
// Do not allow duplicate categories. // Do not allow duplicate categories.
if (categoryExists(name)) { if (categoryExists(name, category.id)) {
controller.onCategoryExistsError() controller.onCategoryExistsError()
return false return false
} }
@ -131,8 +131,8 @@ class CategoryPresenter(
/** /**
* Returns true if a category with the given name already exists. * Returns true if a category with the given name already exists.
*/ */
private fun categoryExists(name: String): Boolean { private fun categoryExists(name: String, id: Int?): Boolean {
return categories.any { it.name.equals(name, true) } return categories.any { it.name.equals(name, true) && id != it.id }
} }
companion object { 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.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI 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.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
import kotlinx.android.synthetic.main.filter_bottom_sheet.* import kotlinx.android.synthetic.main.filter_bottom_sheet.*
@ -176,7 +176,7 @@ open class LibraryController(
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
view.applyWindowInsetsForController() view.applyWindowInsetsForRootController(activity!!.navigationView)
mangaPerRow = getColumnsPreferenceForCurrentOrientation().getOrDefault() mangaPerRow = getColumnsPreferenceForCurrentOrientation().getOrDefault()
if (!::presenter.isInitialized) if (!::presenter.isInitialized)
presenter = LibraryPresenter(this) presenter = LibraryPresenter(this)

View File

@ -69,7 +69,8 @@ class LibraryGridHolder(
var glide = GlideApp.with(adapter.recyclerView.context).load(item.manga) var glide = GlideApp.with(adapter.recyclerView.context).load(item.manga)
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.signature(ObjectKey(MangaImpl.getLastCoverFetch(id).toString())) .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) glide.into(cover_thumbnail)
} }
} }

View File

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

View File

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

View File

@ -6,34 +6,24 @@ import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.CompoundButton 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.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.view.setBottomEdge import eu.kanade.tachiyomi.util.view.setBottomEdge
import eu.kanade.tachiyomi.util.view.setEdgeToEdge import eu.kanade.tachiyomi.util.view.setEdgeToEdge
import eu.kanade.tachiyomi.util.view.visibleIf import eu.kanade.tachiyomi.util.view.visibleIf
import kotlinx.android.synthetic.main.chapter_sort_bottom_sheet.* 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) { (controller.activity!!, R.style.BottomSheetDialogTheme) {
val activity = controller.activity!! val activity = controller.activity!!
/**
* Preferences helper.
*/
private val preferences by injectLazy<PreferencesHelper>()
private var sheetBehavior: BottomSheetBehavior<*> private var sheetBehavior: BottomSheetBehavior<*>
private val presenter = controller.presenter
init { init {
// Use activity theme for this layout // 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) { val height = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.window.decorView.rootWindowInsets.systemWindowInsetBottom activity.window.decorView.rootWindowInsets.systemWindowInsetBottom
} else 0 } else 0
sheetBehavior.peekHeight = 220.dpToPx + height sheetBehavior.peekHeight = 380.dpToPx + height
sheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { sheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, progress: Float) { } override fun onSlide(bottomSheet: View, progress: Float) { }
@ -62,7 +52,6 @@ class ChaptersSortBottomSheet(private val controller: MangaChaptersController) :
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
sheetBehavior.skipCollapsed = true sheetBehavior.skipCollapsed = true
sheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
} }
/** /**
@ -71,87 +60,79 @@ class ChaptersSortBottomSheet(private val controller: MangaChaptersController) :
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
initGeneralPreferences() initGeneralPreferences()
setBottomEdge(show_bookmark, activity) setBottomEdge(hide_titles, activity)
close_button.setOnClickListener { close_button.setOnClickListener { dismiss() }
dismiss()
true
}
settings_scroll_view.viewTreeObserver.addOnGlobalLayoutListener { settings_scroll_view.viewTreeObserver.addOnGlobalLayoutListener {
val isScrollable = val isScrollable =
settings_scroll_view!!.height < bottom_sheet.height + settings_scroll_view!!.height < bottom_sheet.height +
settings_scroll_view.paddingTop + settings_scroll_view.paddingBottom settings_scroll_view.paddingTop + settings_scroll_view.paddingBottom
close_button.visibleIf(isScrollable) close_button.visibleIf(isScrollable)
} }
setOnDismissListener {
presenter.setFilters(
show_read.isChecked,
show_unread.isChecked,
show_download.isChecked,
show_bookmark.isChecked
)
}
} }
private fun initGeneralPreferences() { private fun initGeneralPreferences() {
show_read.isChecked = presenter.onlyRead()
show_read.isChecked = controller.presenter.onlyRead() show_unread.isChecked = presenter.onlyUnread()
show_unread.isChecked = controller.presenter.onlyUnread() show_download.isChecked = presenter.onlyDownloaded()
show_download.isChecked = controller.presenter.onlyDownloaded() show_bookmark.isChecked = presenter.onlyBookmarked()
show_bookmark.isChecked = controller.presenter.onlyBookmarked()
show_all.isChecked = !(show_read.isChecked || show_unread.isChecked || show_all.isChecked = !(show_read.isChecked || show_unread.isChecked ||
show_download.isChecked || show_bookmark.isChecked) show_download.isChecked || show_bookmark.isChecked)
if (controller.presenter.onlyRead()) sort_group.check(if (presenter.manga.sortDescending()) R.id.sort_newest else
//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) R.id.sort_oldest)
show_titles.isChecked = controller.presenter.manga.displayMode == Manga.DISPLAY_NAME hide_titles.isChecked = presenter.manga.displayMode != Manga.DISPLAY_NAME
sort_by_source.isChecked = controller.presenter.manga.sorting == Manga.SORTING_SOURCE 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 -> sort_group.setOnCheckedChangeListener { _, checkedId ->
controller.presenter.setSortOrder(checkedId == R.id.sort_oldest) presenter.setSortOrder(checkedId == R.id.sort_oldest)
dismiss() dismiss()
} }
/*sort_group.bindToPreference(preferences.libraryLayout()) { sort_method_group.setOnCheckedChangeListener { _, checkedId ->
controller.reattachAdapter() presenter.setSortMethod(checkedId == R.id.sort_by_source)
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()
}*/
} }
/** hide_titles.setOnCheckedChangeListener { _, isChecked ->
* Binds a checkbox or switch view with a boolean preference. presenter.hideTitle(isChecked)
*/
private fun CompoundButton.bindToPreference(pref: Preference<Boolean>, block: () -> Unit) {
isChecked = pref.getOrDefault()
setOnCheckedChangeListener { _, isChecked ->
pref.set(isChecked)
block()
}
} }
/** show_all.setOnCheckedChangeListener(::checkedFilter)
* Binds a radio group with a int preference. show_read.setOnCheckedChangeListener(::checkedFilter)
*/ show_unread.setOnCheckedChangeListener(::checkedFilter)
private fun RadioGroup.bindToPreference(pref: Preference<Int>, block: () -> Unit) { show_download.setOnCheckedChangeListener(::checkedFilter)
(getChildAt(pref.getOrDefault()) as RadioButton).isChecked = true show_bookmark.setOnCheckedChangeListener(::checkedFilter)
setOnCheckedChangeListener { _, checkedId -> }
val index = indexOfChild(findViewById(checkedId))
pref.set(index) private fun checkedFilter(checkBox: CompoundButton, isChecked: Boolean) {
block() 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.animation.ValueAnimator
import android.app.Activity 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.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@ -16,16 +21,19 @@ import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.PopupMenu 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.ColorUtils
import androidx.core.graphics.drawable.IconCompat
import androidx.palette.graphics.Palette import androidx.palette.graphics.Palette
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager 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.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import com.bumptech.glide.load.engine.DiskCacheStrategy 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.target.CustomTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import com.bumptech.glide.signature.ObjectKey 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl 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.download.model.Download
import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.controller.BaseController 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.base.controller.NoToolbarElevationController
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog 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.ChapterItem
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterMatHolder import eu.kanade.tachiyomi.ui.manga.chapter.ChapterMatHolder
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter 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.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate 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.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI 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.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.view.getText import eu.kanade.tachiyomi.util.view.getText
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative 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.*
import kotlinx.android.synthetic.main.big_manga_controller.swipe_refresh import kotlinx.android.synthetic.main.big_manga_controller.swipe_refresh
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.manga_info_controller.* import kotlinx.android.synthetic.main.manga_info_controller.*
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File
class MangaChaptersController : BaseController, class MangaChaptersController : BaseController,
ActionMode.Callback,
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemLongClickListener,
ChaptersAdapter.MangaHeaderInterface, ChaptersAdapter.MangaHeaderInterface,
@ -184,7 +201,7 @@ class MangaChaptersController : BaseController,
) )
colorAnimator?.cancel() colorAnimator?.cancel()
colorAnimator = ValueAnimator.ofObject( colorAnimator = ValueAnimator.ofObject(
ArgbEvaluator(), colorFrom, colorTo android.animation.ArgbEvaluator(), colorFrom, colorTo
) )
colorAnimator?.duration = 250 // milliseconds colorAnimator?.duration = 250 // milliseconds
colorAnimator?.addUpdateListener { animator -> colorAnimator?.addUpdateListener { animator ->
@ -226,7 +243,7 @@ class MangaChaptersController : BaseController,
colorBack colorBack
) )
else it?.getDarkMutedColor(colorBack)) ?: colorBack else it?.getDarkMutedColor(colorBack)) ?: colorBack
onCoverLoaded(backDropColor) coverColor = backDropColor
(recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder) (recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)
?.setBackDrop(backDropColor) ?.setBackDrop(backDropColor)
if (toolbarIsColored) { if (toolbarIsColored) {
@ -264,11 +281,15 @@ class MangaChaptersController : BaseController,
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type) super.onChangeStarted(handler, type)
if (type == ControllerChangeType.PUSH_ENTER || type == ControllerChangeType.POP_ENTER) { 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).appbar.setBackgroundColor(Color.TRANSPARENT)
(activity as MainActivity).toolbar.setBackgroundColor(Color.TRANSPARENT) (activity as MainActivity).toolbar.setBackgroundColor(Color.TRANSPARENT)
activity?.window?.statusBarColor = Color.TRANSPARENT activity?.window?.statusBarColor = Color.TRANSPARENT
} }
else if (type == ControllerChangeType.PUSH_EXIT || type == ControllerChangeType.POP_EXIT) { else if (type == ControllerChangeType.PUSH_EXIT || type == ControllerChangeType.POP_EXIT) {
if (router.backstack.lastOrNull()?.controller() is DialogController)
return
colorAnimator?.cancel() colorAnimator?.cancel()
(activity as MainActivity).toolbar.setBackgroundColor(activity?.getResourceColor( (activity as MainActivity).toolbar.setBackgroundColor(activity?.getResourceColor(
@ -295,6 +316,7 @@ class MangaChaptersController : BaseController,
listOf(ChapterItem(presenter.headerItem, presenter.manga)) + presenter.chapters listOf(ChapterItem(presenter.headerItem, presenter.manga)) + presenter.chapters
) )
} }
activity?.invalidateOptionsMenu()
} }
@ -305,8 +327,11 @@ class MangaChaptersController : BaseController,
presenter.fetchChaptersFromSource() presenter.fetchChaptersFromSource()
} }
adapter?.updateDataSet(listOf(presenter.headerItem) + chapters) adapter?.updateDataSet(listOf(presenter.headerItem) + chapters)
activity?.invalidateOptionsMenu()
} }
fun refreshAdapter() = adapter?.notifyDataSetChanged()
override fun onItemClick(view: View?, position: Int): Boolean { override fun onItemClick(view: View?, position: Int): Boolean {
val adapter = adapter ?: return false val adapter = adapter ?: return false
val chapter = adapter.getItem(position)?.chapter ?: return false val chapter = adapter.getItem(position)?.chapter ?: return false
@ -392,43 +417,231 @@ class MangaChaptersController : BaseController,
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { 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 { override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.big_manga_controller, container, false) 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 coverColor(): Int? = coverColor
override fun topCoverHeight(): Int = headerHeight override fun topCoverHeight(): Int = headerHeight
override fun nextChapter(): Chapter? = presenter.getNextUnreadChapter()
override fun mangaSource(): Source = presenter.source
override fun readNextChapter() { override fun readNextChapter() {
if (activity is SearchActivity && presenter.isLockedFromSearch) { if (activity is SearchActivity && presenter.isLockedFromSearch) {
SecureActivityDelegate.promptLockIfNeeded(activity) SecureActivityDelegate.promptLockIfNeeded(activity)
@ -451,17 +664,17 @@ class MangaChaptersController : BaseController,
} }
override fun downloadChapter(position: Int) { override fun downloadChapter(position: Int) {
val adapter = adapter ?: return val view = view ?: return
val chapter = adapter.getItem(position) ?: return val chapter = adapter?.getItem(position) ?: return
if (chapter.isHeader) return if (chapter.isHeader) return
if (chapter.status != Download.NOT_DOWNLOADED && chapter.status != Download.ERROR) { if (chapter.status != Download.NOT_DOWNLOADED && chapter.status != Download.ERROR) {
presenter.deleteChapters(listOf(chapter)) presenter.deleteChapters(listOf(chapter))
} }
else { else {
val isError = chapter.status == Download.ERROR if (chapter.status == Download.ERROR)
presenter.downloadChapters(listOf(chapter)) DownloadService.start(view.context)
if (isError) else
presenter.restartDownloads() downloadChapters(listOf(chapter))
} }
} }
@ -477,8 +690,6 @@ class MangaChaptersController : BaseController,
ChaptersSortBottomSheet(this).show() ChaptersSortBottomSheet(this).show()
} }
override fun chapterCount():Int = presenter.chapters.size
override fun favoriteManga(longPress: Boolean) { override fun favoriteManga(longPress: Boolean) {
if (presenter.isLockedFromSearch) { if (presenter.isLockedFromSearch) {
SecureActivityDelegate.promptLockIfNeeded(activity) SecureActivityDelegate.promptLockIfNeeded(activity)
@ -560,8 +771,29 @@ class MangaChaptersController : BaseController,
(activity as? MainActivity)?.setUndoSnackBar(snack, fab_favorite) (activity as? MainActivity)?.setUndoSnackBar(snack, fab_favorite)
} }
override fun mangaPresenter(): MangaPresenter = presenter
override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) { override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {
val manga = mangas.firstOrNull() ?: return val manga = mangas.firstOrNull() ?: return
presenter.moveMangaToCategories(manga, categories) 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.content.ContextCompat
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.signature.ObjectKey import com.bumptech.glide.signature.ObjectKey
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
@ -44,9 +45,9 @@ class MangaHeaderHolder(
manga_genres_tags.setOnTagClickListener { manga_genres_tags.setOnTagClickListener {
adapter.coverListener?.tagClicked(it) adapter.coverListener?.tagClicked(it)
} }
filter_button.setOnClickListener { filter_button.setOnClickListener { adapter.coverListener?.showChapterFilter() }
adapter.coverListener?.showChapterFilter() filters_text.setOnClickListener { adapter.coverListener?.showChapterFilter() }
} share_button.setOnClickListener { adapter.coverListener?.prepareToShareManga() }
favorite_button.setOnClickListener { favorite_button.setOnClickListener {
adapter.coverListener?.favoriteManga(false) adapter.coverListener?.favoriteManga(false)
} }
@ -66,6 +67,7 @@ class MangaHeaderHolder(
} }
override fun bind(item: ChapterItem, manga: Manga) { override fun bind(item: ChapterItem, manga: Manga) {
val presenter = adapter.coverListener?.mangaPresenter() ?: return
manga_full_title.text = manga.currentTitle() manga_full_title.text = manga.currentTitle()
if (manga.currentGenres().isNullOrBlank().not()) if (manga.currentGenres().isNullOrBlank().not())
@ -83,7 +85,8 @@ class MangaHeaderHolder(
.no_description) .no_description)
manga_summary.post { 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() more_button_group.gone()
} }
else else
@ -127,11 +130,11 @@ class MangaHeaderHolder(
itemView.context.getResourceColor(R.attr itemView.context.getResourceColor(R.attr
.colorOnSurface), 31)) .colorOnSurface), 31))
} }
true_backdrop.setBackgroundColor(adapter.coverListener?.coverColor() ?: true_backdrop.setBackgroundColor(adapter.coverListener.coverColor() ?:
itemView.context.getResourceColor(android.R.attr.colorBackground)) itemView.context.getResourceColor(android.R.attr.colorBackground))
with(start_reading_button) { with(start_reading_button) {
val nextChapter = adapter.coverListener?.nextChapter() val nextChapter = presenter.getNextUnreadChapter()
visibleIf(nextChapter != null && !item.isLocked) visibleIf(nextChapter != null && !item.isLocked)
if (nextChapter != null) { if (nextChapter != null) {
val number = adapter.decimalFormat.format(nextChapter.chapter_number.toDouble()) 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) chapters_title.text = itemView.resources.getQuantityString(R.plurals.chapters, count, count)
top_view.updateLayoutParams<ConstraintLayout.LayoutParams> { top_view.updateLayoutParams<ConstraintLayout.LayoutParams> {
height = adapter.coverListener?.topCoverHeight() ?: 0 height = adapter.coverListener.topCoverHeight() ?: 0
} }
manga_status.text = (itemView.context.getString( when (manga.status) { manga_status.text = (itemView.context.getString( when (manga.status) {
@ -160,7 +163,9 @@ class MangaHeaderHolder(
SManga.LICENSED -> R.string.licensed SManga.LICENSED -> R.string.licensed
else -> R.string.unknown_status 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 if (!manga.initialized) return
GlideApp.with(view.context).load(manga) GlideApp.with(view.context).load(manga)
@ -171,6 +176,7 @@ class MangaHeaderHolder(
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString())) .signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString()))
.centerCrop() .centerCrop()
.transition(DrawableTransitionOptions.withCrossFade())
.into(backdrop) .into(backdrop)
} }

View File

@ -1,5 +1,8 @@
package eu.kanade.tachiyomi.ui.manga 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.R
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper 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.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SChapter 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.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -30,6 +35,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
import java.util.Date import java.util.Date
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -89,22 +97,24 @@ class MangaPresenter(private val controller: MangaChaptersController,
val chapters = withContext(Dispatchers.IO) { val chapters = withContext(Dispatchers.IO) {
db.getChapters(manga).executeAsBlocking().map { it.toModel() } db.getChapters(manga).executeAsBlocking().map { it.toModel() }
} }
// Store the last emission
this.chapters = applyChapterFilters(chapters)
// Find downloaded chapters // Find downloaded chapters
setDownloadedChapters(chapters) setDownloadedChapters(chapters)
// Store the last emission
this.chapters = applyChapterFilters(chapters)
} }
private fun updateChapters(fetchedChapters: List<Chapter>? = null) { private fun updateChapters(fetchedChapters: List<Chapter>? = null) {
val chapters = (fetchedChapters ?: val chapters = (fetchedChapters ?:
db.getChapters(manga).executeAsBlocking()).map { it.toModel() } db.getChapters(manga).executeAsBlocking()).map { it.toModel() }
// Store the last emission
this.chapters = applyChapterFilters(chapters)
// Find downloaded chapters // Find downloaded chapters
setDownloadedChapters(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. * Sets the active display mode.
* @param mode the mode to set. * @param mode the mode to set.
*/ */
fun setDisplayMode(mode: Int) { fun hideTitle(hide: Boolean) {
manga.displayMode = mode manga.displayMode = if (hide) Manga.DISPLAY_NUMBER else Manga.DISPLAY_NAME
db.updateFlags(manga).executeAsBlocking() 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 } 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. * 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) { fun setSortOrder(desend: Boolean) {
manga.setChapterOrder(if (desend) Manga.SORT_ASC else Manga.SORT_DESC) 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() 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 { fun toggleFavorite(): Boolean {
@ -479,4 +532,122 @@ class MangaPresenter(private val controller: MangaChaptersController,
fetchChapters() 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 androidx.fragment.app.FragmentActivity
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R 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.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault 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.base.controller.BaseController
import eu.kanade.tachiyomi.ui.manga.MangaPresenter
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -60,13 +59,12 @@ class ChaptersAdapter(
interface MangaHeaderInterface { interface MangaHeaderInterface {
fun coverColor(): Int? fun coverColor(): Int?
fun nextChapter(): Chapter? fun mangaPresenter(): MangaPresenter
fun prepareToShareManga()
fun readNextChapter() fun readNextChapter()
fun downloadChapter(position: Int) fun downloadChapter(position: Int)
fun topCoverHeight(): Int fun topCoverHeight(): Int
fun chapterCount(): Int
fun tagClicked(text: String) fun tagClicked(text: String)
fun mangaSource(): Source
fun showChapterFilter() fun showChapterFilter()
fun favoriteManga(longPress: Boolean) 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.data.glide.GlideApp
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.base.controller.DialogController 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.lang.chop
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.android.synthetic.main.edit_manga_dialog.view.* import kotlinx.android.synthetic.main.edit_manga_dialog.view.*
@ -34,9 +35,9 @@ class EditMangaDialog : DialogController {
private var customCoverUri:Uri? = null private var customCoverUri:Uri? = null
private val infoController 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 { .apply {
putLong(KEY_MANGA, manga.id!!) putLong(KEY_MANGA, manga.id!!)
}) { }) {
@ -168,7 +169,6 @@ class EditMangaDialog : DialogController {
dialogView?.manga_author?.text.toString(), dialogView?.manga_artist?.text.toString(), dialogView?.manga_author?.text.toString(), dialogView?.manga_artist?.text.toString(),
customCoverUri, dialogView?.manga_description?.text.toString(), customCoverUri, dialogView?.manga_description?.text.toString(),
dialogView?.manga_genres_tags?.tags) dialogView?.manga_genres_tags?.tags)
infoController.updateTitle()
} }
private companion object { private companion object {

View File

@ -5,7 +5,6 @@ import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet import android.animation.AnimatorSet
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.app.Activity import android.app.Activity
import android.app.Dialog
import android.app.PendingIntent import android.app.PendingIntent
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
@ -15,7 +14,6 @@ import android.content.res.Configuration
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
@ -31,8 +29,6 @@ import androidx.transition.ChangeBounds
import androidx.transition.ChangeImageTransform import androidx.transition.ChangeImageTransform
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import androidx.transition.TransitionSet 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.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions 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.Source
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.ChooseShapeDialog
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
@ -236,7 +232,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { 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_open_in_web_view -> openInWebView()
R.id.action_share -> prepareToShareManga() R.id.action_share -> prepareToShareManga()
R.id.action_add_to_home_screen -> addToHomeScreen() 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 * Retrieves the bitmap of the shortcut with the requested shape and calls [createShortcut] when
* the resource is available. * the resource is available.
* *
* @param i The shape index to apply. Defaults to circle crop transformation. * @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 if (activity == null) return
GlideApp.with(activity!!) GlideApp.with(activity!!)
.asBitmap() .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.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import eu.kanade.tachiyomi.util.system.notificationManager 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 eu.kanade.tachiyomi.util.view.snack
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.recent_chapters_controller.* import kotlinx.android.synthetic.main.recent_chapters_controller.*
@ -82,7 +82,8 @@ class RecentChaptersController : NucleusController<RecentChaptersPresenter>(),
*/ */
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
view.applyWindowInsetsForController() view.applyWindowInsetsForRootController(activity!!.navigationView)
view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS) view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS)
// Init RecyclerView and adapter // Init RecyclerView and adapter
val layoutManager = LinearLayoutManager(view.context) 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.launchUI
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener 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 eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.recently_read_controller.* import kotlinx.android.synthetic.main.recently_read_controller.*
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -80,16 +81,7 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle),
*/ */
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
view.applyWindowInsetsForController() view.applyWindowInsetsForRootController(activity!!.navigationView)
/*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()
}*/
// Initialize adapter // Initialize adapter
adapter = RecentlyReadAdapter(this) adapter = RecentlyReadAdapter(this)
recycler.adapter = adapter 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.ui.base.controller.BaseController
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController 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.Observable
import rx.Subscription import rx.Subscription
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
@ -35,24 +37,12 @@ abstract class SettingsController : PreferenceController() {
untilDestroySubscriptions = CompositeSubscription() untilDestroySubscriptions = CompositeSubscription()
} }
val view = super.onCreateView(inflater, container, savedInstanceState) val view = super.onCreateView(inflater, container, savedInstanceState)
/*view.updateLayoutParams<FrameLayout.LayoutParams> { if (this is SettingsMainController)
val attrsArray = intArrayOf(android.R.attr.actionBarSize) view.applyWindowInsetsForRootController(activity!!.navigationView)
val array = view.context.obtainStyledAttributes(attrsArray) else {
topMargin = array.getDimensionPixelSize(0, 0)
array.recycle()
}*/
view.applyWindowInsetsForController() 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) listView.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
}
return view return view
} }

View File

@ -12,6 +12,7 @@ import android.graphics.drawable.GradientDrawable
import android.os.Build import android.os.Build
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.WindowInsets import android.view.WindowInsets
import android.widget.Button import android.widget.Button
import android.widget.FrameLayout import android.widget.FrameLayout
@ -207,6 +208,28 @@ fun View.applyWindowInsetsForController() {
requestApplyInsetsWhenAttached() 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() { fun View.requestApplyInsetsWhenAttached() {
if (isAttachedToWindow) { if (isAttachedToWindow) {

View File

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

View File

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

View File

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

View File

@ -165,7 +165,7 @@
app:icon="@drawable/ic_sync_black_24dp" /> app:icon="@drawable/ic_sync_black_24dp" />
<ImageView <ImageView
android:id="@+id/edit_button" android:id="@+id/share_button"
style="@style/Theme.Widget.CustomImageButton" style="@style/Theme.Widget.CustomImageButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -173,8 +173,8 @@
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:layout_marginEnd="6dp" android:layout_marginEnd="6dp"
android:padding="5dp" android:padding="5dp"
android:src="@drawable/ic_edit_white_24dp" android:contentDescription="@string/action_share"
app:layout_constraintBottom_toBottomOf="@id/chapters_title" /> android:src="@drawable/ic_share_white_24dp" />
</LinearLayout> </LinearLayout>
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
@ -312,16 +312,17 @@
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/chapters_title" android:id="@+id/chapters_title"
style="@style/TextAppearance.MaterialComponents.Headline6" style="@style/TextAppearance.MaterialComponents.Headline6"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="18dp" android:layout_marginTop="18dp"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:maxLines="1"
android:text="@string/chapters" android:text="@string/chapters"
android:textSize="17sp" android:textSize="17sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/filters_text"
app:layout_constraintStart_toStartOf="@id/manga_summary_label" app:layout_constraintStart_toStartOf="@id/manga_summary_label"
app:layout_constraintTop_toBottomOf="@id/start_reading_button" app:layout_constraintTop_toBottomOf="@id/start_reading_button" />
app:layout_constraintEnd_toStartOf="@id/filters_text"/>
<ImageView <ImageView
android:id="@+id/filter_button" android:id="@+id/filter_button"
@ -332,17 +333,22 @@
android:padding="5dp" android:padding="5dp"
android:src="@drawable/ic_filter_list_white_24dp" android:src="@drawable/ic_filter_list_white_24dp"
app:layout_constraintBottom_toBottomOf="@id/chapters_title" 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 <com.google.android.material.textview.MaterialTextView
android:id="@+id/filters_text" android:id="@+id/filters_text"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="6dp" android:layout_marginEnd="6dp"
android:maxLines="2"
android:padding="5dp" android:padding="5dp"
tools:text="Read" android:textColor="?android:textColorHint"
app:layout_constraintTop_toTopOf="@id/filter_button" android:textAlignment="textEnd"
app:layout_constraintBottom_toBottomOf="@id/filter_button" 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> </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_skip_manga">Don\'t migrate</string>
<string name="action_select_all">Select all</string> <string name="action_select_all">Select all</string>
<string name="action_mark_as_read">Mark as read</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_as_unread">Mark as unread</string>
<string name="action_mark_previous_as_read">Mark previous as read</string> <string name="action_mark_previous_as_read">Mark previous as read</string>
<string name="action_mark_multiple">Mark multiple</string> <string name="action_mark_multiple">Mark multiple</string>
@ -507,6 +508,7 @@
<item quantity="other">%1$d chapters</item> <item quantity="other">%1$d chapters</item>
</plurals> </plurals>
<string name="no_description">No description</string> <string name="no_description">No description</string>
<string name="mark_all_as_read_message">Mark all chapters as read?</string>
<!-- Manga chapters fragment --> <!-- Manga chapters fragment -->
<string name="start_reading">Start reading</string> <string name="start_reading">Start reading</string>
@ -710,5 +712,16 @@
<string name="action_auto">Auto</string> <string name="action_auto">Auto</string>
<string name="more">More</string> <string name="more">More</string>
<string name="less">Less</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> </resources>