Pages thumbnails sheet

This commit is contained in:
Koitharu
2020-02-22 16:25:51 +02:00
parent 18889dcb39
commit 95ec1251c0
16 changed files with 330 additions and 10 deletions

View File

@@ -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<T, E>(private val onItemClickListener: OnRecy
protected val dataSet = ArrayList<T>()
val items get() = dataSet.toImmutableList()
init {
@Suppress("LeakingThis")
setHasStableIds(true)

View File

@@ -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())
}
}

View File

@@ -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<ReaderState>(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 {

View File

@@ -0,0 +1,8 @@
package org.koitharu.kotatsu.ui.reader.thumbnails
import org.koitharu.kotatsu.core.model.MangaPage
interface OnPageSelectListener {
fun onPageSelected(page: MangaPage)
}

View File

@@ -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<MangaPage, Unit>(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()
}
}
}
}

View File

@@ -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<MangaPage>?) :
BaseRecyclerAdapter<MangaPage, Unit>(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
}

View File

@@ -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<MangaPage> {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.addItemDecoration(SpacingItemDecoration(view.resources.resolveDp(8)))
val pages = arguments?.getParcelableArrayList<MangaPage>(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<MangaPage>, title: String) =
PagesThumbnailsSheet().withArgs(2) {
putParcelableArrayList(ARG_PAGES, ArrayList<MangaPage>(pages))
putString(ARG_TITLE, title)
}.show(fm, TAG)
}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,11 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@android:color/white"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
</vector>

View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
@@ -8,5 +7,5 @@
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M3,11H11V3H3M3,21H11V13H3M13,21H21V13H13M13,3V11H21V3" />
android:pathData="M3 11H11V3H3M5 5H9V9H5M13 21H21V13H13M15 15H19V19H15M3 21H11V13H3M5 15H9V19H5M13 3V11H21V3M19 9H15V5H19Z" />
</vector>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:background="?attr/scrimBackground"
android:layout_height="wrap_content">
<org.koitharu.kotatsu.ui.common.widgets.CoverImageView
android:id="@+id/imageView_thumb"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:text="2"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:padding="12dp"
android:gravity="end|bottom"
android:background="?android:selectableItemBackground"
android:id="@+id/textView_number" />
</FrameLayout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?android:windowBackground"
android:elevation="0dp"
app:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Widget.MaterialComponents.Toolbar.Surface"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="0dp"
android:outlineProvider="@null"
android:visibility="gone"
app:elevation="0dp"
app:navigationIcon="@drawable/ic_cross"
tools:visibility="visible" />
<TextView
android:id="@+id/textView_title"
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="16dp"
android:textColor="?android:textColorSecondary"
tools:visibility="gone" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:spanCount="3"
tools:listitem="@layout/item_page_thumb" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -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" />
<item
android:id="@+id/action_pages_thumbs"
android:icon="@drawable/ic_grid"
android:title="@string/pages"
app:showAsAction="ifRoom" />
<item

View File

@@ -6,4 +6,5 @@
<dimen name="chapter_list_item_height">46dp</dimen>
<dimen name="preferred_grid_width">120dp</dimen>
<dimen name="header_height">42dp</dimen>
<dimen name="elevation_large">16dp</dimen>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="pages">
<item quantity="one">Total %1$d page</item>
<item quantity="other">Total %1$d pages</item>
</plurals>
</resources>

View File

@@ -60,4 +60,5 @@
<string name="light">Light</string>
<string name="dark">Dark</string>
<string name="automatic">Automatic</string>
<string name="pages">Pages</string>
</resources>