Refactor reader
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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?)
|
||||
}
|
||||
@@ -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) :
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
|
||||
6
app/src/main/res/layout/fragment_reader_standard.xml
Normal file
6
app/src/main/res/layout/fragment_reader_standard.xml
Normal 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" />
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user