mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 21:01:51 +01:00
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:
parent
52e6a7fc95
commit
ffb8a7bd17
@ -25,7 +25,7 @@ object Migrations {
|
||||
if (BuildConfig.INCLUDE_UPDATER && preferences.automaticUpdates()) {
|
||||
UpdaterJob.setupTask()
|
||||
}
|
||||
return false
|
||||
return BuildConfig.DEBUG
|
||||
}
|
||||
|
||||
if (oldVersion < 14) {
|
||||
|
@ -455,11 +455,12 @@ class LibraryUpdateService(
|
||||
.concatMap { manga ->
|
||||
val source = sourceManager.get(manga.source) as? HttpSource
|
||||
?: return@concatMap Observable.empty<LibraryManga>()
|
||||
|
||||
source.fetchMangaDetails(manga)
|
||||
.map { networkManga ->
|
||||
val thumbnailUrl = manga.thumbnail_url
|
||||
manga.copyFrom(networkManga)
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
if (thumbnailUrl != networkManga.thumbnail_url)
|
||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
||||
manga
|
||||
}
|
||||
|
@ -28,10 +28,11 @@ import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
|
||||
import eu.kanade.tachiyomi.ui.catalogue.latest.LatestUpdatesController
|
||||
import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController
|
||||
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
|
||||
import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.catalogue_main_controller.*
|
||||
import kotlinx.android.synthetic.main.main_activity.*
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
@ -102,7 +103,7 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
|
||||
*/
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
view.applyWindowInsetsForController()
|
||||
view.applyWindowInsetsForRootController(activity!!.navigationView)
|
||||
|
||||
adapter = CatalogueAdapter(this)
|
||||
|
||||
|
@ -53,8 +53,8 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
|
||||
title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorHint))
|
||||
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable
|
||||
.ic_add_white_24dp)
|
||||
edit_button.gone()
|
||||
image.gone()
|
||||
edit_button.setImageDrawable(null)
|
||||
edit_text.setText("")
|
||||
edit_text.hint = title.text
|
||||
}
|
||||
@ -62,7 +62,6 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
|
||||
title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorPrimary))
|
||||
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable
|
||||
.ic_reorder_grey_24dp)
|
||||
edit_button.visible()
|
||||
image.visible()
|
||||
edit_text.setText(title.text)
|
||||
}
|
||||
@ -94,12 +93,13 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
|
||||
else {
|
||||
if (!createCategory) {
|
||||
setDragHandleView(reorder)
|
||||
edit_button.setImageDrawable(ContextCompat.getDrawable(itemView.context, R.drawable.ic_edit_white_24dp))
|
||||
}
|
||||
else {
|
||||
edit_button.setImageDrawable(null)
|
||||
reorder.setOnTouchListener { _, _ -> true}
|
||||
}
|
||||
edit_text.clearFocus()
|
||||
edit_button.setImageDrawable(ContextCompat.getDrawable(itemView.context, R.drawable.ic_edit_white_24dp))
|
||||
edit_button.drawable.mutate().setTint(ContextCompat.getColor(itemView.context, R
|
||||
.color.gray_button))
|
||||
reorder.setImageDrawable(regularDrawable)
|
||||
|
@ -59,7 +59,7 @@ class CategoryPresenter(
|
||||
*/
|
||||
fun createCategory(name: String): Boolean {
|
||||
// Do not allow duplicate categories.
|
||||
if (categoryExists(name)) {
|
||||
if (categoryExists(name, null)) {
|
||||
controller.onCategoryExistsError()
|
||||
return false
|
||||
}
|
||||
@ -116,7 +116,7 @@ class CategoryPresenter(
|
||||
*/
|
||||
fun renameCategory(category: Category, name: String): Boolean {
|
||||
// Do not allow duplicate categories.
|
||||
if (categoryExists(name)) {
|
||||
if (categoryExists(name, category.id)) {
|
||||
controller.onCategoryExistsError()
|
||||
return false
|
||||
}
|
||||
@ -131,8 +131,8 @@ class CategoryPresenter(
|
||||
/**
|
||||
* Returns true if a category with the given name already exists.
|
||||
*/
|
||||
private fun categoryExists(name: String): Boolean {
|
||||
return categories.any { it.name.equals(name, true) }
|
||||
private fun categoryExists(name: String, id: Int?): Boolean {
|
||||
return categories.any { it.name.equals(name, true) && id != it.id }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -51,7 +51,7 @@ import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
|
||||
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
|
||||
import eu.kanade.tachiyomi.util.view.snack
|
||||
import kotlinx.android.synthetic.main.filter_bottom_sheet.*
|
||||
@ -176,7 +176,7 @@ open class LibraryController(
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
view.applyWindowInsetsForController()
|
||||
view.applyWindowInsetsForRootController(activity!!.navigationView)
|
||||
mangaPerRow = getColumnsPreferenceForCurrentOrientation().getOrDefault()
|
||||
if (!::presenter.isInitialized)
|
||||
presenter = LibraryPresenter(this)
|
||||
|
@ -69,7 +69,8 @@ class LibraryGridHolder(
|
||||
var glide = GlideApp.with(adapter.recyclerView.context).load(item.manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(id).toString()))
|
||||
glide = if (fixedSize) glide.centerCrop() else glide.override(cover_thumbnail.maxHeight)
|
||||
glide = if (fixedSize) glide.centerCrop().override(cover_thumbnail.maxHeight)
|
||||
else glide.override(cover_thumbnail.maxHeight)
|
||||
glide.into(cover_thumbnail)
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@ -21,6 +22,7 @@ import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
||||
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
||||
import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class LibraryItem(val manga: LibraryManga,
|
||||
@ -70,7 +72,9 @@ class LibraryItem(val manga: LibraryManga,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
cover_thumbnail.maxHeight = (parent.itemWidth / 3f * 3.7f).toInt()
|
||||
cover_thumbnail.minimumHeight = (parent.itemWidth / 3f * 3.7f).toInt()
|
||||
constraint_layout.minHeight = 0
|
||||
cover_thumbnail.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
cover_thumbnail.adjustViewBounds = false
|
||||
cover_thumbnail.layoutParams = FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
|
@ -1,5 +1,8 @@
|
||||
package eu.kanade.tachiyomi.ui.main
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.SearchManager
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
@ -7,7 +10,6 @@ import android.graphics.Color
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
@ -86,6 +88,10 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
private var extraViewForUndo:View? = null
|
||||
private var canDismissSnackBar = false
|
||||
|
||||
private var animationSet: AnimatorSet? = null
|
||||
|
||||
private var bottomNavHeight = 0
|
||||
|
||||
fun setUndoSnackBar(snackBar: Snackbar?, extraViewToCheck: View? = null) {
|
||||
this.snackBar = snackBar
|
||||
canDismissSnackBar = false
|
||||
@ -306,11 +312,12 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
}
|
||||
|
||||
navigationView.visibility = if (router.backstackSize > 1) View.GONE else View.VISIBLE
|
||||
navigationView.alpha = if (router.backstackSize > 1) 0f else 1f
|
||||
router.addChangeListener(object : ControllerChangeHandler.ControllerChangeListener {
|
||||
override fun onChangeStarted(to: Controller?, from: Controller?, isPush: Boolean,
|
||||
container: ViewGroup, handler: ControllerChangeHandler) {
|
||||
|
||||
syncActivityViewWithController(to, from)
|
||||
syncActivityViewWithController(to, from, isPush)
|
||||
}
|
||||
|
||||
override fun onChangeCompleted(to: Controller?, from: Controller?, isPush: Boolean,
|
||||
@ -360,7 +367,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
return super.startSupportActionMode(callback)
|
||||
}
|
||||
|
||||
override fun onSupportActionModeFinished(mode: androidx.appcompat.view.ActionMode) {
|
||||
/* override fun onSupportActionModeFinished(mode: androidx.appcompat.view.ActionMode) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) launchUI {
|
||||
val scale = Settings.Global.getFloat(
|
||||
contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f
|
||||
@ -371,7 +378,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
window?.statusBarColor = getResourceColor(android.R.attr.statusBarColor)
|
||||
}
|
||||
super.onSupportActionModeFinished(mode)
|
||||
}
|
||||
}*/
|
||||
|
||||
private fun setExtensionsBadge() {
|
||||
val updates = preferences.extensionUpdatesCount().getOrDefault()
|
||||
@ -542,7 +549,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
return super.dispatchTouchEvent(ev)
|
||||
}
|
||||
|
||||
protected open fun syncActivityViewWithController(to: Controller?, from: Controller? = null) {
|
||||
protected open fun syncActivityViewWithController(to: Controller?, from: Controller? = null,
|
||||
isPush: Boolean = false) {
|
||||
if (from is DialogController || to is DialogController) {
|
||||
return
|
||||
}
|
||||
@ -577,10 +585,37 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
appbar.enableElevation()
|
||||
}
|
||||
|
||||
if (to !is DialogController)
|
||||
if (to !is DialogController) {
|
||||
navigationView.visibility = if (router.backstackSize == 0 ||
|
||||
(router.backstackSize <= 1 && !isPush))
|
||||
View.VISIBLE else navigationView.visibility
|
||||
animationSet?.cancel()
|
||||
animationSet = AnimatorSet()
|
||||
val alphaAnimation = ValueAnimator.ofFloat(
|
||||
navigationView.alpha,
|
||||
if (router.backstackSize > 1) 0f else 1f
|
||||
)
|
||||
alphaAnimation.addUpdateListener { valueAnimator ->
|
||||
navigationView.alpha = valueAnimator.animatedValue as Float
|
||||
}
|
||||
alphaAnimation.addListener(object : Animator.AnimatorListener {
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
navigationView.visibility = if (router.backstackSize > 1) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator?) { }
|
||||
|
||||
override fun onAnimationRepeat(animation: Animator?) { }
|
||||
|
||||
override fun onAnimationStart(animation: Animator?) { }
|
||||
})
|
||||
alphaAnimation.duration = 200
|
||||
alphaAnimation.startDelay = 50
|
||||
animationSet?.playTogether(alphaAnimation)
|
||||
animationSet?.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun downloadStatusChanged(downloading: Boolean) {
|
||||
val downloadManager = Injekt.get<DownloadManager>()
|
||||
val hasQueue = downloading || downloadManager.hasQueue()
|
||||
|
@ -39,7 +39,8 @@ class SearchActivity: MainActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun syncActivityViewWithController(to: Controller?, from: Controller?) {
|
||||
override fun syncActivityViewWithController(to: Controller?, from: Controller?, isPush:
|
||||
Boolean) {
|
||||
if (from is DialogController || to is DialogController) {
|
||||
return
|
||||
}
|
||||
|
@ -6,34 +6,24 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.RadioButton
|
||||
import android.widget.RadioGroup
|
||||
import com.f2prateek.rx.preferences.Preference
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.view.setBottomEdge
|
||||
import eu.kanade.tachiyomi.util.view.setEdgeToEdge
|
||||
import eu.kanade.tachiyomi.util.view.visibleIf
|
||||
import kotlinx.android.synthetic.main.chapter_sort_bottom_sheet.*
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class ChaptersSortBottomSheet(private val controller: MangaChaptersController) : BottomSheetDialog
|
||||
class ChaptersSortBottomSheet(controller: MangaChaptersController) : BottomSheetDialog
|
||||
(controller.activity!!, R.style.BottomSheetDialogTheme) {
|
||||
|
||||
val activity = controller.activity!!
|
||||
|
||||
/**
|
||||
* Preferences helper.
|
||||
*/
|
||||
private val preferences by injectLazy<PreferencesHelper>()
|
||||
|
||||
private var sheetBehavior: BottomSheetBehavior<*>
|
||||
|
||||
private val presenter = controller.presenter
|
||||
|
||||
init {
|
||||
// Use activity theme for this layout
|
||||
@ -45,7 +35,7 @@ class ChaptersSortBottomSheet(private val controller: MangaChaptersController) :
|
||||
val height = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
activity.window.decorView.rootWindowInsets.systemWindowInsetBottom
|
||||
} else 0
|
||||
sheetBehavior.peekHeight = 220.dpToPx + height
|
||||
sheetBehavior.peekHeight = 380.dpToPx + height
|
||||
|
||||
sheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
|
||||
override fun onSlide(bottomSheet: View, progress: Float) { }
|
||||
@ -62,7 +52,6 @@ class ChaptersSortBottomSheet(private val controller: MangaChaptersController) :
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
sheetBehavior.skipCollapsed = true
|
||||
sheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,87 +60,79 @@ class ChaptersSortBottomSheet(private val controller: MangaChaptersController) :
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
initGeneralPreferences()
|
||||
setBottomEdge(show_bookmark, activity)
|
||||
close_button.setOnClickListener {
|
||||
dismiss()
|
||||
true
|
||||
}
|
||||
setBottomEdge(hide_titles, activity)
|
||||
close_button.setOnClickListener { dismiss() }
|
||||
settings_scroll_view.viewTreeObserver.addOnGlobalLayoutListener {
|
||||
val isScrollable =
|
||||
settings_scroll_view!!.height < bottom_sheet.height +
|
||||
settings_scroll_view.paddingTop + settings_scroll_view.paddingBottom
|
||||
close_button.visibleIf(isScrollable)
|
||||
}
|
||||
|
||||
setOnDismissListener {
|
||||
presenter.setFilters(
|
||||
show_read.isChecked,
|
||||
show_unread.isChecked,
|
||||
show_download.isChecked,
|
||||
show_bookmark.isChecked
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun initGeneralPreferences() {
|
||||
|
||||
show_read.isChecked = controller.presenter.onlyRead()
|
||||
show_unread.isChecked = controller.presenter.onlyUnread()
|
||||
show_download.isChecked = controller.presenter.onlyDownloaded()
|
||||
show_bookmark.isChecked = controller.presenter.onlyBookmarked()
|
||||
show_read.isChecked = presenter.onlyRead()
|
||||
show_unread.isChecked = presenter.onlyUnread()
|
||||
show_download.isChecked = presenter.onlyDownloaded()
|
||||
show_bookmark.isChecked = presenter.onlyBookmarked()
|
||||
|
||||
show_all.isChecked = !(show_read.isChecked || show_unread.isChecked ||
|
||||
show_download.isChecked || show_bookmark.isChecked)
|
||||
|
||||
if (controller.presenter.onlyRead())
|
||||
//Disable unread filter option if read filter is enabled.
|
||||
show_unread.isEnabled = false
|
||||
if (controller.presenter.onlyUnread())
|
||||
//Disable read filter option if unread filter is enabled.
|
||||
show_read.isEnabled = false
|
||||
|
||||
sort_group.check(if (controller.presenter.manga.sortDescending()) R.id.sort_newest else
|
||||
sort_group.check(if (presenter.manga.sortDescending()) R.id.sort_newest else
|
||||
R.id.sort_oldest)
|
||||
|
||||
show_titles.isChecked = controller.presenter.manga.displayMode == Manga.DISPLAY_NAME
|
||||
sort_by_source.isChecked = controller.presenter.manga.sorting == Manga.SORTING_SOURCE
|
||||
hide_titles.isChecked = presenter.manga.displayMode != Manga.DISPLAY_NAME
|
||||
sort_method_group.check(if (presenter.manga.sorting == Manga.SORTING_SOURCE) R.id.sort_by_source else
|
||||
R.id.sort_by_number)
|
||||
|
||||
sort_group.setOnCheckedChangeListener { _, checkedId ->
|
||||
controller.presenter.setSortOrder(checkedId == R.id.sort_oldest)
|
||||
presenter.setSortOrder(checkedId == R.id.sort_oldest)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
/*sort_group.bindToPreference(preferences.libraryLayout()) {
|
||||
controller.reattachAdapter()
|
||||
if (sheetBehavior.state == BottomSheetBehavior.STATE_COLLAPSED)
|
||||
dismiss()
|
||||
}
|
||||
uniform_grid.bindToPreference(preferences.uniformGrid()) {
|
||||
controller.reattachAdapter()
|
||||
}
|
||||
grid_size_toggle_group.bindToPreference(preferences.gridSize()) {
|
||||
controller.reattachAdapter()
|
||||
}
|
||||
download_badge.bindToPreference(preferences.downloadBadge()) {
|
||||
controller.presenter.requestDownloadBadgesUpdate()
|
||||
}
|
||||
unread_badge_group.bindToPreference(preferences.unreadBadgeType()) {
|
||||
controller.presenter.requestUnreadBadgesUpdate()
|
||||
}*/
|
||||
sort_method_group.setOnCheckedChangeListener { _, checkedId ->
|
||||
presenter.setSortMethod(checkedId == R.id.sort_by_source)
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a checkbox or switch view with a boolean preference.
|
||||
*/
|
||||
private fun CompoundButton.bindToPreference(pref: Preference<Boolean>, block: () -> Unit) {
|
||||
isChecked = pref.getOrDefault()
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
pref.set(isChecked)
|
||||
block()
|
||||
}
|
||||
hide_titles.setOnCheckedChangeListener { _, isChecked ->
|
||||
presenter.hideTitle(isChecked)
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a radio group with a int preference.
|
||||
*/
|
||||
private fun RadioGroup.bindToPreference(pref: Preference<Int>, block: () -> Unit) {
|
||||
(getChildAt(pref.getOrDefault()) as RadioButton).isChecked = true
|
||||
setOnCheckedChangeListener { _, checkedId ->
|
||||
val index = indexOfChild(findViewById(checkedId))
|
||||
pref.set(index)
|
||||
block()
|
||||
show_all.setOnCheckedChangeListener(::checkedFilter)
|
||||
show_read.setOnCheckedChangeListener(::checkedFilter)
|
||||
show_unread.setOnCheckedChangeListener(::checkedFilter)
|
||||
show_download.setOnCheckedChangeListener(::checkedFilter)
|
||||
show_bookmark.setOnCheckedChangeListener(::checkedFilter)
|
||||
}
|
||||
|
||||
private fun checkedFilter(checkBox: CompoundButton, isChecked: Boolean) {
|
||||
if (isChecked) {
|
||||
if (show_all == checkBox) {
|
||||
show_read.isChecked = false
|
||||
show_unread.isChecked = false
|
||||
show_download.isChecked = false
|
||||
show_bookmark.isChecked = false
|
||||
}
|
||||
else {
|
||||
show_all.isChecked = false
|
||||
if (show_read == checkBox) show_unread.isChecked = false
|
||||
else if (show_unread == checkBox) show_read.isChecked = false
|
||||
}
|
||||
}
|
||||
else if (!show_read.isChecked && !show_unread.isChecked &&
|
||||
!show_download.isChecked && !show_bookmark.isChecked) {
|
||||
show_all.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -2,8 +2,13 @@ package eu.kanade.tachiyomi.ui.manga
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
@ -16,16 +21,19 @@ import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.palette.graphics.Palette
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.vectordrawable.graphics.drawable.ArgbEvaluator
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
@ -38,12 +46,15 @@ import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BaseController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
|
||||
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
|
||||
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
||||
@ -54,25 +65,31 @@ import eu.kanade.tachiyomi.ui.manga.MangaController.Companion.FROM_CATALOGUE_EXT
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterMatHolder
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter
|
||||
import eu.kanade.tachiyomi.ui.manga.info.EditMangaDialog
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets
|
||||
import eu.kanade.tachiyomi.util.view.getText
|
||||
import eu.kanade.tachiyomi.util.view.snack
|
||||
import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
||||
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
|
||||
import jp.wasabeef.glide.transformations.CropSquareTransformation
|
||||
import jp.wasabeef.glide.transformations.MaskTransformation
|
||||
import kotlinx.android.synthetic.main.big_manga_controller.*
|
||||
import kotlinx.android.synthetic.main.big_manga_controller.swipe_refresh
|
||||
import kotlinx.android.synthetic.main.main_activity.*
|
||||
import kotlinx.android.synthetic.main.manga_info_controller.*
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
|
||||
class MangaChaptersController : BaseController,
|
||||
ActionMode.Callback,
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
FlexibleAdapter.OnItemLongClickListener,
|
||||
ChaptersAdapter.MangaHeaderInterface,
|
||||
@ -184,7 +201,7 @@ class MangaChaptersController : BaseController,
|
||||
)
|
||||
colorAnimator?.cancel()
|
||||
colorAnimator = ValueAnimator.ofObject(
|
||||
ArgbEvaluator(), colorFrom, colorTo
|
||||
android.animation.ArgbEvaluator(), colorFrom, colorTo
|
||||
)
|
||||
colorAnimator?.duration = 250 // milliseconds
|
||||
colorAnimator?.addUpdateListener { animator ->
|
||||
@ -226,7 +243,7 @@ class MangaChaptersController : BaseController,
|
||||
colorBack
|
||||
)
|
||||
else it?.getDarkMutedColor(colorBack)) ?: colorBack
|
||||
onCoverLoaded(backDropColor)
|
||||
coverColor = backDropColor
|
||||
(recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)
|
||||
?.setBackDrop(backDropColor)
|
||||
if (toolbarIsColored) {
|
||||
@ -264,11 +281,15 @@ class MangaChaptersController : BaseController,
|
||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||
super.onChangeStarted(handler, type)
|
||||
if (type == ControllerChangeType.PUSH_ENTER || type == ControllerChangeType.POP_ENTER) {
|
||||
if (type == ControllerChangeType.POP_ENTER)
|
||||
return
|
||||
(activity as MainActivity).appbar.setBackgroundColor(Color.TRANSPARENT)
|
||||
(activity as MainActivity).toolbar.setBackgroundColor(Color.TRANSPARENT)
|
||||
activity?.window?.statusBarColor = Color.TRANSPARENT
|
||||
}
|
||||
else if (type == ControllerChangeType.PUSH_EXIT || type == ControllerChangeType.POP_EXIT) {
|
||||
if (router.backstack.lastOrNull()?.controller() is DialogController)
|
||||
return
|
||||
colorAnimator?.cancel()
|
||||
|
||||
(activity as MainActivity).toolbar.setBackgroundColor(activity?.getResourceColor(
|
||||
@ -295,6 +316,7 @@ class MangaChaptersController : BaseController,
|
||||
listOf(ChapterItem(presenter.headerItem, presenter.manga)) + presenter.chapters
|
||||
)
|
||||
}
|
||||
activity?.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
|
||||
@ -305,7 +327,10 @@ class MangaChaptersController : BaseController,
|
||||
presenter.fetchChaptersFromSource()
|
||||
}
|
||||
adapter?.updateDataSet(listOf(presenter.headerItem) + chapters)
|
||||
}
|
||||
activity?.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
fun refreshAdapter() = adapter?.notifyDataSetChanged()
|
||||
|
||||
override fun onItemClick(view: View?, position: Int): Boolean {
|
||||
val adapter = adapter ?: return false
|
||||
@ -392,43 +417,231 @@ class MangaChaptersController : BaseController,
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.chapters, menu)
|
||||
inflater.inflate(R.menu.manga_details, menu)
|
||||
val editItem = menu.findItem(R.id.action_edit)
|
||||
editItem.isVisible = presenter.manga.favorite && !presenter.isLockedFromSearch
|
||||
menu.findItem(R.id.action_download).isVisible = !presenter.isLockedFromSearch
|
||||
menu.findItem(R.id.action_mark_all_as_read).isVisible =
|
||||
presenter.getNextUnreadChapter() != null && !presenter.isLockedFromSearch
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_edit -> EditMangaDialog(this, presenter.manga).showDialog(router)
|
||||
R.id.action_open_in_web_view -> openInWebView()
|
||||
R.id.action_share -> prepareToShareManga()
|
||||
R.id.action_add_to_home_screen -> addToHomeScreen()
|
||||
R.id.action_mark_all_as_read -> {
|
||||
MaterialDialog(view!!.context)
|
||||
.message(R.string.mark_all_as_read_message)
|
||||
.positiveButton(R.string.action_mark_as_read) {
|
||||
markAsRead(presenter.chapters)
|
||||
}
|
||||
.negativeButton(android.R.string.cancel)
|
||||
.show()
|
||||
}
|
||||
R.id.download_next, R.id.download_next_5, R.id.download_next_10,
|
||||
R.id.download_custom, R.id.download_unread, R.id.download_all
|
||||
-> downloadChapters(item.itemId)
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to run Intent with [Intent.ACTION_SEND], which show share dialog.
|
||||
*/
|
||||
override fun prepareToShareManga() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && manga_cover.drawable != null)
|
||||
GlideApp.with(activity!!).asBitmap().load(presenter.manga).into(object :
|
||||
CustomTarget<Bitmap>() {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
presenter.shareManga(resource)
|
||||
}
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
shareManga()
|
||||
}
|
||||
})
|
||||
else shareManga()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to run Intent with [Intent.ACTION_SEND], which show share dialog.
|
||||
*/
|
||||
fun shareManga(cover: File? = null) {
|
||||
val context = view?.context ?: return
|
||||
|
||||
val source = presenter.source as? HttpSource ?: return
|
||||
val stream = cover?.getUriCompat(context)
|
||||
try {
|
||||
val url = source.mangaDetailsRequest(presenter.manga).url.toString()
|
||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = "text/*"
|
||||
putExtra(Intent.EXTRA_TEXT, url)
|
||||
putExtra(Intent.EXTRA_TITLE, presenter.manga.currentTitle())
|
||||
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
if (stream != null) {
|
||||
clipData = ClipData.newRawUri(null, stream)
|
||||
}
|
||||
}
|
||||
startActivity(Intent.createChooser(intent, context.getString(R.string.action_share)))
|
||||
} catch (e: Exception) {
|
||||
context.toast(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openInWebView() {
|
||||
val source = presenter.source as? HttpSource ?: return
|
||||
|
||||
val url = try {
|
||||
source.mangaDetailsRequest(presenter.manga).url.toString()
|
||||
} catch (e: Exception) {
|
||||
return
|
||||
}
|
||||
|
||||
val activity = activity ?: return
|
||||
val intent = WebViewActivity.newIntent(activity.applicationContext, source.id, url, presenter.manga
|
||||
.originalTitle())
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun downloadChapters(choice: Int) {
|
||||
val chaptersToDownload = when (choice) {
|
||||
R.id.download_next -> presenter.getUnreadChaptersSorted().take(1)
|
||||
R.id.download_next_5 -> presenter.getUnreadChaptersSorted().take(5)
|
||||
R.id.download_next_10 -> presenter.getUnreadChaptersSorted().take(10)
|
||||
R.id.download_custom -> {
|
||||
showCustomDownloadDialog()
|
||||
return
|
||||
}
|
||||
R.id.download_unread -> presenter.chapters.filter { !it.read }
|
||||
R.id.download_all -> presenter.chapters
|
||||
else -> emptyList()
|
||||
}
|
||||
if (chaptersToDownload.isNotEmpty()) {
|
||||
downloadChapters(chaptersToDownload)
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadChapters(chapters: List<ChapterItem>) {
|
||||
val view = view
|
||||
presenter.downloadChapters(chapters)
|
||||
if (view != null && !presenter.manga.favorite && (snack == null ||
|
||||
snack?.getText() != view.context.getString(R.string.snack_add_to_library))) {
|
||||
snack = view.snack(view.context.getString(R.string.snack_add_to_library), Snackbar.LENGTH_INDEFINITE) {
|
||||
setAction(R.string.action_add) {
|
||||
presenter.setFavorite(true)
|
||||
}
|
||||
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
|
||||
super.onDismissed(transientBottomBar, event)
|
||||
if (snack == transientBottomBar) snack = null
|
||||
}
|
||||
})
|
||||
}
|
||||
(activity as? MainActivity)?.setUndoSnackBar(snack)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a shortcut of the manga to the home screen
|
||||
*/
|
||||
private fun addToHomeScreen() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// TODO are transformations really unsupported or is it just the Pixel Launcher?
|
||||
createShortcutForShape()
|
||||
} else {
|
||||
ChooseShapeDialog(this).showDialog(router)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the bitmap of the shortcut with the requested shape and calls [createShortcut] when
|
||||
* the resource is available.
|
||||
*
|
||||
* @param i The shape index to apply. Defaults to circle crop transformation.
|
||||
*/
|
||||
fun createShortcutForShape(i: Int = 0) {
|
||||
if (activity == null) return
|
||||
GlideApp.with(activity!!)
|
||||
.asBitmap()
|
||||
.load(presenter.manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.apply {
|
||||
when (i) {
|
||||
0 -> circleCrop()
|
||||
1 -> transform(RoundedCorners(5))
|
||||
2 -> transform(CropSquareTransformation())
|
||||
3 -> centerCrop().transform(MaskTransformation(R.drawable.mask_star))
|
||||
}
|
||||
}
|
||||
.into(object : CustomTarget<Bitmap>(128, 128) {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
createShortcut(resource)
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) { }
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
activity?.toast(R.string.icon_creation_fail)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create shortcut using ShortcutManager.
|
||||
*
|
||||
* @param icon The image of the shortcut.
|
||||
*/
|
||||
private fun createShortcut(icon: Bitmap) {
|
||||
val activity = activity ?: return
|
||||
|
||||
// Create the shortcut intent.
|
||||
val shortcutIntent = activity.intent
|
||||
.setAction(MainActivity.SHORTCUT_MANGA)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
.putExtra(MangaController.MANGA_EXTRA, presenter.manga.id)
|
||||
|
||||
// Check if shortcut placement is supported
|
||||
if (ShortcutManagerCompat.isRequestPinShortcutSupported(activity)) {
|
||||
val shortcutId = "manga-shortcut-${presenter.manga.originalTitle()}-${presenter.source.name}"
|
||||
|
||||
// Create shortcut info
|
||||
val shortcutInfo = ShortcutInfoCompat.Builder(activity, shortcutId)
|
||||
.setShortLabel(presenter.manga.currentTitle())
|
||||
.setIcon(IconCompat.createWithBitmap(icon))
|
||||
.setIntent(shortcutIntent)
|
||||
.build()
|
||||
|
||||
val successCallback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Create the CallbackIntent.
|
||||
val intent = ShortcutManagerCompat.createShortcutResultIntent(activity, shortcutInfo)
|
||||
|
||||
// Configure the intent so that the broadcast receiver gets the callback successfully.
|
||||
PendingIntent.getBroadcast(activity, 0, intent, 0)
|
||||
} else {
|
||||
NotificationReceiver.shortcutCreatedBroadcast(activity)
|
||||
}
|
||||
|
||||
// Request shortcut.
|
||||
ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfo,
|
||||
successCallback.intentSender)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCustomDownloadDialog() {
|
||||
// DownloadCustomChaptersDialog(this, presenter.chapters.size).showDialog(router)
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
return inflater.inflate(R.layout.big_manga_controller, container, false)
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
fun onCoverLoaded(color: Int) {
|
||||
if (view == null) return
|
||||
coverColor = color
|
||||
//activity?.window?.statusBarColor = color
|
||||
}
|
||||
|
||||
override fun coverColor(): Int? = coverColor
|
||||
override fun topCoverHeight(): Int = headerHeight
|
||||
|
||||
override fun nextChapter(): Chapter? = presenter.getNextUnreadChapter()
|
||||
override fun mangaSource(): Source = presenter.source
|
||||
|
||||
override fun readNextChapter() {
|
||||
if (activity is SearchActivity && presenter.isLockedFromSearch) {
|
||||
SecureActivityDelegate.promptLockIfNeeded(activity)
|
||||
@ -451,17 +664,17 @@ class MangaChaptersController : BaseController,
|
||||
}
|
||||
|
||||
override fun downloadChapter(position: Int) {
|
||||
val adapter = adapter ?: return
|
||||
val chapter = adapter.getItem(position) ?: return
|
||||
val view = view ?: return
|
||||
val chapter = adapter?.getItem(position) ?: return
|
||||
if (chapter.isHeader) return
|
||||
if (chapter.status != Download.NOT_DOWNLOADED && chapter.status != Download.ERROR) {
|
||||
presenter.deleteChapters(listOf(chapter))
|
||||
}
|
||||
else {
|
||||
val isError = chapter.status == Download.ERROR
|
||||
presenter.downloadChapters(listOf(chapter))
|
||||
if (isError)
|
||||
presenter.restartDownloads()
|
||||
if (chapter.status == Download.ERROR)
|
||||
DownloadService.start(view.context)
|
||||
else
|
||||
downloadChapters(listOf(chapter))
|
||||
}
|
||||
}
|
||||
|
||||
@ -477,8 +690,6 @@ class MangaChaptersController : BaseController,
|
||||
ChaptersSortBottomSheet(this).show()
|
||||
}
|
||||
|
||||
override fun chapterCount():Int = presenter.chapters.size
|
||||
|
||||
override fun favoriteManga(longPress: Boolean) {
|
||||
if (presenter.isLockedFromSearch) {
|
||||
SecureActivityDelegate.promptLockIfNeeded(activity)
|
||||
@ -560,8 +771,29 @@ class MangaChaptersController : BaseController,
|
||||
(activity as? MainActivity)?.setUndoSnackBar(snack, fab_favorite)
|
||||
}
|
||||
|
||||
override fun mangaPresenter(): MangaPresenter = presenter
|
||||
|
||||
override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {
|
||||
val manga = mangas.firstOrNull() ?: return
|
||||
presenter.moveMangaToCategories(manga, categories)
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a string to clipboard
|
||||
*
|
||||
* @param label Label to show to the user describing the content
|
||||
* @param content the actual text to copy to the board
|
||||
*/
|
||||
private fun copyToClipboard(label: String, content: String, resId: Int) {
|
||||
if (content.isBlank()) return
|
||||
|
||||
val activity = activity ?: return
|
||||
val view = view ?: return
|
||||
|
||||
val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText(label, content))
|
||||
|
||||
snack = view.snack(view.context.getString(R.string.copied_to_clipboard, view.context
|
||||
.getString(resId)))
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
@ -44,9 +45,9 @@ class MangaHeaderHolder(
|
||||
manga_genres_tags.setOnTagClickListener {
|
||||
adapter.coverListener?.tagClicked(it)
|
||||
}
|
||||
filter_button.setOnClickListener {
|
||||
adapter.coverListener?.showChapterFilter()
|
||||
}
|
||||
filter_button.setOnClickListener { adapter.coverListener?.showChapterFilter() }
|
||||
filters_text.setOnClickListener { adapter.coverListener?.showChapterFilter() }
|
||||
share_button.setOnClickListener { adapter.coverListener?.prepareToShareManga() }
|
||||
favorite_button.setOnClickListener {
|
||||
adapter.coverListener?.favoriteManga(false)
|
||||
}
|
||||
@ -66,6 +67,7 @@ class MangaHeaderHolder(
|
||||
}
|
||||
|
||||
override fun bind(item: ChapterItem, manga: Manga) {
|
||||
val presenter = adapter.coverListener?.mangaPresenter() ?: return
|
||||
manga_full_title.text = manga.currentTitle()
|
||||
|
||||
if (manga.currentGenres().isNullOrBlank().not())
|
||||
@ -83,7 +85,8 @@ class MangaHeaderHolder(
|
||||
.no_description)
|
||||
|
||||
manga_summary.post {
|
||||
if (manga_summary.lineCount < 3 && manga.currentGenres().isNullOrBlank()) {
|
||||
if ((manga_summary.lineCount < 3 && manga.currentGenres().isNullOrBlank())
|
||||
|| less_button.visibility == View.VISIBLE) {
|
||||
more_button_group.gone()
|
||||
}
|
||||
else
|
||||
@ -127,11 +130,11 @@ class MangaHeaderHolder(
|
||||
itemView.context.getResourceColor(R.attr
|
||||
.colorOnSurface), 31))
|
||||
}
|
||||
true_backdrop.setBackgroundColor(adapter.coverListener?.coverColor() ?:
|
||||
true_backdrop.setBackgroundColor(adapter.coverListener.coverColor() ?:
|
||||
itemView.context.getResourceColor(android.R.attr.colorBackground))
|
||||
|
||||
with(start_reading_button) {
|
||||
val nextChapter = adapter.coverListener?.nextChapter()
|
||||
val nextChapter = presenter.getNextUnreadChapter()
|
||||
visibleIf(nextChapter != null && !item.isLocked)
|
||||
if (nextChapter != null) {
|
||||
val number = adapter.decimalFormat.format(nextChapter.chapter_number.toDouble())
|
||||
@ -147,11 +150,11 @@ class MangaHeaderHolder(
|
||||
}
|
||||
}
|
||||
|
||||
val count = adapter.coverListener?.chapterCount() ?: 0
|
||||
val count = presenter.chapters.size
|
||||
chapters_title.text = itemView.resources.getQuantityString(R.plurals.chapters, count, count)
|
||||
|
||||
top_view.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
height = adapter.coverListener?.topCoverHeight() ?: 0
|
||||
height = adapter.coverListener.topCoverHeight() ?: 0
|
||||
}
|
||||
|
||||
manga_status.text = (itemView.context.getString( when (manga.status) {
|
||||
@ -160,7 +163,9 @@ class MangaHeaderHolder(
|
||||
SManga.LICENSED -> R.string.licensed
|
||||
else -> R.string.unknown_status
|
||||
}))
|
||||
manga_source.text = adapter.coverListener?.mangaSource()?.toString()
|
||||
manga_source.text = presenter.source.toString()
|
||||
|
||||
filters_text.text = presenter.currentFilters()
|
||||
|
||||
if (!manga.initialized) return
|
||||
GlideApp.with(view.context).load(manga)
|
||||
@ -171,6 +176,7 @@ class MangaHeaderHolder(
|
||||
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString()))
|
||||
.centerCrop()
|
||||
.transition(DrawableTransitionOptions.withCrossFade())
|
||||
.into(backdrop)
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
package eu.kanade.tachiyomi.ui.manga
|
||||
|
||||
import android.app.Application
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
@ -18,9 +21,11 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -30,6 +35,9 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.Date
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@ -89,22 +97,24 @@ class MangaPresenter(private val controller: MangaChaptersController,
|
||||
val chapters = withContext(Dispatchers.IO) {
|
||||
db.getChapters(manga).executeAsBlocking().map { it.toModel() }
|
||||
}
|
||||
// Store the last emission
|
||||
this.chapters = applyChapterFilters(chapters)
|
||||
|
||||
// Find downloaded chapters
|
||||
setDownloadedChapters(chapters)
|
||||
|
||||
// Store the last emission
|
||||
this.chapters = applyChapterFilters(chapters)
|
||||
|
||||
}
|
||||
|
||||
private fun updateChapters(fetchedChapters: List<Chapter>? = null) {
|
||||
val chapters = (fetchedChapters ?:
|
||||
db.getChapters(manga).executeAsBlocking()).map { it.toModel() }
|
||||
|
||||
// Store the last emission
|
||||
this.chapters = applyChapterFilters(chapters)
|
||||
|
||||
// Find downloaded chapters
|
||||
setDownloadedChapters(chapters)
|
||||
|
||||
// Store the last emission
|
||||
this.chapters = applyChapterFilters(chapters)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -148,9 +158,10 @@ class MangaPresenter(private val controller: MangaChaptersController,
|
||||
* Sets the active display mode.
|
||||
* @param mode the mode to set.
|
||||
*/
|
||||
fun setDisplayMode(mode: Int) {
|
||||
manga.displayMode = mode
|
||||
fun hideTitle(hide: Boolean) {
|
||||
manga.displayMode = if (hide) Manga.DISPLAY_NUMBER else Manga.DISPLAY_NAME
|
||||
db.updateFlags(manga).executeAsBlocking()
|
||||
controller.refreshAdapter()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,6 +251,11 @@ class MangaPresenter(private val controller: MangaChaptersController,
|
||||
return chapters.sortedByDescending { it.source_order }.find { !it.read }
|
||||
}
|
||||
|
||||
fun getUnreadChaptersSorted() = chapters
|
||||
.filter { !it.read && it.status == Download.NOT_DOWNLOADED }
|
||||
.distinctBy { it.name }
|
||||
.sortedByDescending { it.source_order }
|
||||
|
||||
/**
|
||||
* Returns the next unread chapter or null if everything is read.
|
||||
*/
|
||||
@ -404,13 +420,50 @@ class MangaPresenter(private val controller: MangaChaptersController,
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the sorting and requests an UI update.
|
||||
* Sets the sorting order and requests an UI update.
|
||||
*/
|
||||
fun setSortOrder(desend: Boolean) {
|
||||
manga.setChapterOrder(if (desend) Manga.SORT_ASC else Manga.SORT_DESC)
|
||||
db.updateFlags(manga).executeAsBlocking()
|
||||
asyncUpdateMangaAndChapters()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sorting method and requests an UI update.
|
||||
*/
|
||||
fun setSortMethod(bySource: Boolean) {
|
||||
manga.sorting = if (bySource) Manga.SORTING_SOURCE else Manga.SORTING_NUMBER
|
||||
asyncUpdateMangaAndChapters()
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all filters and requests an UI update.
|
||||
*/
|
||||
fun setFilters(read: Boolean, unread: Boolean, downloaded: Boolean, bookmarked: Boolean) {
|
||||
manga.readFilter = when {
|
||||
read -> Manga.SHOW_READ
|
||||
unread -> Manga.SHOW_UNREAD
|
||||
else -> Manga.SHOW_ALL
|
||||
}
|
||||
manga.downloadedFilter = if (downloaded) Manga.SHOW_DOWNLOADED else Manga.SHOW_ALL
|
||||
manga.bookmarkedFilter = if (bookmarked) Manga.SHOW_BOOKMARKED else Manga.SHOW_ALL
|
||||
asyncUpdateMangaAndChapters()
|
||||
}
|
||||
|
||||
private fun asyncUpdateMangaAndChapters() {
|
||||
launch {
|
||||
withContext(Dispatchers.IO) { db.updateFlags(manga).executeAsBlocking() }
|
||||
updateChapters()
|
||||
controller.updateChapters(chapters)
|
||||
withContext(Dispatchers.Main) { controller.updateChapters(chapters) }
|
||||
}
|
||||
}
|
||||
|
||||
fun currentFilters(): String {
|
||||
val filtersId = mutableListOf<Int?>()
|
||||
filtersId.add(if (onlyRead()) R.string.action_filter_read else null)
|
||||
filtersId.add(if (onlyUnread()) R.string.action_filter_unread else null)
|
||||
filtersId.add(if (onlyDownloaded()) R.string.action_filter_downloaded else null)
|
||||
filtersId.add(if (onlyBookmarked()) R.string.action_filter_bookmarked else null)
|
||||
return filtersId.filterNotNull().joinToString(", ") { preferences.context.getString(it) }
|
||||
}
|
||||
|
||||
fun toggleFavorite(): Boolean {
|
||||
@ -479,4 +532,122 @@ class MangaPresenter(private val controller: MangaChaptersController,
|
||||
fetchChapters()
|
||||
}
|
||||
}
|
||||
|
||||
fun shareManga(cover: Bitmap) {
|
||||
val context = Injekt.get<Application>()
|
||||
|
||||
val destDir = File(context.cacheDir, "shared_image")
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
destDir.deleteRecursively()
|
||||
try {
|
||||
val image = saveImage(cover, destDir, manga)
|
||||
if (image != null)
|
||||
controller.shareManga(image)
|
||||
else controller.shareManga()
|
||||
}
|
||||
catch (e:java.lang.Exception) { }
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveImage(cover:Bitmap, directory: File, manga: Manga): File? {
|
||||
directory.mkdirs()
|
||||
|
||||
// Build destination file.
|
||||
val filename = DiskUtil.buildValidFilename("${manga.originalTitle()} - Cover.jpg")
|
||||
|
||||
val destFile = File(directory, filename)
|
||||
val stream: OutputStream = FileOutputStream(destFile)
|
||||
cover.compress(Bitmap.CompressFormat.JPEG, 75, stream)
|
||||
stream.flush()
|
||||
stream.close()
|
||||
return destFile
|
||||
}
|
||||
|
||||
fun updateManga(title:String?, author:String?, artist: String?, uri: Uri?,
|
||||
description: String?, tags: Array<String>?) {
|
||||
if (manga.source == LocalSource.ID) {
|
||||
manga.title = if (title.isNullOrBlank()) manga.url else title.trim()
|
||||
manga.author = author?.trim()
|
||||
manga.artist = artist?.trim()
|
||||
manga.description = description?.trim()
|
||||
val tagsString = tags?.joinToString(", ") { it.capitalize() }
|
||||
manga.genre = if (tags.isNullOrEmpty()) null else tagsString?.trim()
|
||||
LocalSource(downloadManager.context).updateMangaInfo(manga)
|
||||
db.updateMangaInfo(manga).executeAsBlocking()
|
||||
}
|
||||
else {
|
||||
var changed = false
|
||||
val title = title?.trim()
|
||||
if (!title.isNullOrBlank() && manga.originalTitle().isBlank()) {
|
||||
manga.title = title
|
||||
changed = true
|
||||
}
|
||||
else if (title.isNullOrBlank() && manga.currentTitle() != manga.originalTitle()) {
|
||||
manga.title = manga.originalTitle()
|
||||
changed = true
|
||||
} else if (!title.isNullOrBlank() && title != manga.currentTitle()) {
|
||||
manga.title = "${title}${SManga.splitter}${manga.originalTitle()}"
|
||||
changed = true
|
||||
}
|
||||
|
||||
val author = author?.trim()
|
||||
if (author.isNullOrBlank() && manga.currentAuthor() != manga.originalAuthor()) {
|
||||
manga.author = manga.originalAuthor()
|
||||
changed = true
|
||||
} else if (!author.isNullOrBlank() && author != manga.currentAuthor()) {
|
||||
manga.author = "${author}${SManga.splitter}${manga.originalAuthor() ?: ""}"
|
||||
changed = true
|
||||
}
|
||||
|
||||
val artist = artist?.trim()
|
||||
if (artist.isNullOrBlank() && manga.currentArtist() != manga.originalArtist()) {
|
||||
manga.artist = manga.originalArtist()
|
||||
changed = true
|
||||
} else if (!artist.isNullOrBlank() && artist != manga.currentArtist()) {
|
||||
manga.artist = "${artist}${SManga.splitter}${manga.originalArtist() ?: ""}"
|
||||
changed = true
|
||||
}
|
||||
|
||||
val description = description?.trim()
|
||||
if (description.isNullOrBlank() && manga.currentDesc() != manga.originalDesc()) {
|
||||
manga.description = manga.originalDesc()
|
||||
changed = true
|
||||
} else if (!description.isNullOrBlank() && description != manga.currentDesc()) {
|
||||
manga.description = "${description}${SManga.splitter}${manga.originalDesc() ?: ""}"
|
||||
changed = true
|
||||
}
|
||||
|
||||
var tagsString = tags?.joinToString(", ")
|
||||
if ((tagsString.isNullOrBlank() && manga.currentGenres() != manga.originalGenres())
|
||||
|| tagsString == manga.originalGenres()) {
|
||||
manga.genre = manga.originalGenres()
|
||||
changed = true
|
||||
} else if (!tagsString.isNullOrBlank() && tagsString != manga.currentGenres()) {
|
||||
tagsString = tags?.joinToString(", ") { it.capitalize() }
|
||||
manga.genre = "${tagsString}${SManga.splitter}${manga.originalGenres() ?: ""}"
|
||||
changed = true
|
||||
}
|
||||
if (changed) db.updateMangaInfo(manga).executeAsBlocking()
|
||||
}
|
||||
if (uri != null) editCoverWithStream(uri)
|
||||
controller.updateHeader()
|
||||
}
|
||||
|
||||
private fun editCoverWithStream(uri: Uri): Boolean {
|
||||
val inputStream = downloadManager.context.contentResolver.openInputStream(uri) ?:
|
||||
return false
|
||||
if (manga.source == LocalSource.ID) {
|
||||
LocalSource.updateCover(downloadManager.context, manga, inputStream)
|
||||
return true
|
||||
}
|
||||
|
||||
if (manga.thumbnail_url != null && manga.favorite) {
|
||||
Injekt.get<PreferencesHelper>().refreshCoversToo().set(false)
|
||||
coverCache.copyToCache(manga.thumbnail_url!!, inputStream)
|
||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
@ -5,11 +5,10 @@ import android.view.MenuItem
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BaseController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaPresenter
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
@ -60,13 +59,12 @@ class ChaptersAdapter(
|
||||
|
||||
interface MangaHeaderInterface {
|
||||
fun coverColor(): Int?
|
||||
fun nextChapter(): Chapter?
|
||||
fun mangaPresenter(): MangaPresenter
|
||||
fun prepareToShareManga()
|
||||
fun readNextChapter()
|
||||
fun downloadChapter(position: Int)
|
||||
fun topCoverHeight(): Int
|
||||
fun chapterCount(): Int
|
||||
fun tagClicked(text: String)
|
||||
fun mangaSource(): Source
|
||||
fun showChapterFilter()
|
||||
fun favoriteManga(longPress: Boolean)
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaChaptersController
|
||||
import eu.kanade.tachiyomi.util.lang.chop
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.android.synthetic.main.edit_manga_dialog.view.*
|
||||
@ -34,9 +35,9 @@ class EditMangaDialog : DialogController {
|
||||
private var customCoverUri:Uri? = null
|
||||
|
||||
private val infoController
|
||||
get() = targetController as MangaInfoController
|
||||
get() = targetController as MangaChaptersController
|
||||
|
||||
constructor(target: MangaInfoController, manga: Manga) : super(Bundle()
|
||||
constructor(target: MangaChaptersController, manga: Manga) : super(Bundle()
|
||||
.apply {
|
||||
putLong(KEY_MANGA, manga.id!!)
|
||||
}) {
|
||||
@ -168,7 +169,6 @@ class EditMangaDialog : DialogController {
|
||||
dialogView?.manga_author?.text.toString(), dialogView?.manga_artist?.text.toString(),
|
||||
customCoverUri, dialogView?.manga_description?.text.toString(),
|
||||
dialogView?.manga_genres_tags?.tags)
|
||||
infoController.updateTitle()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
@ -5,7 +5,6 @@ import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.app.PendingIntent
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
@ -15,7 +14,6 @@ import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
@ -31,8 +29,6 @@ import androidx.transition.ChangeBounds
|
||||
import androidx.transition.ChangeImageTransform
|
||||
import androidx.transition.TransitionManager
|
||||
import androidx.transition.TransitionSet
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.list.listItemsSingleChoice
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
@ -55,13 +51,13 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
|
||||
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.manga.ChooseShapeDialog
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||
@ -236,7 +232,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_edit -> EditMangaDialog(this, presenter.manga).showDialog(router)
|
||||
//R.id.action_edit -> EditMangaDialog(this, presenter.manga).showDialog(router)
|
||||
R.id.action_open_in_web_view -> openInWebView()
|
||||
R.id.action_share -> prepareToShareManga()
|
||||
R.id.action_add_to_home_screen -> addToHomeScreen()
|
||||
@ -608,41 +604,13 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog to choose a shape for the icon.
|
||||
*/
|
||||
private class ChooseShapeDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
||||
|
||||
constructor(target: MangaInfoController) : this() {
|
||||
targetController = target
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||
val modes = intArrayOf(R.string.circular_icon,
|
||||
R.string.rounded_icon,
|
||||
R.string.square_icon,
|
||||
R.string.star_icon)
|
||||
|
||||
return MaterialDialog(activity!!)
|
||||
.title(R.string.icon_shape)
|
||||
.negativeButton(android.R.string.cancel)
|
||||
.listItemsSingleChoice (
|
||||
items = modes.map { activity?.getString(it) as CharSequence },
|
||||
waitForPositiveButton = false)
|
||||
{ _, i, _ ->
|
||||
(targetController as? MangaInfoController)?.createShortcutForShape(i)
|
||||
dismissDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the bitmap of the shortcut with the requested shape and calls [createShortcut] when
|
||||
* the resource is available.
|
||||
*
|
||||
* @param i The shape index to apply. Defaults to circle crop transformation.
|
||||
*/
|
||||
private fun createShortcutForShape(i: Int = 0) {
|
||||
fun createShortcutForShape(i: Int = 0) {
|
||||
if (activity == null) return
|
||||
GlideApp.with(activity!!)
|
||||
.asBitmap()
|
||||
|
@ -28,7 +28,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaChaptersController
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
|
||||
import eu.kanade.tachiyomi.util.view.snack
|
||||
import kotlinx.android.synthetic.main.main_activity.*
|
||||
import kotlinx.android.synthetic.main.recent_chapters_controller.*
|
||||
@ -82,7 +82,8 @@ class RecentChaptersController : NucleusController<RecentChaptersPresenter>(),
|
||||
*/
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
view.applyWindowInsetsForController()
|
||||
view.applyWindowInsetsForRootController(activity!!.navigationView)
|
||||
|
||||
view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS)
|
||||
// Init RecyclerView and adapter
|
||||
val layoutManager = LinearLayoutManager(view.context)
|
||||
|
@ -26,8 +26,9 @@ import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
|
||||
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
|
||||
import kotlinx.android.synthetic.main.main_activity.*
|
||||
import kotlinx.android.synthetic.main.recently_read_controller.*
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
@ -80,16 +81,7 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle),
|
||||
*/
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
view.applyWindowInsetsForController()
|
||||
/*view.updateLayoutParams<FrameLayout.LayoutParams> {
|
||||
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
|
||||
val array = view.context.obtainStyledAttributes(attrsArray)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
topMargin = activity!!.window.decorView.rootWindowInsets.systemWindowInsetTop + array
|
||||
.getDimensionPixelSize(0, 0)
|
||||
}
|
||||
array.recycle()
|
||||
}*/
|
||||
view.applyWindowInsetsForRootController(activity!!.navigationView)
|
||||
// Initialize adapter
|
||||
adapter = RecentlyReadAdapter(this)
|
||||
recycler.adapter = adapter
|
||||
|
@ -17,6 +17,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BaseController
|
||||
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController
|
||||
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
|
||||
import kotlinx.android.synthetic.main.main_activity.*
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.subscriptions.CompositeSubscription
|
||||
@ -35,24 +37,12 @@ abstract class SettingsController : PreferenceController() {
|
||||
untilDestroySubscriptions = CompositeSubscription()
|
||||
}
|
||||
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||
/*view.updateLayoutParams<FrameLayout.LayoutParams> {
|
||||
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
|
||||
val array = view.context.obtainStyledAttributes(attrsArray)
|
||||
topMargin = array.getDimensionPixelSize(0, 0)
|
||||
array.recycle()
|
||||
}*/
|
||||
|
||||
if (this is SettingsMainController)
|
||||
view.applyWindowInsetsForRootController(activity!!.navigationView)
|
||||
else {
|
||||
view.applyWindowInsetsForController()
|
||||
/*view.updateLayoutParams<FrameLayout.LayoutParams> {
|
||||
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
|
||||
val array = view.context.obtainStyledAttributes(attrsArray)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
topMargin = activity!!.window.decorView.rootWindowInsets.systemWindowInsetTop + array
|
||||
.getDimensionPixelSize(0, 0)
|
||||
}
|
||||
array.recycle()
|
||||
}*/
|
||||
listView.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.WindowInsets
|
||||
import android.widget.Button
|
||||
import android.widget.FrameLayout
|
||||
@ -207,6 +208,28 @@ fun View.applyWindowInsetsForController() {
|
||||
requestApplyInsetsWhenAttached()
|
||||
}
|
||||
|
||||
fun View.applyWindowInsetsForRootController(bottomNav: View) {
|
||||
viewTreeObserver.addOnGlobalLayoutListener(
|
||||
object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
if (bottomNav.height > 0) {
|
||||
viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
setOnApplyWindowInsetsListener { view, insets ->
|
||||
view.updateLayoutParams<FrameLayout.LayoutParams> {
|
||||
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
|
||||
val array = view.context.obtainStyledAttributes(attrsArray)
|
||||
topMargin = insets.systemWindowInsetTop + array.getDimensionPixelSize(0, 0)
|
||||
bottomMargin = bottomNav.height
|
||||
array.recycle()
|
||||
}
|
||||
insets
|
||||
}
|
||||
requestApplyInsetsWhenAttached()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun View.requestApplyInsetsWhenAttached() {
|
||||
if (isAttachedToWindow) {
|
||||
|
@ -52,6 +52,7 @@
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="none"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="@id/title"
|
||||
|
@ -30,7 +30,7 @@
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/sort_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="12dp"
|
||||
@ -39,34 +39,19 @@
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/sort_newest"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Newest first" />
|
||||
android:text="@string/newest_first" />
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/sort_oldest"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="Oldest first" />
|
||||
android:text="@string/oldest_first" />
|
||||
</RadioGroup>
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/sort_by_source"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="Sort by source's order" />
|
||||
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/show_titles"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="Hide chapter titles" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
android:layout_width="match_parent"
|
||||
@ -82,7 +67,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="Show All" />
|
||||
android:text="@string/show_all" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/show_read"
|
||||
@ -90,7 +75,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="Show read chapters" />
|
||||
android:text="@string/show_read_chapters" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/show_unread"
|
||||
@ -98,7 +83,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="Show unread chapters" />
|
||||
android:text="@string/show_unread_chapters" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/show_download"
|
||||
@ -106,7 +91,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="Show downloaded chapters" />
|
||||
android:text="@string/show_downloaded_chapters" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/show_bookmark"
|
||||
@ -114,7 +99,45 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="Show bookmarked chapters" />
|
||||
android:text="@string/show_bookmarked_chapters" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:text="@string/more" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/sort_method_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp">
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/sort_by_source"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sort_by_source_s_order" />
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/sort_by_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sort_by_chapter_number" />
|
||||
</RadioGroup>
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/hide_titles"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="@string/hide_chapter_titles" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
@ -10,8 +10,8 @@
|
||||
android:id="@+id/controller_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/navigationView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
|
@ -165,7 +165,7 @@
|
||||
app:icon="@drawable/ic_sync_black_24dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/edit_button"
|
||||
android:id="@+id/share_button"
|
||||
style="@style/Theme.Widget.CustomImageButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@ -173,8 +173,8 @@
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_edit_white_24dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/chapters_title" />
|
||||
android:contentDescription="@string/action_share"
|
||||
android:src="@drawable/ic_share_white_24dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
@ -312,16 +312,17 @@
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/chapters_title"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="18dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:maxLines="1"
|
||||
android:text="@string/chapters"
|
||||
android:textSize="17sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/filters_text"
|
||||
app:layout_constraintStart_toStartOf="@id/manga_summary_label"
|
||||
app:layout_constraintTop_toBottomOf="@id/start_reading_button"
|
||||
app:layout_constraintEnd_toStartOf="@id/filters_text"/>
|
||||
app:layout_constraintTop_toBottomOf="@id/start_reading_button" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/filter_button"
|
||||
@ -332,17 +333,22 @@
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_filter_list_white_24dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/chapters_title"
|
||||
app:layout_constraintTop_toTopOf="@id/chapters_title"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/chapters_title" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/filters_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:maxLines="2"
|
||||
android:padding="5dp"
|
||||
tools:text="Read"
|
||||
app:layout_constraintTop_toTopOf="@id/filter_button"
|
||||
android:textColor="?android:textColorHint"
|
||||
android:textAlignment="textEnd"
|
||||
app:layout_constraintBottom_toBottomOf="@id/filter_button"
|
||||
app:layout_constraintEnd_toStartOf="@id/filter_button" />
|
||||
app:layout_constraintEnd_toStartOf="@id/filter_button"
|
||||
app:layout_constraintStart_toEndOf="@+id/chapters_title"
|
||||
app:layout_constraintTop_toTopOf="@id/filter_button"
|
||||
tools:text="Read, Unread, Bookmarked, Downloaded, All" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
54
app/src/main/res/menu/manga_details.xml
Normal file
54
app/src/main/res/menu/manga_details.xml
Normal 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>
|
@ -68,6 +68,7 @@
|
||||
<string name="action_skip_manga">Don\'t migrate</string>
|
||||
<string name="action_select_all">Select all</string>
|
||||
<string name="action_mark_as_read">Mark as read</string>
|
||||
<string name="action_mark_all_as_read">Mark all as read</string>
|
||||
<string name="action_mark_as_unread">Mark as unread</string>
|
||||
<string name="action_mark_previous_as_read">Mark previous as read</string>
|
||||
<string name="action_mark_multiple">Mark multiple</string>
|
||||
@ -507,6 +508,7 @@
|
||||
<item quantity="other">%1$d chapters</item>
|
||||
</plurals>
|
||||
<string name="no_description">No description</string>
|
||||
<string name="mark_all_as_read_message">Mark all chapters as read?</string>
|
||||
|
||||
<!-- Manga chapters fragment -->
|
||||
<string name="start_reading">Start reading</string>
|
||||
@ -710,5 +712,16 @@
|
||||
<string name="action_auto">Auto</string>
|
||||
<string name="more">More</string>
|
||||
<string name="less">Less</string>
|
||||
<string name="more_options">More options</string>
|
||||
<string name="show_bookmarked_chapters">Show bookmarked chapters</string>
|
||||
<string name="show_downloaded_chapters">Show downloaded chapters</string>
|
||||
<string name="show_unread_chapters">Show unread chapters</string>
|
||||
<string name="show_read_chapters">Show read chapters</string>
|
||||
<string name="show_all">Show All</string>
|
||||
<string name="hide_chapter_titles">Hide chapter titles</string>
|
||||
<string name="sort_by_source_s_order">Sort by source\'s order</string>
|
||||
<string name="sort_by_chapter_number">Sort by chapter number</string>
|
||||
<string name="newest_first">Newest to oldest</string>
|
||||
<string name="oldest_first">Oldest to newest</string>
|
||||
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user