From 02896679feb0d803f3182e9cab67fd648b4526ec Mon Sep 17 00:00:00 2001 From: Jay Date: Tue, 14 Apr 2020 23:56:50 -0400 Subject: [PATCH] Added Chapter list to reader activity Also using this as a test bed for fast adapter --- app/build.gradle.kts | 3 + .../ui/library/DisplayBottomSheet.kt | 2 +- .../manga/chapter/ChaptersSortBottomSheet.kt | 2 +- .../tachiyomi/ui/reader/ReaderActivity.kt | 118 +++++++------- .../tachiyomi/ui/reader/ReaderChapterItem.kt | 113 +++++++++++++ .../tachiyomi/ui/reader/ReaderChapterSheet.kt | 149 ++++++++++++++++++ .../tachiyomi/ui/reader/ReaderPresenter.kt | 63 +++++--- .../res/drawable/bg_bottom_sheet_black.xml | 8 + .../res/drawable/ic_bookmark_border_24dp.xml | 5 + .../drawable/ic_format_list_numbered_24dp.xml | 5 + app/src/main/res/drawable/oval_ripple.xml | 32 ++++ app/src/main/res/drawable/rounded_ripple.xml | 8 +- .../res/layout/chapter_sort_bottom_sheet.xml | 2 +- .../main/res/layout/display_bottom_sheet.xml | 2 +- app/src/main/res/layout/filter_buttons.xml | 6 +- app/src/main/res/layout/reader_activity.xml | 63 +------- .../main/res/layout/reader_chapter_item.xml | 64 ++++++++ .../main/res/layout/reader_chapters_sheet.xml | 61 +++++++ app/src/main/res/values-night/themes.xml | 1 + app/src/main/res/values/themes.xml | 4 + 20 files changed, 561 insertions(+), 150 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapterItem.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapterSheet.kt create mode 100644 app/src/main/res/drawable/bg_bottom_sheet_black.xml create mode 100644 app/src/main/res/drawable/ic_bookmark_border_24dp.xml create mode 100644 app/src/main/res/drawable/ic_format_list_numbered_24dp.xml create mode 100644 app/src/main/res/drawable/oval_ripple.xml create mode 100644 app/src/main/res/layout/reader_chapter_item.xml create mode 100644 app/src/main/res/layout/reader_chapters_sheet.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7e1caf8f8e..3dbe7b3990 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -186,6 +186,9 @@ dependencies { // UI implementation("com.dmitrymalkovich.android:material-design-dimens:1.4") implementation("com.github.dmytrodanylyk.android-process-button:library:1.0.4") + val latestFastAdapterRelease = "5.0.0" + implementation("com.mikepenz:fastadapter:${latestFastAdapterRelease}") + implementation("com.mikepenz:fastadapter-extensions-binding:${latestFastAdapterRelease}") implementation("eu.davidea:flexible-adapter:5.1.0") implementation("eu.davidea:flexible-adapter-ui:1.0.0") implementation("com.nononsenseapps:filepicker:2.5.2") diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/DisplayBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/DisplayBottomSheet.kt index 1cf9728375..18083c1727 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/DisplayBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/DisplayBottomSheet.kt @@ -71,7 +71,7 @@ class DisplayBottomSheet(private val controller: LibraryController) : BottomShee } settings_scroll_view.viewTreeObserver.addOnGlobalLayoutListener { val isScrollable = - settings_scroll_view!!.height < bottom_sheet.height + + settings_scroll_view!!.height < display_layout.height + settings_scroll_view.paddingTop + settings_scroll_view.paddingBottom close_button.visibleIf(isScrollable) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt index d5b7d715f4..77aa0b415f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt @@ -68,7 +68,7 @@ class ChaptersSortBottomSheet(controller: MangaDetailsController) : BottomSheetD close_button.setOnClickListener { dismiss() } settings_scroll_view.viewTreeObserver.addOnGlobalLayoutListener { val isScrollable = - settings_scroll_view!!.height < bottom_sheet.height + + settings_scroll_view!!.height < sort_layout.height + settings_scroll_view.paddingTop + settings_scroll_view.paddingBottom close_button.visibleIf(isScrollable) pill.visibleIf(!isScrollable) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 4c033b809f..47b1d2532d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -16,12 +16,14 @@ import android.view.Menu import android.view.MenuItem import android.view.MotionEvent import android.view.View +import android.view.ViewGroup import android.view.WindowManager import android.view.animation.Animation import android.view.animation.AnimationUtils import android.widget.SeekBar import androidx.appcompat.app.AppCompatDelegate import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView +import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.snackbar.Snackbar import eu.kanade.tachiyomi.R @@ -50,12 +52,15 @@ import eu.kanade.tachiyomi.util.system.ThemeUtil 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.gone import eu.kanade.tachiyomi.util.view.snack +import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.widget.SimpleAnimationListener import eu.kanade.tachiyomi.widget.SimpleSeekBarListener import kotlinx.android.synthetic.main.reader_activity.* +import kotlinx.android.synthetic.main.reader_chapters_sheet.* import kotlinx.coroutines.Job import kotlinx.coroutines.delay import me.zhanghai.android.systemuihelper.SystemUiHelper @@ -123,6 +128,8 @@ class ReaderActivity : BaseRxActivity(), */ private var bottomSheet: BottomSheetDialog? = null + var sheetManageNavColor = false + /** * Progress dialog used when switching chapters from the menu buttons. */ @@ -181,6 +188,7 @@ class ReaderActivity : BaseRxActivity(), menuVisible = savedInstanceState.getBoolean(::menuVisible.name) } + chapters_bottom_sheet.setup(this) config = ReaderConfig() initializeMenu() } @@ -191,6 +199,7 @@ class ReaderActivity : BaseRxActivity(), override fun onDestroy() { super.onDestroy() viewer?.destroy() + chapters_bottom_sheet.adapter = null viewer = null config?.destroy() config = null @@ -248,6 +257,9 @@ class ReaderActivity : BaseRxActivity(), else -> return super.onOptionsItemSelected(item) } bottomSheet?.show() + if (chapters_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) { + chapters_bottom_sheet.sheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED + } return true } @@ -256,6 +268,10 @@ class ReaderActivity : BaseRxActivity(), * delegated to the presenter. */ override fun onBackPressed() { + if (chapters_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) { + chapters_bottom_sheet.sheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED + return + } presenter.onBackPressed() super.onBackPressed() } @@ -288,6 +304,7 @@ class ReaderActivity : BaseRxActivity(), onBackPressed() } + BottomSheetBehavior.from(chapters_bottom_sheet).isHideable = true // Init listeners on bottom menu page_seekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { @@ -296,27 +313,35 @@ class ReaderActivity : BaseRxActivity(), } } }) - left_chapter.setOnClickListener { - if (viewer != null) { - if (viewer is R2LPagerViewer) - loadNextChapter() - else - loadPreviousChapter() - } - } - right_chapter.setOnClickListener { - if (viewer != null) { - if (viewer is R2LPagerViewer) - loadPreviousChapter() - else - loadNextChapter() - } - } // Set initial visibility setMenuVisibility(menuVisible) if (!menuVisible) - reader_menu_bottom.visibility = View.GONE + chapters_bottom_sheet.sheetBehavior?.state = BottomSheetBehavior.STATE_HIDDEN + reader_menu.doOnApplyWindowInsets { v, insets, _ -> + sheetManageNavColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && (insets + .systemWindowInsetBottom != insets.tappableElementInsets.bottom)) { + window.navigationBarColor = Color.TRANSPARENT + false + } + // if in landscape with 2/3 button mode, fully opaque nav bar + else if (insets.systemWindowInsetLeft > 0 || insets.systemWindowInsetRight > 0) { + window.navigationBarColor = getResourceColor(R.attr.colorPrimary) + false + } + // if in portrait with 2/3 button mode, translucent nav bar + else { + true + } + + toolbar.updateLayoutParams { + topMargin = insets.systemWindowInsetTop + } + v.updateLayoutParams { + leftMargin = insets.systemWindowInsetLeft + rightMargin = insets.systemWindowInsetRight + } + } } /** @@ -330,10 +355,10 @@ class ReaderActivity : BaseRxActivity(), snackbar?.dismiss() systemUi?.show() reader_menu.visibility = View.VISIBLE - reader_menu_bottom.visibility = View.VISIBLE - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - window.navigationBarColor = getResourceColor(R.attr.colorPrimaryDark) - } + if (chapters_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) + chapters_bottom_sheet.sheetBehavior?.isHideable = false + if (chapters_bottom_sheet.sheetBehavior?.state != BottomSheetBehavior.STATE_EXPANDED && sheetManageNavColor) + window.navigationBarColor = Color.TRANSPARENT // getResourceColor(R.attr.colorPrimaryDark) if (animate) { if (!menuStickyVisible) { val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top) @@ -344,8 +369,7 @@ class ReaderActivity : BaseRxActivity(), }) toolbar.startAnimation(toolbarAnimation) } - val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_bottom) - reader_menu_bottom.startAnimation(bottomAnimation) + BottomSheetBehavior.from(chapters_bottom_sheet).state = BottomSheetBehavior.STATE_COLLAPSED } } else { systemUi?.hide() @@ -358,10 +382,8 @@ class ReaderActivity : BaseRxActivity(), } }) toolbar.startAnimation(toolbarAnimation) - if (reader_menu_bottom.visibility != View.GONE) { - val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_bottom) - reader_menu_bottom.startAnimation(bottomAnimation) - } + BottomSheetBehavior.from(chapters_bottom_sheet).isHideable = true + BottomSheetBehavior.from(chapters_bottom_sheet).state = BottomSheetBehavior.STATE_HIDDEN } else reader_menu.visibility = View.GONE } @@ -465,20 +487,8 @@ class ReaderActivity : BaseRxActivity(), viewer.moveToPage(page) } - /** - * Tells the presenter to load the next chapter and mark it as active. The progress dialog - * should be automatically shown. - */ - private fun loadNextChapter() { - presenter.loadNextChapter() - } - - /** - * Tells the presenter to load the previous chapter and mark it as active. The progress dialog - * should be automatically shown. - */ - private fun loadPreviousChapter() { - presenter.loadPreviousChapter() + fun refreshChapters() { + chapters_bottom_sheet.refreshList() } /** @@ -492,19 +502,17 @@ class ReaderActivity : BaseRxActivity(), // Set bottom page number page_number.text = "${page.number}/${pages.size}" - // Set seekbar page number - if (viewer !is R2LPagerViewer) { - left_page_text.text = "${page.number}" - right_page_text.text = "${pages.size}" - } else { - right_page_text.text = "${page.number}" - left_page_text.text = "${pages.size}" - } + page_text.text = "${page.number} / ${pages.size}" - if (newChapter && config?.showNewChapter == false) { - systemUi?.show() + if (newChapter) { + if (config?.showNewChapter == false) systemUi?.show() + } else if (chapters_bottom_sheet.shouldCollaspe && chapters_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) { + chapters_bottom_sheet.sheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED } + if (chapters_bottom_sheet.selectedChapterId != presenter.getCurrentChapter()?.chapter?.id) + chapters_bottom_sheet.refreshList() + chapters_bottom_sheet.shouldCollaspe = true // Set seekbar progress page_seekbar.max = pages.lastIndex @@ -517,6 +525,9 @@ class ReaderActivity : BaseRxActivity(), */ fun onPageLongTap(page: ReaderPage) { ReaderPageSheet(this, page).show() + if (chapters_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) { + chapters_bottom_sheet.sheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED + } } /** @@ -621,10 +632,7 @@ class ReaderActivity : BaseRxActivity(), setMenuVisibility(false) } } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - window.navigationBarColor = getColor(android.R.color.transparent) - } - reader_menu_bottom.visibility = View.GONE + if (sheetManageNavColor) window.navigationBarColor = getResourceColor(R.attr.colorPrimary) reader_menu.visibility = View.VISIBLE val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top) toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapterItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapterItem.kt new file mode 100644 index 0000000000..a4ee97606b --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapterItem.kt @@ -0,0 +1,113 @@ +package eu.kanade.tachiyomi.ui.reader + +import android.content.res.ColorStateList +import android.text.format.DateUtils +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.TextView +import androidx.core.content.ContextCompat +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.items.AbstractItem +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.util.system.getResourceColor +import kotlinx.android.synthetic.main.chapters_item.* +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols +import java.util.Date + +class ReaderChapterItem(val chapter: Chapter, val manga: Manga, val isCurrent: Boolean) : + AbstractItem + () { + + var name: String? = null + var description: String? = null + val decimalFormat = + DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' }) + + /** defines the type defining this item. must be unique. preferably an id */ + override val type: Int + get() = R.id.reader_chapter_layout + + /** defines the layout which will be used for this item in the list */ + override val layoutRes: Int + get() = R.layout.reader_chapter_item + + override var identifier: Long + get() = chapter.id!! + set(value) {} + + override fun getViewHolder(v: View): ViewHolder { + return ViewHolder(v) + } + + class ViewHolder(view: View) : FastAdapter.ViewHolder(view) { + var chapterTitle: TextView = view.findViewById(R.id.chapter_title) + var chapterSubtitle: TextView = view.findViewById(R.id.chapter_scanlator) + var bookmarkButton: FrameLayout = view.findViewById(R.id.bookmark_layout) + var bookmarkImage: ImageView = view.findViewById(R.id.bookmark_image) + + private var readColor = view.context.getResourceColor(android.R.attr.textColorHint) + private var unreadColor = view.context.getResourceColor(android.R.attr.textColorPrimary) + private var activeColor = view.context.getResourceColor(android.R.attr.colorAccent) + + private var unbookmark = ContextCompat.getDrawable(view.context, R.drawable + .ic_bookmark_border_24dp) + private var bookmark = ContextCompat.getDrawable(view.context, R.drawable + .ic_bookmark_24dp) + + override fun bindView(item: ReaderChapterItem, payloads: List) { + val chapter = item.chapter + val manga = item.manga + chapterTitle.text = when (manga.displayMode) { + Manga.DISPLAY_NUMBER -> { + val number = item.decimalFormat.format(chapter.chapter_number.toDouble()) + itemView.context.getString(R.string.chapter_, number) + } + else -> chapter.name + } + val statuses = mutableListOf() + if (chapter.date_upload > 0) { + statuses.add( + DateUtils.getRelativeTimeSpanString( + chapter.date_upload, Date().time, DateUtils.HOUR_IN_MILLIS + ).toString() + ) + } + + if (!chapter.scanlator.isNullOrBlank()) { + statuses.add(chapter.scanlator!!) + } + + chapterTitle.setTextColor( + when { + item.isCurrent -> activeColor + chapter.read -> readColor + else -> unreadColor + } + ) + + chapterSubtitle.setTextColor( + when { + item.isCurrent -> activeColor + chapter.read -> readColor + else -> unreadColor + } + ) + bookmarkImage.setImageDrawable(if (chapter.bookmark) + bookmark + else unbookmark) + bookmarkImage.imageTintList = ColorStateList.valueOf(if (chapter.bookmark) + activeColor + else readColor) + chapterSubtitle.text = statuses.joinToString(" • ") + } + + override fun unbindView(item: ReaderChapterItem) { + chapterTitle.text = null + chapterSubtitle.text = null + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapterSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapterSheet.kt new file mode 100644 index 0000000000..1741d84753 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapterSheet.kt @@ -0,0 +1,149 @@ +package eu.kanade.tachiyomi.ui.reader + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Color +import android.os.Build +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.adapters.ItemAdapter +import com.mikepenz.fastadapter.listeners.ClickEventHook +import eu.kanade.tachiyomi.R +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.view.doOnApplyWindowInsets +import eu.kanade.tachiyomi.util.view.updateLayoutParams +import eu.kanade.tachiyomi.util.view.updatePaddingRelative +import kotlinx.android.synthetic.main.reader_chapters_sheet.view.* +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToInt + +class ReaderChapterSheet @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + LinearLayout(context, attrs) { + + var sheetBehavior: BottomSheetBehavior? = null + lateinit var presenter: ReaderPresenter + var adapter: FastAdapter? = null + private val itemAdapter = ItemAdapter() + var shouldCollaspe = true + var selectedChapterId = -1L + + fun setup(activity: ReaderActivity) { + presenter = activity.presenter + val primary = activity.getResourceColor(R.attr.colorPrimary) + val fullPrimary = ContextCompat.getColor(activity, R.color.darkPrimaryColor) + sheetBehavior = BottomSheetBehavior.from(this) + chapters_button.setOnClickListener { + if (sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) sheetBehavior?.state = + BottomSheetBehavior.STATE_COLLAPSED + else sheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED + } + val peek = sheetBehavior?.peekHeight ?: 30.dpToPx + post { + chapter_recycler.alpha = + if (sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) 1f else 0f + } + + chapters_bottom_sheet.doOnApplyWindowInsets { _, insets, _ -> + sheetBehavior?.peekHeight = + peek + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) insets.mandatorySystemGestureInsets.bottom + else insets.systemWindowInsetBottom + chapters_bottom_sheet.updateLayoutParams { + height = 280.dpToPx + insets.systemWindowInsetBottom + } + chapter_recycler.updatePaddingRelative(bottom = insets.systemWindowInsetBottom) + } + sheetBehavior?.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { + override fun onSlide(bottomSheet: View, progress: Float) { + val trueProgress = max(progress, 0f) + backgroundTintList = ColorStateList.valueOf(lerpColor(primary, fullPrimary, trueProgress)) + chapter_recycler.alpha = trueProgress + if (activity.sheetManageNavColor) activity.window.navigationBarColor = + lerpColor(ColorUtils.setAlphaComponent(primary, 0), primary, trueProgress) + } + + override fun onStateChanged(p0: View, state: Int) { + if (state == BottomSheetBehavior.STATE_COLLAPSED) { + shouldCollaspe = true + sheetBehavior?.isHideable = false + (chapter_recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset( + adapter?.getPosition(presenter.getCurrentChapter()?.chapter?.id ?: 0L) ?: 0, + chapter_recycler.height / 2 - 30.dpToPx + ) + } + if (state == BottomSheetBehavior.STATE_EXPANDED) { + chapter_recycler.alpha = 1f + if (activity.sheetManageNavColor) activity.window.navigationBarColor = primary + } + } + }) + + adapter = FastAdapter.with(itemAdapter) + chapter_recycler.adapter = adapter + adapter?.onClickListener = { _, _, item, _ -> + if (item.chapter.id != presenter.getCurrentChapter()?.chapter?.id) { + shouldCollaspe = false + presenter.loadChapter(item.chapter) + } + true + } + adapter?.addEventHook(object : ClickEventHook() { + override fun onBind(viewHolder: RecyclerView.ViewHolder): View? { + return if (viewHolder is ReaderChapterItem.ViewHolder) { + viewHolder.bookmarkButton + } else { + null + } + } + + override fun onClick(v: View, position: Int, fastAdapter: FastAdapter, item: ReaderChapterItem) { + presenter.toggleBookmark(item.chapter) + refreshList() + } + }) + + chapter_recycler.layoutManager = LinearLayoutManager(context) + refreshList() + } + + fun refreshList() { + launchUI { + val chapters = presenter.getChapters() + + selectedChapterId = chapters.find { it.isCurrent }?.chapter?.id ?: -1L + itemAdapter.clear() + itemAdapter.add(chapters) + + (chapter_recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset( + adapter?.getPosition(presenter.getCurrentChapter()?.chapter?.id ?: 0L) ?: 0, + chapter_recycler.height / 2 - 30.dpToPx + ) + } + } + + fun lerpColor(colorStart: Int, colorEnd: Int, percent: Float): Int { + val perc = (percent * 100).roundToInt() + return Color.argb( + lerpColorCalc(Color.alpha(colorStart), Color.alpha(colorEnd), perc), + lerpColorCalc(Color.red(colorStart), Color.red(colorEnd), perc), + lerpColorCalc(Color.green(colorStart), Color.green(colorEnd), perc), + lerpColorCalc(Color.blue(colorStart), Color.blue(colorEnd), perc) + ) + } + + fun lerpColorCalc(colorStart: Int, colorEnd: Int, percent: Int): Int { + return (min(colorStart, colorEnd) * (100 - percent) + max( + colorStart, colorEnd + ) * percent) / 100 + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index dc8064be58..d22606ec52 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -7,11 +7,13 @@ import com.jakewharton.rxrelay.BehaviorRelay import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.MangaImpl import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.SourceManager @@ -24,8 +26,11 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.system.ImageUtil +import eu.kanade.tachiyomi.util.system.executeOnIO +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import rx.Completable @@ -51,6 +56,8 @@ class ReaderPresenter( private val preferences: PreferencesHelper = Injekt.get() ) : BasePresenter() { + private var scope = CoroutineScope(Job() + Dispatchers.Default) + /** * The manga loaded in the reader. It can be null when instantiated for a short time. */ @@ -112,6 +119,8 @@ class ReaderPresenter( }.map(::ReaderChapter) } + var chapterItems = emptyList() + /** * Called when the presenter is created. It retrieves the saved active chapter if the process * was restored. @@ -182,6 +191,25 @@ class ReaderPresenter( }, ReaderActivity::setInitialChapterError) } + suspend fun getChapters(): List { + val manga = manga ?: return emptyList() + chapterItems = withContext(Dispatchers.IO) { + val list = db.getChapters(manga).executeOnIO().sortedBy { + when (manga.sorting) { + Manga.SORTING_NUMBER -> it.chapter_number + else -> it.source_order.toFloat() + } + }.map { + ReaderChapterItem(it, manga, it.id == + getCurrentChapter()?.chapter?.id ?: chapterId) + } + if (!manga.sortDescending(preferences.chaptersDescAsDefault().getOrDefault())) + list.reversed() + else list + } + return chapterItems + } + fun init(mangaId: Long, chapterUrl: String) { if (!needsInit()) return val context = Injekt.get() @@ -270,27 +298,28 @@ class ReaderPresenter( .also(::add) } - /** - * Called when the user is going to load the prev/next chapter through the menu button. It - * sets the [isLoadingAdjacentChapterRelay] that the view uses to prevent any further - * interaction until the chapter is loaded. - */ - private fun loadAdjacent(chapter: ReaderChapter) { + fun loadChapter(chapter: Chapter) { val loader = loader ?: return - Timber.d("Loading adjacent ${chapter.chapter.url}") + Timber.d("Loading adjacent ${chapter.url}") activeChapterSubscription?.unsubscribe() - activeChapterSubscription = getLoadObservable(loader, chapter) + activeChapterSubscription = getLoadObservable(loader, ReaderChapter(chapter)) .doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) } .doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) } .subscribeFirst({ view, _ -> view.moveToPageIndex(0) + view.refreshChapters() }, { _, _ -> // Ignore onError event, viewers handle that state }) } + fun toggleBookmark(chapter: Chapter) { + chapter.bookmark = !chapter.bookmark + db.updateChapterProgress(chapter).executeAsBlocking() + } + /** * Called when the viewers decide it's a good time to preload a [chapter] and improve the UX so * that the user doesn't have to wait too long to continue reading. @@ -355,6 +384,8 @@ class ReaderPresenter( * Saves this [chapter] progress (last read page and whether it's read). */ private fun saveChapterProgress(chapter: ReaderChapter) { + val dbChapter = db.getChapter(chapter.chapter.id!!).executeAsBlocking() + chapter.chapter.bookmark = dbChapter!!.bookmark db.updateChapterProgress(chapter.chapter).asRxCompletable() .onErrorComplete() .subscribeOn(Schedulers.io()) @@ -376,22 +407,6 @@ class ReaderPresenter( preload(chapter) } - /** - * Called from the activity to load and set the next chapter as active. - */ - fun loadNextChapter() { - val nextChapter = viewerChaptersRelay.value?.nextChapter ?: return - loadAdjacent(nextChapter) - } - - /** - * Called from the activity to load and set the previous chapter as active. - */ - fun loadPreviousChapter() { - val prevChapter = viewerChaptersRelay.value?.prevChapter ?: return - loadAdjacent(prevChapter) - } - /** * Returns the currently active chapter. */ diff --git a/app/src/main/res/drawable/bg_bottom_sheet_black.xml b/app/src/main/res/drawable/bg_bottom_sheet_black.xml new file mode 100644 index 0000000000..78d8b682e7 --- /dev/null +++ b/app/src/main/res/drawable/bg_bottom_sheet_black.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_bookmark_border_24dp.xml b/app/src/main/res/drawable/ic_bookmark_border_24dp.xml new file mode 100644 index 0000000000..4b895836e3 --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark_border_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_format_list_numbered_24dp.xml b/app/src/main/res/drawable/ic_format_list_numbered_24dp.xml new file mode 100644 index 0000000000..a1a1961725 --- /dev/null +++ b/app/src/main/res/drawable/ic_format_list_numbered_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/oval_ripple.xml b/app/src/main/res/drawable/oval_ripple.xml new file mode 100644 index 0000000000..4f48db0d13 --- /dev/null +++ b/app/src/main/res/drawable/oval_ripple.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_ripple.xml b/app/src/main/res/drawable/rounded_ripple.xml index 9fcec08167..522b28103e 100644 --- a/app/src/main/res/drawable/rounded_ripple.xml +++ b/app/src/main/res/drawable/rounded_ripple.xml @@ -1,10 +1,10 @@ + android:color="@color/gray_button"> - - + + @@ -24,7 +24,7 @@ - + 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 716d3f8c0a..11ad1260a2 100644 --- a/app/src/main/res/layout/chapter_sort_bottom_sheet.xml +++ b/app/src/main/res/layout/chapter_sort_bottom_sheet.xml @@ -12,7 +12,7 @@ android:layout_height="wrap_content"> - @@ -49,65 +48,9 @@ android:layout_height="?attr/actionBarSize" android:background="?colorPrimary" /> - + - - - - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/reader_chapters_sheet.xml b/app/src/main/res/layout/reader_chapters_sheet.xml new file mode 100644 index 0000000000..134556e8bd --- /dev/null +++ b/app/src/main/res/layout/reader_chapters_sheet.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 450fcea18c..e3dcb775b8 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -8,6 +8,7 @@