From ffb8a7bd1765b881e9118f8f5f33adbcf412ebfc Mon Sep 17 00:00:00 2001 From: Jay Date: Wed, 4 Mar 2020 22:56:26 -0800 Subject: [PATCH] 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 --- .../java/eu/kanade/tachiyomi/Migrations.kt | 2 +- .../data/library/LibraryUpdateService.kt | 5 +- .../ui/catalogue/CatalogueController.kt | 5 +- .../tachiyomi/ui/category/CategoryHolder.kt | 6 +- .../ui/category/CategoryPresenter.kt | 8 +- .../tachiyomi/ui/library/LibraryController.kt | 4 +- .../tachiyomi/ui/library/LibraryGridHolder.kt | 3 +- .../tachiyomi/ui/library/LibraryItem.kt | 4 + .../kanade/tachiyomi/ui/main/MainActivity.kt | 49 ++- .../tachiyomi/ui/main/SearchActivity.kt | 3 +- .../ui/manga/ChaptersSortBottomSheet.kt | 119 +++---- .../tachiyomi/ui/manga/ChooseShapeDialog.kt | 43 +++ .../ui/manga/MangaChaptersController.kt | 316 +++++++++++++++--- .../tachiyomi/ui/manga/MangaHeaderHolder.kt | 24 +- .../tachiyomi/ui/manga/MangaPresenter.kt | 193 ++++++++++- .../ui/manga/chapter/ChaptersAdapter.kt | 8 +- .../ui/manga/info/EditMangaDialog.kt | 6 +- .../ui/manga/info/MangaInfoController.kt | 38 +-- .../RecentChaptersController.kt | 5 +- .../recently_read/RecentlyReadController.kt | 14 +- .../ui/setting/SettingsController.kt | 26 +- .../tachiyomi/util/view/ViewExtensions.kt | 23 ++ app/src/main/res/layout/categories_item.xml | 1 + .../res/layout/chapter_sort_bottom_sheet.xml | 75 +++-- app/src/main/res/layout/main_activity.xml | 2 +- app/src/main/res/layout/manga_header_item.xml | 30 +- app/src/main/res/menu/manga_details.xml | 54 +++ app/src/main/res/values/strings.xml | 13 + 28 files changed, 812 insertions(+), 267 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChooseShapeDialog.kt create mode 100644 app/src/main/res/menu/manga_details.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 25b91118ca..0f0d7b1897 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -25,7 +25,7 @@ object Migrations { if (BuildConfig.INCLUDE_UPDATER && preferences.automaticUpdates()) { UpdaterJob.setupTask() } - return false + return BuildConfig.DEBUG } if (oldVersion < 14) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index 9319f77f5d..a4cd28f076 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -455,12 +455,13 @@ class LibraryUpdateService( .concatMap { manga -> val source = sourceManager.get(manga.source) as? HttpSource ?: return@concatMap Observable.empty() - source.fetchMangaDetails(manga) .map { networkManga -> + val thumbnailUrl = manga.thumbnail_url manga.copyFrom(networkManga) db.insertManga(manga).executeAsBlocking() - MangaImpl.setLastCoverFetch(manga.id!!, Date().time) + if (thumbnailUrl != networkManga.thumbnail_url) + MangaImpl.setLastCoverFetch(manga.id!!, Date().time) manga } .onErrorReturn { manga } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt index 2d6997644b..9a18c3a3cc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt @@ -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(), */ override fun onViewCreated(view: View) { super.onViewCreated(view) - view.applyWindowInsetsForController() + view.applyWindowInsetsForRootController(activity!!.navigationView) adapter = CatalogueAdapter(this) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt index 0c846ff28a..96ee53745f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt index d21ff188dc..7b9cd1c7b3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt @@ -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 { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index c882759869..b8a15061f6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt index ab6efca0fd..2a21f7df60 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt @@ -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) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index 53bd7f8f4e..73799631ea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index e4e1456cbd..fe8b475b22 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -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,8 +585,35 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { appbar.enableElevation() } - if (to !is DialogController) - navigationView.visibility = if (router.backstackSize > 1) View.GONE else View.VISIBLE + 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) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt index 15b731136f..f3cfb38c4c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt @@ -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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChaptersSortBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChaptersSortBottomSheet.kt index 9d2b290c69..dd6d05fc0a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChaptersSortBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChaptersSortBottomSheet.kt @@ -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() - 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() + sort_method_group.setOnCheckedChangeListener { _, checkedId -> + presenter.setSortMethod(checkedId == R.id.sort_by_source) } - uniform_grid.bindToPreference(preferences.uniformGrid()) { - controller.reattachAdapter() + + hide_titles.setOnCheckedChangeListener { _, isChecked -> + presenter.hideTitle(isChecked) } - 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() - }*/ + + show_all.setOnCheckedChangeListener(::checkedFilter) + show_read.setOnCheckedChangeListener(::checkedFilter) + show_unread.setOnCheckedChangeListener(::checkedFilter) + show_download.setOnCheckedChangeListener(::checkedFilter) + show_bookmark.setOnCheckedChangeListener(::checkedFilter) } - /** - * Binds a checkbox or switch view with a boolean preference. - */ - private fun CompoundButton.bindToPreference(pref: Preference, block: () -> Unit) { - isChecked = pref.getOrDefault() - setOnCheckedChangeListener { _, isChecked -> - pref.set(isChecked) - block() + 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 + } } - } - - /** - * Binds a radio group with a int preference. - */ - private fun RadioGroup.bindToPreference(pref: Preference, block: () -> Unit) { - (getChildAt(pref.getOrDefault()) as RadioButton).isChecked = true - setOnCheckedChangeListener { _, checkedId -> - val index = indexOfChild(findViewById(checkedId)) - pref.set(index) - block() + else if (!show_read.isChecked && !show_unread.isChecked && + !show_download.isChecked && !show_bookmark.isChecked) { + show_all.isChecked = true } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChooseShapeDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChooseShapeDialog.kt new file mode 100644 index 0000000000..3492d61fda --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChooseShapeDialog.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaChaptersController.kt index 1e227db855..0fac1e1e16 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaChaptersController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaChaptersController.kt @@ -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() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + 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) { + 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() { + 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(128, 128) { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + 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, categories: List) { 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))) + } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt index ce832c48d3..0763ffb336 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt @@ -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 { - 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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index 72da5d9fee..2fdc00366d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt @@ -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? = 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() - updateChapters() - controller.updateChapters(chapters) + 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() + withContext(Dispatchers.Main) { controller.updateChapters(chapters) } + } + } + + fun currentFilters(): String { + val filtersId = mutableListOf() + 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() + + 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?) { + 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().refreshCoversToo().set(false) + coverCache.copyToCache(manga.thumbnail_url!!, inputStream) + MangaImpl.setLastCoverFetch(manga.id!!, Date().time) + return true + } + return false + } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt index 03f94ab2ae..01dd376595 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt @@ -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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/EditMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/EditMangaDialog.kt index f457793101..53af9b7d6b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/EditMangaDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/EditMangaDialog.kt @@ -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 { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt index 3fffd8daca..43c355ea1b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt @@ -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(), 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(), } } - /** - * 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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt index af19b4290c..a5e9a5182e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt @@ -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(), */ 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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt index 4cd67376b9..27207c8444 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt @@ -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 { - 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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt index b61b12d2af..613e4e5d6e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt @@ -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 { - val attrsArray = intArrayOf(android.R.attr.actionBarSize) - val array = view.context.obtainStyledAttributes(attrsArray) - topMargin = array.getDimensionPixelSize(0, 0) - array.recycle() - }*/ - - view.applyWindowInsetsForController() - /*view.updateLayoutParams { - 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) + if (this is SettingsMainController) + view.applyWindowInsetsForRootController(activity!!.navigationView) + else { + view.applyWindowInsetsForController() + listView.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) + } return view } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index f8aea5c918..6ec2891d5e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -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 { + 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) { diff --git a/app/src/main/res/layout/categories_item.xml b/app/src/main/res/layout/categories_item.xml index 0b0345af65..775f76ca42 100644 --- a/app/src/main/res/layout/categories_item.xml +++ b/app/src/main/res/layout/categories_item.xml @@ -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" diff --git a/app/src/main/res/layout/chapter_sort_bottom_sheet.xml b/app/src/main/res/layout/chapter_sort_bottom_sheet.xml index 1c62026cd6..6e5fbe1036 100644 --- a/app/src/main/res/layout/chapter_sort_bottom_sheet.xml +++ b/app/src/main/res/layout/chapter_sort_bottom_sheet.xml @@ -30,7 +30,7 @@ + android:text="@string/newest_first" /> + android:text="@string/oldest_first" /> - - - - - + android:text="@string/show_all" /> + android:text="@string/show_read_chapters" /> + android:text="@string/show_unread_chapters" /> + android:text="@string/show_downloaded_chapters" /> + android:text="@string/show_bookmarked_chapters" /> + + + + + + + + + + + diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml index 461c2acf16..5efe0b2ece 100644 --- a/app/src/main/res/layout/main_activity.xml +++ b/app/src/main/res/layout/main_activity.xml @@ -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"> diff --git a/app/src/main/res/layout/manga_header_item.xml b/app/src/main/res/layout/manga_header_item.xml index 54335c9438..811e0b6790 100644 --- a/app/src/main/res/layout/manga_header_item.xml +++ b/app/src/main/res/layout/manga_header_item.xml @@ -165,7 +165,7 @@ app:icon="@drawable/ic_sync_black_24dp" /> + android:contentDescription="@string/action_share" + android:src="@drawable/ic_share_white_24dp" /> + app:layout_constraintTop_toBottomOf="@id/start_reading_button" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@id/chapters_title" /> + 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" /> \ No newline at end of file diff --git a/app/src/main/res/menu/manga_details.xml b/app/src/main/res/menu/manga_details.xml new file mode 100644 index 0000000000..fd032306c4 --- /dev/null +++ b/app/src/main/res/menu/manga_details.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a69fb1ccc2..9a36359139 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -68,6 +68,7 @@ Don\'t migrate Select all Mark as read + Mark all as read Mark as unread Mark previous as read Mark multiple @@ -507,6 +508,7 @@ %1$d chapters No description + Mark all chapters as read? Start reading @@ -710,5 +712,16 @@ Auto More Less + More options + Show bookmarked chapters + Show downloaded chapters + Show unread chapters + Show read chapters + Show All + Hide chapter titles + Sort by source\'s order + Sort by chapter number + Newest to oldest + Oldest to newest