Refactor reader

This commit is contained in:
Koitharu
2020-02-24 19:58:16 +02:00
parent e6befc57e0
commit de9e773d2c
13 changed files with 187 additions and 53 deletions

View File

@@ -5,5 +5,6 @@ import java.io.FilenameFilter
class CbzFilter : FilenameFilter {
override fun accept(dir: File, name: String) = name.endsWith(".cbz", ignoreCase = true)
override fun accept(dir: File, name: String) =
name.endsWith(".cbz", ignoreCase = true) || name.endsWith(".zip", ignoreCase = true)
}

View File

@@ -16,6 +16,7 @@ import org.koitharu.kotatsu.utils.ext.readText
import org.koitharu.kotatsu.utils.ext.safe
import java.io.File
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
class LocalMangaRepository(loaderContext: MangaLoaderContext) : BaseMangaRepository(loaderContext) {
@@ -68,7 +69,11 @@ class LocalMangaRepository(loaderContext: MangaLoaderContext) : BaseMangaReposit
x.copy(
source = MangaSource.LOCAL,
url = fileUri,
coverUrl = zipUri(file, it.getCoverEntry() ?: zip.entries().nextElement().name),
coverUrl = zipUri(
file,
entryName = it.getCoverEntry()
?: findFirstEntry(zip.entries())?.name.orEmpty()
),
chapters = x.chapters?.map { c -> c.copy(url = fileUri) }
)
}
@@ -79,7 +84,7 @@ class LocalMangaRepository(loaderContext: MangaLoaderContext) : BaseMangaReposit
title = title,
url = fileUri,
source = MangaSource.LOCAL,
coverUrl = zipUri(file, zip.entries().nextElement().name),
coverUrl = zipUri(file, findFirstEntry(zip.entries())?.name.orEmpty()),
chapters = listOf(
MangaChapter(
id = file.absolutePath.longHashCode(),
@@ -101,6 +106,13 @@ class LocalMangaRepository(loaderContext: MangaLoaderContext) : BaseMangaReposit
private fun zipUri(file: File, entryName: String) =
Uri.fromParts("cbz", file.path, entryName).toString()
private fun findFirstEntry(entries: Enumeration<out ZipEntry>): ZipEntry? {
val list = entries.toList()
.filterNot { it.isDirectory }
.sortedWith(compareBy(AlphanumComparator()) { x -> x.name })
return list.firstOrNull()
}
companion object {
fun isFileSupported(name: String): Boolean {

View File

@@ -36,7 +36,6 @@ class DownloadNotification(private val context: Context) {
builder.setContentText(context.getString(R.string.manga_downloading_))
builder.setProgress(1, 0, true)
builder.setSmallIcon(android.R.drawable.stat_sys_download)
builder.setSubText(context.getText(R.string.preparing_))
builder.setLargeIcon(null)
}
@@ -50,19 +49,18 @@ class DownloadNotification(private val context: Context) {
chapter * PROGRESS_STEP + (page / pagesTotal.toFloat() * PROGRESS_STEP).roundToInt()
val percent = (progress / max.toFloat() * 100).roundToInt()
builder.setProgress(max, progress, false)
builder.setSubText("$percent%")
builder.setContentText(context.getString(R.string.downloading_d_percent, percent))
}
fun setPostProcessing() {
builder.setProgress(1, 0, true)
builder.setSubText(context.getString(R.string.processing_))
builder.setContentText(context.getString(R.string.processing_))
}
fun setDone() {
builder.setProgress(0, 0, false)
builder.setContentText(context.getString(R.string.download_complete))
builder.setSmallIcon(android.R.drawable.stat_sys_download_done)
builder.setSubText(null)
}
fun update(id: Int = NOTIFICATION_ID) {

View File

@@ -0,0 +1,34 @@
package org.koitharu.kotatsu.ui.reader
import android.net.Uri
import androidx.annotation.LayoutRes
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.common.BaseFragment
abstract class BaseReaderFragment(@LayoutRes contentLayoutId: Int) : BaseFragment(contentLayoutId), ReaderView {
abstract val hasItems: Boolean
abstract val currentPageIndex: Int
abstract val pages: List<MangaPage>
abstract fun setCurrentPage(index: Int, smooth: Boolean)
val currentPage get() = pages.getOrNull(currentPageIndex)
/**
* Handled by activity
*/
override fun onLoadingStateChanged(isLoading: Boolean) = Unit
/**
* Handled by activity
*/
override fun onError(e: Exception) = Unit
/**
* Handled by activity
*/
override fun onPageSaved(uri: Uri?) = Unit
}

View File

@@ -12,6 +12,7 @@ import android.view.MotionEvent
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_reader.*
import moxy.MvpDelegate
@@ -22,6 +23,7 @@ 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.standard.StandardReaderFragment
import org.koitharu.kotatsu.ui.reader.thumbnails.OnPageSelectListener
import org.koitharu.kotatsu.ui.reader.thumbnails.PagesThumbnailsSheet
import org.koitharu.kotatsu.utils.GridTouchHelper
@@ -32,14 +34,15 @@ import org.koitharu.kotatsu.utils.ext.*
class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnChapterChangeListener,
GridTouchHelper.OnGridTouchListener, OnPageSelectListener {
private val presenter by moxyPresenter(factory = ::ReaderPresenter)
private val presenter by moxyPresenter(factory = ReaderPresenter.Companion::getInstance)
private lateinit var state: ReaderState
private lateinit var loader: PageLoader
private lateinit var adapter: PagesAdapter
private lateinit var touchHelper: GridTouchHelper
private val reader
get() = supportFragmentManager.findFragmentById(R.id.container) as? BaseReaderFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_reader)
@@ -67,23 +70,22 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
insets
}
loader = PageLoader()
adapter = PagesAdapter(loader)
pager.adapter = adapter
pager.offscreenPageLimit = 2
if (reader == null) {
supportFragmentManager.commit {
replace(R.id.container, StandardReaderFragment())
}
}
if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) {
presenter.loadChapter(state)
}
}
override fun onDestroy() {
loader.dispose()
super.onDestroy()
}
override fun onPause() {
state = state.copy(page = pager.currentItem)
presenter.saveState(state)
reader?.let {
state = state.copy(page = it.currentPageIndex)
presenter.saveState(state)
}
super.onPause()
}
@@ -102,9 +104,9 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
true
}
R.id.action_pages_thumbs -> {
if (adapter.hasItems) {
if (reader?.hasItems == true) {
PagesThumbnailsSheet.show(
supportFragmentManager, adapter.items,
supportFragmentManager, reader!!.pages,
state.chapter?.name ?: title?.toString().orEmpty()
)
} else {
@@ -113,10 +115,13 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
true
}
R.id.action_save_page -> {
if (adapter.hasItems) {
if (reader?.hasItems == true) {
requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) {
if (it) {
presenter.savePage(contentResolver, adapter.getItem(pager.currentItem))
presenter.savePage(
resolver = contentResolver,
page = reader?.currentPage ?: return@requestPermission
)
}
}
} else {
@@ -128,8 +133,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
}
override fun onPagesReady(pages: List<MangaPage>, index: Int) {
adapter.replaceData(pages)
pager.setCurrentItem(index, false)
}
override fun onLoadingStateChanged(isLoading: Boolean) {
@@ -141,7 +145,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
setTitle(R.string.error_occurred)
setMessage(e.message)
setPositiveButton(R.string.close, null)
if (!adapter.hasItems) {
if (reader?.hasItems != true) {
setOnDismissListener {
finish()
}
@@ -164,20 +168,22 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
}
GridTouchHelper.AREA_TOP,
GridTouchHelper.AREA_LEFT -> {
pager.setCurrentItem(pager.currentItem - 1, true)
reader?.let {
it.setCurrentPage(it.currentPageIndex - 1, true)
}
}
GridTouchHelper.AREA_BOTTOM,
GridTouchHelper.AREA_RIGHT -> {
pager.setCurrentItem(pager.currentItem + 1, true)
reader?.let {
it.setCurrentPage(it.currentPageIndex + 1, true)
}
}
}
}
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 {
@@ -201,20 +207,22 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
}
override fun onPageSelected(page: MangaPage) {
val index = adapter.items.indexOfFirst { x -> x.id == page.id }
if (index != -1) {
pager.setCurrentItem(index, false)
reader?.let {
val index = it.pages.indexOfFirst { x -> x.id == page.id }
if (index != -1) {
it.setCurrentPage(index, false)
}
}
}
override fun onPageSaved(uri: Uri?) {
if (uri != null) {
Snackbar.make(pager, R.string.page_saved, Snackbar.LENGTH_LONG)
Snackbar.make(container, R.string.page_saved, Snackbar.LENGTH_LONG)
.setAction(R.string.share) {
ShareHelper.shareImage(this, uri)
}.show()
} else {
Snackbar.make(pager, R.string.error_occurred, Snackbar.LENGTH_SHORT).show()
Snackbar.make(container, R.string.error_occurred, Snackbar.LENGTH_SHORT).show()
}
}

View File

@@ -84,4 +84,20 @@ class ReaderPresenter : BasePresenter<ReaderView>() {
}
}
override fun onDestroy() {
instance = null
super.onDestroy()
}
companion object {
private var instance: ReaderPresenter? = null
fun getInstance(): ReaderPresenter = instance ?: synchronized(this) {
ReaderPresenter().also {
instance = it
}
}
}
}

View File

@@ -2,22 +2,21 @@ package org.koitharu.kotatsu.ui.reader
import android.net.Uri
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.OneExecutionStateStrategy
import moxy.viewstate.strategy.StateStrategyType
import moxy.viewstate.strategy.alias.AddToEndSingle
import moxy.viewstate.strategy.alias.OneExecution
import org.koitharu.kotatsu.core.model.MangaPage
interface ReaderView : MvpView {
@StateStrategyType(AddToEndSingleStrategy::class)
@AddToEndSingle
fun onPagesReady(pages: List<MangaPage>, index: Int)
@StateStrategyType(AddToEndSingleStrategy::class)
@AddToEndSingle
fun onLoadingStateChanged(isLoading: Boolean)
@StateStrategyType(OneExecutionStateStrategy::class)
@OneExecution
fun onError(e: Exception)
@StateStrategyType(OneExecutionStateStrategy::class)
@OneExecution
fun onPageSaved(uri: Uri?)
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.reader
package org.koitharu.kotatsu.ui.reader.standard
import android.view.ViewGroup
import androidx.core.net.toUri
@@ -10,6 +10,7 @@ import kotlinx.coroutines.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class PageHolder(parent: ViewGroup, private val loader: PageLoader) :

View File

@@ -1,12 +1,14 @@
package org.koitharu.kotatsu.ui.reader
package org.koitharu.kotatsu.ui.reader.standard
import android.view.ViewGroup
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter
import org.koitharu.kotatsu.ui.reader.PageLoader
class PagesAdapter(private val loader: PageLoader) : BaseRecyclerAdapter<MangaPage, Unit>() {
override fun onCreateViewHolder(parent: ViewGroup) = PageHolder(parent, loader)
override fun onCreateViewHolder(parent: ViewGroup) =
PageHolder(parent, loader)
override fun onGetItemId(item: MangaPage) = item.id

View File

@@ -0,0 +1,56 @@
package org.koitharu.kotatsu.ui.reader.standard
import android.os.Bundle
import android.view.View
import kotlinx.android.synthetic.main.fragment_reader_standard.*
import moxy.ktx.moxyPresenter
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.reader.BaseReaderFragment
import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.ReaderPresenter
class StandardReaderFragment : BaseReaderFragment(R.layout.fragment_reader_standard) {
private val presenter by moxyPresenter(factory = ReaderPresenter.Companion::getInstance)
private var adapter: PagesAdapter? = null
private lateinit var loader: PageLoader
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loader = PageLoader()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = PagesAdapter(loader)
pager.adapter = adapter
pager.offscreenPageLimit = 2
}
override fun onPagesReady(pages: List<MangaPage>, index: Int) {
adapter?.let {
it.replaceData(pages)
pager.setCurrentItem(index, false)
}
}
override fun onDestroy() {
loader.dispose()
super.onDestroy()
}
override val hasItems: Boolean
get() = adapter?.hasItems == true
override val currentPageIndex: Int
get() = pager.currentItem
override val pages: List<MangaPage>
get() = adapter?.items.orEmpty()
override fun setCurrentPage(index: Int, smooth: Boolean) {
pager.setCurrentItem(index, smooth)
}
}

View File

@@ -4,11 +4,11 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:keepScreenOn="true">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:keepScreenOn="true"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.viewpager2.widget.ViewPager2
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -79,4 +79,5 @@
<string name="clear_pages_cache">Clear pages cache</string>
<string name="cache">Cache</string>
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
<string name="downloading_d_percent">Downloading: %d%%</string>
</resources>