diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/common/list/BaseRecyclerAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/common/list/BaseRecyclerAdapter.kt index ccb62092c..3491c234e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/common/list/BaseRecyclerAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/common/list/BaseRecyclerAdapter.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.ui.common.list import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView +import okhttp3.internal.toImmutableList import org.koin.core.KoinComponent import org.koitharu.kotatsu.utils.ext.replaceWith @@ -11,6 +12,8 @@ abstract class BaseRecyclerAdapter(private val onItemClickListener: OnRecy protected val dataSet = ArrayList() + val items get() = dataSet.toImmutableList() + init { @Suppress("LeakingThis") setHasStableIds(true) diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/main/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/main/MainActivity.kt index dbb2397ae..3cf982810 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/main/MainActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/main/MainActivity.kt @@ -35,8 +35,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList navigationView.setNavigationItemSelectedListener(this) if (supportFragmentManager.findFragmentById(R.id.container) == null) { - navigationView.setCheckedItem(R.id.nav_local_storage) - setPrimaryFragment(LocalListFragment.newInstance()) + navigationView.setCheckedItem(R.id.nav_history) + setPrimaryFragment(HistoryListFragment.newInstance()) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt index 70d06e017..eccbb85e3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt @@ -18,12 +18,14 @@ import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.ui.common.BaseFullscreenActivity +import org.koitharu.kotatsu.ui.reader.thumbnails.OnPageSelectListener +import org.koitharu.kotatsu.ui.reader.thumbnails.PagesThumbnailsSheet import org.koitharu.kotatsu.utils.GridTouchHelper import org.koitharu.kotatsu.utils.anim.Motion import org.koitharu.kotatsu.utils.ext.* class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnChapterChangeListener, - GridTouchHelper.OnGridTouchListener { + GridTouchHelper.OnGridTouchListener, OnPageSelectListener { private val presenter by moxyPresenter(factory = ::ReaderPresenter) @@ -39,6 +41,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh supportActionBar?.setDisplayHomeAsUpEnabled(true) touchHelper = GridTouchHelper(this, this) toolbar_bottom.inflateMenu(R.menu.opt_reader_bottom) + toolbar_bottom.setOnMenuItemClickListener(::onOptionsItemSelected) state = savedInstanceState?.getParcelable(EXTRA_STATE) ?: intent.getParcelableExtra(EXTRA_STATE) @@ -90,6 +93,13 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh ) true } + R.id.action_pages_thumbs -> { + PagesThumbnailsSheet.show( + supportFragmentManager, adapter.items, + state.chapter?.name ?: title?.toString().orEmpty() + ) + true + } else -> super.onOptionsItemSelected(item) } @@ -135,7 +145,11 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh } override fun onProcessTouch(rawX: Int, rawY: Int): Boolean { - return if (appbar_top.hasGlobalPoint(rawX, rawY) || appbar_bottom.hasGlobalPoint(rawX, rawY)) { + return if (appbar_top.hasGlobalPoint(rawX, rawY) || appbar_bottom.hasGlobalPoint( + rawX, + rawY + ) + ) { false } else { val target = rootLayout.hitTest(rawX, rawY) @@ -149,10 +163,19 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh } override fun onChapterChanged(chapter: MangaChapter) { - presenter.loadChapter(state.copy( - chapterId = chapter.id, - page = 0 - )) + presenter.loadChapter( + state.copy( + chapterId = chapter.id, + page = 0 + ) + ) + } + + override fun onPageSelected(page: MangaPage) { + val index = adapter.items.indexOfFirst { x -> x.id == page.id } + if (index != -1) { + pager.setCurrentItem(index, false) + } } companion object { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/OnPageSelectListener.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/OnPageSelectListener.kt new file mode 100644 index 000000000..5a2255e45 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/OnPageSelectListener.kt @@ -0,0 +1,8 @@ +package org.koitharu.kotatsu.ui.reader.thumbnails + +import org.koitharu.kotatsu.core.model.MangaPage + +interface OnPageSelectListener { + + fun onPageSelected(page: MangaPage) +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PageThumbnailHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PageThumbnailHolder.kt new file mode 100644 index 000000000..2e3e1da73 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PageThumbnailHolder.kt @@ -0,0 +1,46 @@ +package org.koitharu.kotatsu.ui.reader.thumbnails + +import android.view.ViewGroup +import coil.Coil +import coil.api.get +import kotlinx.android.synthetic.main.item_page_thumb.* +import kotlinx.coroutines.* +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.model.MangaPage +import org.koitharu.kotatsu.domain.MangaProviderFactory +import org.koitharu.kotatsu.ui.common.list.BaseViewHolder +import org.koitharu.kotatsu.utils.DrawUtils +import org.koitharu.kotatsu.utils.ext.resolveDp + +class PageThumbnailHolder(parent: ViewGroup, private val scope: CoroutineScope) : + BaseViewHolder(parent, R.layout.item_page_thumb) { + + private var job: Job? = null + + init { + val color = DrawUtils.invertColor(textView_number.currentTextColor) + textView_number.setShadowLayer(parent.resources.resolveDp(26f), 0f, 0f, color) + } + + override fun onBind(data: MangaPage, extra: Unit) { + imageView_thumb.setImageDrawable(null) + textView_number.text = (adapterPosition + 1).toString() + job?.cancel() + job = scope.launch(Dispatchers.IO) { + try { + val url = data.preview ?: data.url.let { + MangaProviderFactory.create(data.source).getPageFullUrl(data) + } + val drawable = Coil.get(url) { + + } + withContext(Dispatchers.Main) { + imageView_thumb.setImageDrawable(drawable) + } + } catch (e: CancellationException) { + } catch (e: Exception) { + e.printStackTrace() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PagesThumbnailsAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PagesThumbnailsAdapter.kt new file mode 100644 index 000000000..12e9d46fc --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PagesThumbnailsAdapter.kt @@ -0,0 +1,30 @@ +package org.koitharu.kotatsu.ui.reader.thumbnails + +import android.view.ViewGroup +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.SupervisorJob +import org.koitharu.kotatsu.core.model.MangaPage +import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter +import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener +import kotlin.coroutines.CoroutineContext + +class PagesThumbnailsAdapter(onItemClickListener: OnRecyclerItemClickListener?) : + BaseRecyclerAdapter(onItemClickListener), CoroutineScope, DisposableHandle { + + private val job = SupervisorJob() + + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + job + + override fun dispose() { + job.cancel() + } + + override fun getExtra(item: MangaPage, position: Int) = Unit + + override fun onCreateViewHolder(parent: ViewGroup) = PageThumbnailHolder(parent, this) + + override fun onGetItemId(item: MangaPage) = item.id +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PagesThumbnailsSheet.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PagesThumbnailsSheet.kt new file mode 100644 index 000000000..42016540b --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/PagesThumbnailsSheet.kt @@ -0,0 +1,91 @@ +package org.koitharu.kotatsu.ui.reader.thumbnails + +import android.os.Bundle +import android.view.View +import androidx.core.view.isVisible +import androidx.fragment.app.FragmentManager +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import kotlinx.android.synthetic.main.sheet_pages.* +import kotlinx.coroutines.DisposableHandle +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.model.MangaPage +import org.koitharu.kotatsu.ui.common.BaseBottomSheet +import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener +import org.koitharu.kotatsu.ui.common.list.decor.SpacingItemDecoration +import org.koitharu.kotatsu.utils.ext.resolveDp +import org.koitharu.kotatsu.utils.ext.withArgs + +class PagesThumbnailsSheet : BaseBottomSheet(R.layout.sheet_pages), + OnRecyclerItemClickListener { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + recyclerView.addItemDecoration(SpacingItemDecoration(view.resources.resolveDp(8))) + val pages = arguments?.getParcelableArrayList(ARG_PAGES) + if (pages != null) { + recyclerView.adapter = PagesThumbnailsAdapter(this).apply { + replaceData(pages) + } + } else { + dismissAllowingStateLoss() + return + } + val title = arguments?.getString(ARG_TITLE) + toolbar.title = title + toolbar.setNavigationOnClickListener { dismiss() } + toolbar.subtitle = resources.getQuantityString(R.plurals.pages, pages.size, pages.size) + textView_title.text = title + } + + override fun onCreateDialog(savedInstanceState: Bundle?) = + super.onCreateDialog(savedInstanceState).also { + val behavior = (it as BottomSheetDialog).behavior + behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { + private val elevation = resources.getDimension(R.dimen.elevation_large) + + override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit + + override fun onStateChanged(bottomSheet: View, newState: Int) { + if (newState == BottomSheetBehavior.STATE_EXPANDED) { + toolbar.isVisible = true + appbar.elevation = elevation + } else { + toolbar.isVisible = false + appbar.elevation = 0f + } + } + }) + behavior.peekHeight = BottomSheetBehavior.PEEK_HEIGHT_AUTO + behavior.isFitToContents = false + } + + override fun onDestroyView() { + (recyclerView.adapter as? DisposableHandle)?.dispose() + recyclerView.adapter = null + super.onDestroyView() + } + + override fun onItemClick(item: MangaPage, position: Int, view: View) { + ((parentFragment as? OnPageSelectListener) + ?: (activity as? OnPageSelectListener))?.run { + onPageSelected(item) + dismiss() + } + } + + companion object { + + private const val ARG_PAGES = "pages" + private const val ARG_TITLE = "title" + + private const val TAG = "PagesThumbnailsSheet" + + fun show(fm: FragmentManager, pages: List, title: String) = + PagesThumbnailsSheet().withArgs(2) { + putParcelableArrayList(ARG_PAGES, ArrayList(pages)) + putString(ARG_TITLE, title) + }.show(fm, TAG) + + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/DrawUtils.kt b/app/src/main/java/org/koitharu/kotatsu/utils/DrawUtils.kt new file mode 100644 index 000000000..2837d9630 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/DrawUtils.kt @@ -0,0 +1,17 @@ +package org.koitharu.kotatsu.utils + +import android.graphics.Color +import androidx.annotation.ColorInt + +object DrawUtils { + + @JvmStatic + @ColorInt + fun invertColor(@ColorInt color: Int): Int { + val red = Color.red(color) + val green = Color.green(color) + val blue = Color.blue(color) + val alpha = Color.alpha(color) + return Color.argb(alpha, 255 - red, 255 - green, 255 - blue) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_cross.xml b/app/src/main/res/drawable/ic_cross.xml new file mode 100644 index 000000000..5cfade110 --- /dev/null +++ b/app/src/main/res/drawable/ic_cross.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_grid.xml b/app/src/main/res/drawable/ic_grid.xml index f7c6ef71b..0b00afe73 100644 --- a/app/src/main/res/drawable/ic_grid.xml +++ b/app/src/main/res/drawable/ic_grid.xml @@ -1,4 +1,3 @@ - + android:pathData="M3 11H11V3H3M5 5H9V9H5M13 21H21V13H13M15 15H19V19H15M3 21H11V13H3M5 15H9V19H5M13 3V11H21V3M19 9H15V5H19Z" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_page_thumb.xml b/app/src/main/res/layout/item_page_thumb.xml new file mode 100644 index 000000000..fc9202cbb --- /dev/null +++ b/app/src/main/res/layout/item_page_thumb.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_pages.xml b/app/src/main/res/layout/sheet_pages.xml new file mode 100644 index 000000000..89cdc0745 --- /dev/null +++ b/app/src/main/res/layout/sheet_pages.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/opt_reader_bottom.xml b/app/src/main/res/menu/opt_reader_bottom.xml index ed8abf64a..cd018def0 100644 --- a/app/src/main/res/menu/opt_reader_bottom.xml +++ b/app/src/main/res/menu/opt_reader_bottom.xml @@ -7,6 +7,13 @@ android:id="@+id/action_bookmark_add" android:icon="@drawable/ic_bookmark_add" android:title="@string/add_bookmark" + android:visible="false" + app:showAsAction="ifRoom" /> + + 46dp 120dp 42dp + 16dp \ No newline at end of file diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml new file mode 100644 index 000000000..b4a4af465 --- /dev/null +++ b/app/src/main/res/values/plurals.xml @@ -0,0 +1,7 @@ + + + + Total %1$d page + Total %1$d pages + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 69bc6e79a..6397cf6e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -60,4 +60,5 @@ Light Dark Automatic + Pages \ No newline at end of file