Webtoon reader
parent
646d5df476
commit
6cf9e69f99
@ -0,0 +1,24 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class PreferencesDao {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM preferences WHERE manga_id = :mangaId")
|
||||||
|
abstract suspend fun find(mangaId: Long): MangaPrefsEntity?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
abstract suspend fun insert(pref: MangaPrefsEntity): Long
|
||||||
|
|
||||||
|
@Update
|
||||||
|
abstract suspend fun update(pref: MangaPrefsEntity): Int
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open suspend fun upsert(pref: MangaPrefsEntity) {
|
||||||
|
if (update(pref) == 0) {
|
||||||
|
insert(pref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db.entity
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "preferences")
|
||||||
|
data class MangaPrefsEntity(
|
||||||
|
@PrimaryKey(autoGenerate = false)
|
||||||
|
@ColumnInfo(name = "manga_id") val mangaId: Long,
|
||||||
|
@ColumnInfo(name = "mode") val mode: Int
|
||||||
|
)
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package org.koitharu.kotatsu.core.prefs
|
||||||
|
|
||||||
|
enum class ReaderMode(val id: Int) {
|
||||||
|
|
||||||
|
UNKNOWN(0),
|
||||||
|
STANDARD(1),
|
||||||
|
WEBTOON(2);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun valueOf(id: Int) = values().firstOrNull { it.id == id }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package org.koitharu.kotatsu.domain
|
||||||
|
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
import org.koin.core.inject
|
||||||
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity
|
||||||
|
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||||
|
|
||||||
|
class MangaPreferencesRepository : KoinComponent {
|
||||||
|
|
||||||
|
private val db: MangaDatabase by inject()
|
||||||
|
|
||||||
|
suspend fun saveData(mangaId: Long, mode: ReaderMode) {
|
||||||
|
db.preferencesDao().upsert(
|
||||||
|
MangaPrefsEntity(
|
||||||
|
mangaId = mangaId,
|
||||||
|
mode = mode.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getReaderMode(mangaId: Long): ReaderMode {
|
||||||
|
return db.preferencesDao().find(mangaId)?.let { ReaderMode.valueOf(it.mode) }
|
||||||
|
?: ReaderMode.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.reader.wetoon
|
||||||
|
|
||||||
|
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 WebtoonAdapter(private val loader: PageLoader) : BaseRecyclerAdapter<MangaPage, Unit>() {
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup) =
|
||||||
|
WebtoonHolder(parent, loader)
|
||||||
|
|
||||||
|
override fun onGetItemId(item: MangaPage) = item.id
|
||||||
|
|
||||||
|
override fun getExtra(item: MangaPage, position: Int) = Unit
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.reader.wetoon
|
||||||
|
|
||||||
|
import android.graphics.PointF
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
|
import kotlinx.android.synthetic.main.item_page.*
|
||||||
|
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 WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||||
|
BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page),
|
||||||
|
SubsamplingScaleImageView.OnImageEventListener, CoroutineScope by loader {
|
||||||
|
|
||||||
|
private var job: Job? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
ssiv.setOnImageEventListener(this)
|
||||||
|
button_retry.setOnClickListener {
|
||||||
|
doLoad(boundData ?: return@setOnClickListener, force = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(data: MangaPage, extra: Unit) {
|
||||||
|
doLoad(data, force = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doLoad(data: MangaPage, force: Boolean) {
|
||||||
|
job?.cancel()
|
||||||
|
job = launch {
|
||||||
|
layout_error.isVisible = false
|
||||||
|
progressBar.isVisible = true
|
||||||
|
ssiv.recycle()
|
||||||
|
try {
|
||||||
|
val uri = withContext(Dispatchers.IO) {
|
||||||
|
loader.loadFile(data.url, force)
|
||||||
|
}.toUri()
|
||||||
|
ssiv.setImage(ImageSource.uri(uri))
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
//do nothing
|
||||||
|
} catch (e: Exception) {
|
||||||
|
onError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReady() {
|
||||||
|
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
||||||
|
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat()
|
||||||
|
ssiv.setScaleAndCenter(
|
||||||
|
ssiv.minScale,
|
||||||
|
PointF(ssiv.sWidth / 2f, 0f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onImageLoadError(e: Exception) = onError(e)
|
||||||
|
|
||||||
|
override fun onImageLoaded() {
|
||||||
|
progressBar.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTileLoadError(e: Exception?) = Unit
|
||||||
|
|
||||||
|
override fun onPreviewReleased() = Unit
|
||||||
|
|
||||||
|
override fun onPreviewLoadError(e: Exception?) = Unit
|
||||||
|
|
||||||
|
private fun onError(e: Throwable) {
|
||||||
|
textView_error.text = e.getDisplayMessage(context.resources)
|
||||||
|
layout_error.isVisible = true
|
||||||
|
progressBar.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.reader.wetoon
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import kotlinx.android.synthetic.main.fragment_reader_webtoon.*
|
||||||
|
import moxy.ktx.moxyPresenter
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.model.MangaPage
|
||||||
|
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||||
|
import org.koitharu.kotatsu.ui.reader.BaseReaderFragment
|
||||||
|
import org.koitharu.kotatsu.ui.reader.PageLoader
|
||||||
|
import org.koitharu.kotatsu.ui.reader.ReaderPresenter
|
||||||
|
import org.koitharu.kotatsu.ui.reader.ReaderState
|
||||||
|
import org.koitharu.kotatsu.utils.ext.firstItem
|
||||||
|
|
||||||
|
class WebtoonReaderFragment : BaseReaderFragment(R.layout.fragment_reader_webtoon) {
|
||||||
|
|
||||||
|
private val presenter by moxyPresenter(factory = ReaderPresenter.Companion::getInstance)
|
||||||
|
|
||||||
|
private var adapter: WebtoonAdapter? = 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 = WebtoonAdapter(loader)
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInitReader(pages: List<MangaPage>, mode: ReaderMode, state: ReaderState) {
|
||||||
|
adapter?.let {
|
||||||
|
it.replaceData(pages)
|
||||||
|
recyclerView.firstItem = state.page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
loader.dispose()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val hasItems: Boolean
|
||||||
|
get() = adapter?.hasItems == true
|
||||||
|
|
||||||
|
override val currentPageIndex: Int
|
||||||
|
get() = recyclerView.firstItem
|
||||||
|
|
||||||
|
override val pages: List<MangaPage>
|
||||||
|
get() = adapter?.items.orEmpty()
|
||||||
|
|
||||||
|
override fun setCurrentPage(index: Int, smooth: Boolean) {
|
||||||
|
if (smooth) {
|
||||||
|
recyclerView.smoothScrollToPosition(index)
|
||||||
|
} else {
|
||||||
|
recyclerView.firstItem = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
Loading…
Reference in New Issue