diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt index 128e25c1d3..901d0041c5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt @@ -22,7 +22,6 @@ class MangaDetailsAdapter( var items: List = emptyList() - private var isAnimating = false val delegate: MangaDetailsInterface = controller val presenter = controller.presenter @@ -51,17 +50,15 @@ class MangaDetailsAdapter( fun performFilter() { val s = getFilter(String::class.java) if (s.isNullOrBlank()) { - updateDataSet(items, isAnimating) + updateDataSet(items) } else { updateDataSet(items.filter { it.name.contains(s, true) || - it.scanlator?.contains(s, true) == true }, isAnimating) + it.scanlator?.contains(s, true) == true }) } - isAnimating = false } override fun onItemSwiped(position: Int, direction: Int) { super.onItemSwiped(position, direction) - isAnimating = true when (direction) { ItemTouchHelper.RIGHT -> controller.bookmarkChapter(position) ItemTouchHelper.LEFT -> controller.toggleReadChapter(position) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index cd96cf59a0..35bb6e6410 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -19,6 +19,7 @@ import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.os.Build import android.os.Bundle +import android.util.DisplayMetrics import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -28,10 +29,12 @@ import android.view.ViewGroup import android.view.ViewPropertyAnimator import android.view.animation.DecelerateInterpolator import android.view.inputmethod.InputMethodManager +import android.widget.LinearLayout import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.SearchView +import androidx.core.content.ContextCompat import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.ColorUtils @@ -99,6 +102,7 @@ import eu.kanade.tachiyomi.util.system.ThemeUtil 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.pxToDp import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.view.getText @@ -173,6 +177,15 @@ class MangaDetailsController : BaseController, var refreshTracker: Int? = null private var textAnim: ViewPropertyAnimator? = null private var scrollAnim: ViewPropertyAnimator? = null + var isTablet = false + + // Tablet Layout + var tabletRecycler: RecyclerView? = null + + /** + * Adapter containing a list of chapters. + */ + private var tabletAdapter: MangaDetailsAdapter? = null /** * Library search query. @@ -200,7 +213,7 @@ class MangaDetailsController : BaseController, } override fun getTitle(): String? { - return if (toolbarIsColored) manga?.title else null + return if (toolbarIsColored && !isTablet) manga?.title else null } override fun onViewCreated(view: View) { @@ -208,8 +221,28 @@ class MangaDetailsController : BaseController, coverColor = null // Init RecyclerView and adapter - adapter = - MangaDetailsAdapter(this, view.context) + adapter = MangaDetailsAdapter(this, view.context) + isTablet = isTabletSize() + if (isTablet) { + tabletRecycler = RecyclerView(view.context) + linear_recycler_layout.addView(tabletRecycler, 0) + tabletRecycler?.updateLayoutParams { + weight = 0.4f + height = ViewGroup.LayoutParams.MATCH_PARENT + width = ViewGroup.LayoutParams.MATCH_PARENT + } + tabletRecycler?.clipToPadding = false + tabletAdapter = MangaDetailsAdapter(this, view.context) + tabletRecycler?.adapter = tabletAdapter + tabletRecycler?.layoutManager = LinearLayoutManager(view.context) + val divider = View(view.context) + divider.setBackgroundColor(ContextCompat.getColor(view.context, R.color.divider)) + linear_recycler_layout.addView(divider, 1) + divider.updateLayoutParams { + height = ViewGroup.LayoutParams.MATCH_PARENT + width = 1.dpToPx + } + } recycler.adapter = adapter adapter?.isSwipeEnabled = true @@ -232,11 +265,13 @@ class MangaDetailsController : BaseController, activity!!.appbar.elevation = 0f recycler.doOnApplyWindowInsets { v, insets, _ -> + v.updatePaddingRelative(bottom = insets.systemWindowInsetBottom) + tabletRecycler?.updatePaddingRelative(bottom = insets.systemWindowInsetBottom) headerHeight = appbarHeight + insets.systemWindowInsetTop statusBarHeight = insets.systemWindowInsetTop swipe_refresh.setProgressViewOffset(false, (-40).dpToPx, headerHeight + offset) - (recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder) - ?.setTopHeight(headerHeight) + if (isTablet) v.updatePaddingRelative(top = headerHeight + 1.dpToPx) + getHeader()?.setTopHeight(headerHeight) fast_scroll_layout.updateLayoutParams { topMargin = headerHeight bottomMargin = insets.systemWindowInsetBottom @@ -245,48 +280,57 @@ class MangaDetailsController : BaseController, } presenter.onCreate() - fast_scroller.translationX = if (showScroll) 0f else 25f.dpToPx + fast_scroller.translationX = if (showScroll || isTablet) 0f else 25f.dpToPx recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) val atTop = !recycler.canScrollVertically(-1) - val tY = getHeader()?.backdrop?.translationY ?: 0f - getHeader()?.backdrop?.translationY = max(0f, tY + dy * 0.25f) - if (router?.backstack?.lastOrNull() - ?.controller() == this@MangaDetailsController && statusBarHeight > -1 && activity != null && activity!!.appbar.height > 0 - ) { - activity!!.appbar.y -= dy - activity!!.appbar.y = MathUtils.clamp( - activity!!.appbar.y, -activity!!.appbar.height.toFloat(), 0f - ) - } - val appBarY = activity?.appbar?.y ?: 0f - if ((!atTop && !toolbarIsColored && (appBarY < (-headerHeight + 1) || (dy < 0 && appBarY == 0f))) || (atTop && toolbarIsColored)) { - colorToolbar(!atTop) + if (!isTablet) { + val tY = getHeader()?.backdrop?.translationY ?: 0f + getHeader()?.backdrop?.translationY = max(0f, tY + dy * 0.25f) + if (router?.backstack?.lastOrNull() + ?.controller() == this@MangaDetailsController && statusBarHeight > -1 && activity != null && activity!!.appbar.height > 0 + ) { + activity!!.appbar.y -= dy + activity!!.appbar.y = MathUtils.clamp( + activity!!.appbar.y, -activity!!.appbar.height.toFloat(), 0f + ) + } + val appBarY = activity?.appbar?.y ?: 0f + if ((!atTop && !toolbarIsColored && (appBarY < (-headerHeight + 1) || (dy < 0 && appBarY == 0f))) || (atTop && toolbarIsColored)) { + colorToolbar(!atTop) + } + } else { + if ((!atTop && !toolbarIsColored) || (atTop && toolbarIsColored)) { + colorToolbar(!atTop) + } } if (atTop) { getHeader()?.backdrop?.translationY = 0f activity!!.appbar.y = 0f } - val fPosition = - (recycler.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() - if (fPosition > 0 && !showScroll) { - showScroll = true - scrollAnim?.cancel() - scrollAnim = fast_scroller.animate().setDuration(100).translationX(0f) + if (!isTablet) { + val fPosition = + (recycler.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() + if (fPosition > 0 && !showScroll) { + showScroll = true + scrollAnim?.cancel() + scrollAnim = fast_scroller.animate().setDuration(100).translationX(0f) scrollAnim?.start() - } else if (fPosition <= 0 && showScroll) { - showScroll = false - scrollAnim?.cancel() - scrollAnim = fast_scroller.animate().setDuration(100).translationX(25f.dpToPx) - scrollAnim?.start() + } else if (fPosition <= 0 && showScroll) { + showScroll = false + scrollAnim?.cancel() + scrollAnim = + fast_scroller.animate().setDuration(100).translationX(25f.dpToPx) + scrollAnim?.start() + } } } override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) val atTop = !recycler.canScrollVertically(-1) - if (newState == RecyclerView.SCROLL_STATE_IDLE) { + if (newState == RecyclerView.SCROLL_STATE_IDLE && !isTablet) { if (router?.backstack?.lastOrNull() ?.controller() == this@MangaDetailsController && statusBarHeight > -1 && activity != null && activity!!.appbar.height > 0 @@ -415,8 +459,7 @@ class MangaDetailsController : BaseController, ) else it?.getDarkVibrantColor(colorBack)) ?: colorBack coverColor = backDropColor - (recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder) - ?.setBackDrop(backDropColor) + getHeader()?.setBackDrop(backDropColor) if (toolbarIsColored) { val translucentColor = ColorUtils.setAlphaComponent(backDropColor, 175) (activity as MainActivity).toolbar.setBackgroundColor(translucentColor) @@ -456,11 +499,33 @@ class MangaDetailsController : BaseController, download.progress) } + private fun isTabletSize(): Boolean { + val activity = activity ?: return false + if ((activity.resources.configuration.screenLayout and Configuration + .SCREENLAYOUT_SIZE_MASK) < Configuration.SCREENLAYOUT_SIZE_LARGE) + return false + val displayMetrics = DisplayMetrics() + activity.windowManager?.defaultDisplay?.getMetrics(displayMetrics) + return displayMetrics.widthPixels.pxToDp >= 720 + } + + fun hasTabletHeight(): Boolean { + val activity = activity ?: return false + if ((activity.resources.configuration.screenLayout and Configuration + .SCREENLAYOUT_SIZE_MASK) < Configuration.SCREENLAYOUT_SIZE_LARGE) return false + val displayMetrics = DisplayMetrics() + activity.windowManager?.defaultDisplay?.getMetrics(displayMetrics) + return displayMetrics.heightPixels.pxToDp >= 720 + } + private fun getHolder(chapter: Chapter): ChapterHolder? { return recycler?.findViewHolderForItemId(chapter.id!!) as? ChapterHolder } - private fun getHeader(): MangaHeaderHolder? = recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder + private fun getHeader(): MangaHeaderHolder? { + return if (isTablet) tabletRecycler?.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder + else recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder + } override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { super.onChangeStarted(handler, type) @@ -719,9 +784,10 @@ class MangaDetailsController : BaseController, setOnQueryTextChangeListener(searchView) { query = it ?: "" - if (query.isNotEmpty()) { - (recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)?.collapse() - } else (recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)?.expand() + if (!isTablet) { + if (query.isNotEmpty()) getHeader()?.collapse() + else getHeader()?.expand() + } adapter?.setFilter(query) adapter?.performFilter() @@ -1084,8 +1150,7 @@ class MangaDetailsController : BaseController, if (!manga.favorite) { toggleMangaFavorite() } else { - val headerHolder = - recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder ?: return + val headerHolder = getHeader() ?: return val popup = PopupMenu(view!!.context, headerHolder.favorite_button) popup.menu.add(R.string.remove_from_library) @@ -1152,8 +1217,7 @@ class MangaDetailsController : BaseController, } }) } - val favButton = (recycler.findViewHolderForAdapterPosition(0) - as? MangaHeaderHolder)?.favorite_button + val favButton = getHeader()?.favorite_button (activity as? MainActivity)?.setUndoSnackBar(snack, favButton) } @@ -1207,8 +1271,7 @@ class MangaDetailsController : BaseController, } fun refreshTracker() { - (recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder) - ?.updateTracking() + getHeader()?.updateTracking() } fun trackRefreshDone() { @@ -1276,7 +1339,12 @@ class MangaDetailsController : BaseController, * Called to set the last used catalogue at the top of the view. */ private fun addMangaHeader() { - if (adapter?.scrollableHeaders?.isEmpty() == true) { + if (tabletAdapter?.scrollableHeaders?.isEmpty() == true) { + tabletAdapter?.removeAllScrollableHeaders() + tabletAdapter?.addScrollableHeader(presenter.headerItem) + adapter?.removeAllScrollableHeaders() + adapter?.addScrollableHeader(presenter.tabletChapterHeaderItem!!) + } else if (!isTablet && adapter?.scrollableHeaders?.isEmpty() == true) { adapter?.removeAllScrollableHeaders() adapter?.addScrollableHeader(presenter.headerItem) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt index f2ad380ca4..46b8181e09 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt @@ -75,8 +75,15 @@ class MangaDetailsPresenter( private set var headerItem = MangaHeaderItem(manga, controller.fromCatalogue) + var tabletChapterHeaderItem: MangaHeaderItem? = null fun onCreate() { + headerItem.startExpanded = controller.hasTabletHeight() || headerItem.startExpanded + headerItem.isTablet = controller.isTablet + if (controller.isTablet) { + tabletChapterHeaderItem = MangaHeaderItem(manga, false) + tabletChapterHeaderItem?.isChapterHeader = true + } isLockedFromSearch = SecureActivityDelegate.shouldBeLocked() headerItem.isLocked = isLockedFromSearch downloadManager.addListener(this) @@ -223,6 +230,8 @@ class MangaDetailsPresenter( * @return an observable of the list of chapters filtered and sorted. */ private fun applyChapterFilters(chapterList: List): List { + if (isLockedFromSearch) + return chapterList var chapters = chapterList if (onlyUnread()) { chapters = chapters.filter { !it.read } 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 77d797b50a..e91e9c20eb 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 @@ -32,60 +32,57 @@ import kotlinx.android.synthetic.main.manga_header_item.* class MangaHeaderHolder( private val view: View, private val adapter: MangaDetailsAdapter, - startExpanded: Boolean + startExpanded: Boolean, + isTablet: Boolean = false ) : BaseFlexibleViewHolder(view, adapter) { init { - start_reading_button.setOnClickListener { adapter.delegate.readNextChapter() } - top_view.updateLayoutParams { - height = adapter.delegate.topCoverHeight() - } - more_button.setOnClickListener { expandDesc() } - manga_summary.setOnClickListener { expandDesc() } - manga_summary.setOnLongClickListener { - if (manga_summary.isTextSelectable && !adapter.recyclerView.canScrollVertically(-1)) { - (adapter.delegate as MangaDetailsController).swipe_refresh.isEnabled = false - } - false - } - manga_summary.setOnTouchListener { _, event -> - if (event.actionMasked == MotionEvent.ACTION_UP) - (adapter.delegate as MangaDetailsController).swipe_refresh.isEnabled = true - false - } - less_button.setOnClickListener { - manga_summary.setTextIsSelectable(false) - manga_summary.maxLines = 3 - manga_summary.setOnClickListener { expandDesc() } - manga_genres_tags.gone() - less_button.gone() - more_button_group.visible() - } - manga_genres_tags.setOnTagClickListener { - adapter.delegate.tagClicked(it) - } chapter_layout.setOnClickListener { adapter.delegate.showChapterFilter() } - webview_button.setOnClickListener { adapter.delegate.openInWebView() } - share_button.setOnClickListener { adapter.delegate.prepareToShareManga() } - favorite_button.setOnClickListener { - adapter.delegate.favoriteManga(false) + if (start_reading_button != null) { + start_reading_button.setOnClickListener { adapter.delegate.readNextChapter() } + top_view.updateLayoutParams { + height = adapter.delegate.topCoverHeight() + } + more_button.setOnClickListener { expandDesc() } + manga_summary.setOnClickListener { expandDesc() } + manga_summary.setOnLongClickListener { + if (manga_summary.isTextSelectable && !adapter.recyclerView.canScrollVertically(-1)) { + (adapter.delegate as MangaDetailsController).swipe_refresh.isEnabled = false + } + false + } + manga_summary.setOnTouchListener { _, event -> + if (event.actionMasked == MotionEvent.ACTION_UP) (adapter.delegate as MangaDetailsController).swipe_refresh.isEnabled = + true + false + } + less_button.setOnClickListener { collapseDesc() } + manga_genres_tags.setOnTagClickListener { + adapter.delegate.tagClicked(it) + } + webview_button.setOnClickListener { adapter.delegate.openInWebView() } + share_button.setOnClickListener { adapter.delegate.prepareToShareManga() } + favorite_button.setOnClickListener { + adapter.delegate.favoriteManga(false) + } + favorite_button.setOnLongClickListener { + adapter.delegate.favoriteManga(true) + true + } + manga_full_title.setOnLongClickListener { + adapter.delegate.copyToClipboard(manga_full_title.text.toString(), R.string.title) + true + } + manga_author.setOnLongClickListener { + adapter.delegate.copyToClipboard(manga_author.text.toString(), R.string.author) + true + } + manga_cover.setOnClickListener { adapter.delegate.zoomImageFromThumb(cover_card) } + track_button.setOnClickListener { adapter.delegate.showTrackingSheet() } + if (startExpanded) expandDesc() + else collapseDesc() + if (isTablet) chapter_layout.gone() } - favorite_button.setOnLongClickListener { - adapter.delegate.favoriteManga(true) - true - } - manga_full_title.setOnLongClickListener { - adapter.delegate.copyToClipboard(manga_full_title.text.toString(), R.string.title) - true - } - manga_author.setOnLongClickListener { - adapter.delegate.copyToClipboard(manga_author.text.toString(), R.string.author) - true - } - manga_cover.setOnClickListener { adapter.delegate.zoomImageFromThumb(cover_card) } - track_button.setOnClickListener { adapter.delegate.showTrackingSheet() } - if (startExpanded) - expandDesc() } private fun expandDesc() { @@ -98,6 +95,22 @@ class MangaHeaderHolder( } } + private fun collapseDesc() { + manga_summary.setTextIsSelectable(false) + manga_summary.maxLines = 3 + manga_summary.setOnClickListener { expandDesc() } + manga_genres_tags.gone() + less_button.gone() + more_button_group.visible() + } + + fun bindChapters() { + val presenter = adapter.delegate.mangaPresenter() + val count = presenter.chapters.size + chapters_title.text = itemView.resources.getQuantityString(R.plurals.chapters, count, count) + filters_text.text = presenter.currentFilters() + } + @SuppressLint("SetTextI18n") fun bind(item: MangaHeaderItem, manga: Manga) { val presenter = adapter.delegate.mangaPresenter() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderItem.kt index 86acaa931e..c569ec6b34 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderItem.kt @@ -8,13 +8,15 @@ import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga -class MangaHeaderItem(val manga: Manga, private val startExpanded: Boolean) : +class MangaHeaderItem(val manga: Manga, var startExpanded: Boolean) : AbstractFlexibleItem() { + var isTablet = false + var isChapterHeader = false var isLocked = false override fun getLayoutRes(): Int { - return R.layout.manga_header_item + return if (isChapterHeader) R.layout.chapter_header_item else R.layout.manga_header_item } override fun isSelectable(): Boolean { @@ -26,7 +28,7 @@ class MangaHeaderItem(val manga: Manga, private val startExpanded: Boolean) : } override fun createViewHolder(view: View, adapter: FlexibleAdapter>): MangaHeaderHolder { - return MangaHeaderHolder(view, adapter as MangaDetailsAdapter, startExpanded) + return MangaHeaderHolder(view, adapter as MangaDetailsAdapter, startExpanded, isTablet) } override fun bindViewHolder( @@ -35,7 +37,8 @@ class MangaHeaderItem(val manga: Manga, private val startExpanded: Boolean) : position: Int, payloads: MutableList? ) { - holder.bind(this, manga) + if (isChapterHeader) holder.bindChapters() + else holder.bind(this, manga) } override fun equals(other: Any?): Boolean { diff --git a/app/src/main/res/layout/chapter_header_item.xml b/app/src/main/res/layout/chapter_header_item.xml new file mode 100644 index 0000000000..6316c4effa --- /dev/null +++ b/app/src/main/res/layout/chapter_header_item.xml @@ -0,0 +1,59 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/manga_details_controller.xml b/app/src/main/res/layout/manga_details_controller.xml index 65169bf663..ddc3d27c0a 100644 --- a/app/src/main/res/layout/manga_details_controller.xml +++ b/app/src/main/res/layout/manga_details_controller.xml @@ -13,17 +13,21 @@ android:layout_height="match_parent" android:background="?android:colorBackground"> - + android:orientation="horizontal"> + + + @@ -76,7 +80,6 @@ tools:background="@color/md_black_1000" /> - \ No newline at end of file