Webtoon reader
This commit is contained in:
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.core.db.entity.*
|
|||||||
@Database(
|
@Database(
|
||||||
entities = [
|
entities = [
|
||||||
MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class,
|
MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class,
|
||||||
FavouriteCategoryEntity::class, FavouriteEntity::class
|
FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class
|
||||||
], version = 1
|
], version = 1
|
||||||
)
|
)
|
||||||
abstract class MangaDatabase : RoomDatabase() {
|
abstract class MangaDatabase : RoomDatabase() {
|
||||||
@@ -20,5 +20,7 @@ abstract class MangaDatabase : RoomDatabase() {
|
|||||||
|
|
||||||
abstract fun favouritesDao(): FavouritesDao
|
abstract fun favouritesDao(): FavouritesDao
|
||||||
|
|
||||||
|
abstract fun preferencesDao(): PreferencesDao
|
||||||
|
|
||||||
abstract fun favouriteCategoriesDao(): FavouriteCategoriesDao
|
abstract fun favouriteCategoriesDao(): FavouriteCategoriesDao
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,7 +46,6 @@ data class MangaEntity(
|
|||||||
altTitle = manga.altTitle,
|
altTitle = manga.altTitle,
|
||||||
rating = manga.rating,
|
rating = manga.rating,
|
||||||
state = manga.state?.name,
|
state = manga.state?.name,
|
||||||
// tags = manga.tags.map(TagEntity.Companion::fromMangaTag),
|
|
||||||
title = manga.title
|
title = manga.title
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,14 @@ import org.koitharu.kotatsu.ui.common.AlertDialogFragment
|
|||||||
|
|
||||||
class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), View.OnClickListener {
|
class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), View.OnClickListener {
|
||||||
|
|
||||||
private val setting by inject<AppSettings>()
|
private val settings by inject<AppSettings>()
|
||||||
|
|
||||||
|
private lateinit var mode: ListMode
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
mode = settings.listMode
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBuildDialog(builder: AlertDialog.Builder) {
|
override fun onBuildDialog(builder: AlertDialog.Builder) {
|
||||||
builder.setTitle(R.string.list_mode)
|
builder.setTitle(R.string.list_mode)
|
||||||
@@ -22,7 +29,6 @@ class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), Vie
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
val mode = setting.listMode
|
|
||||||
button_list.isChecked = mode == ListMode.LIST
|
button_list.isChecked = mode == ListMode.LIST
|
||||||
button_list_detailed.isChecked = mode == ListMode.DETAILED_LIST
|
button_list_detailed.isChecked = mode == ListMode.DETAILED_LIST
|
||||||
button_grid.isChecked = mode == ListMode.GRID
|
button_grid.isChecked = mode == ListMode.GRID
|
||||||
@@ -35,10 +41,13 @@ class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), Vie
|
|||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
R.id.button_ok -> dismiss()
|
R.id.button_ok -> {
|
||||||
R.id.button_list -> setting.listMode = ListMode.LIST
|
settings.listMode = mode
|
||||||
R.id.button_list_detailed -> setting.listMode = ListMode.DETAILED_LIST
|
dismiss()
|
||||||
R.id.button_grid -> setting.listMode = ListMode.GRID
|
}
|
||||||
|
R.id.button_list -> mode = ListMode.LIST
|
||||||
|
R.id.button_list_detailed -> mode = ListMode.DETAILED_LIST
|
||||||
|
R.id.button_grid -> mode = ListMode.GRID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,24 +15,25 @@ import androidx.core.view.updatePadding
|
|||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.android.synthetic.main.activity_reader.*
|
import kotlinx.android.synthetic.main.activity_reader.*
|
||||||
import moxy.MvpDelegate
|
|
||||||
import moxy.ktx.moxyPresenter
|
import moxy.ktx.moxyPresenter
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.model.Manga
|
import org.koitharu.kotatsu.core.model.Manga
|
||||||
import org.koitharu.kotatsu.core.model.MangaChapter
|
import org.koitharu.kotatsu.core.model.MangaChapter
|
||||||
import org.koitharu.kotatsu.core.model.MangaHistory
|
import org.koitharu.kotatsu.core.model.MangaHistory
|
||||||
import org.koitharu.kotatsu.core.model.MangaPage
|
import org.koitharu.kotatsu.core.model.MangaPage
|
||||||
|
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||||
import org.koitharu.kotatsu.ui.common.BaseFullscreenActivity
|
import org.koitharu.kotatsu.ui.common.BaseFullscreenActivity
|
||||||
import org.koitharu.kotatsu.ui.reader.standard.StandardReaderFragment
|
import org.koitharu.kotatsu.ui.reader.standard.StandardReaderFragment
|
||||||
import org.koitharu.kotatsu.ui.reader.thumbnails.OnPageSelectListener
|
import org.koitharu.kotatsu.ui.reader.thumbnails.OnPageSelectListener
|
||||||
import org.koitharu.kotatsu.ui.reader.thumbnails.PagesThumbnailsSheet
|
import org.koitharu.kotatsu.ui.reader.thumbnails.PagesThumbnailsSheet
|
||||||
|
import org.koitharu.kotatsu.ui.reader.wetoon.WebtoonReaderFragment
|
||||||
import org.koitharu.kotatsu.utils.GridTouchHelper
|
import org.koitharu.kotatsu.utils.GridTouchHelper
|
||||||
import org.koitharu.kotatsu.utils.ShareHelper
|
import org.koitharu.kotatsu.utils.ShareHelper
|
||||||
import org.koitharu.kotatsu.utils.anim.Motion
|
import org.koitharu.kotatsu.utils.anim.Motion
|
||||||
import org.koitharu.kotatsu.utils.ext.*
|
import org.koitharu.kotatsu.utils.ext.*
|
||||||
|
|
||||||
class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnChapterChangeListener,
|
class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnChapterChangeListener,
|
||||||
GridTouchHelper.OnGridTouchListener, OnPageSelectListener {
|
GridTouchHelper.OnGridTouchListener, OnPageSelectListener, ReaderConfigDialog.Callback {
|
||||||
|
|
||||||
private val presenter by moxyPresenter(factory = ReaderPresenter.Companion::getInstance)
|
private val presenter by moxyPresenter(factory = ReaderPresenter.Companion::getInstance)
|
||||||
|
|
||||||
@@ -70,14 +71,22 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
|
|||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader == null) {
|
presenter.loadChapter(state)
|
||||||
supportFragmentManager.commit {
|
}
|
||||||
replace(R.id.container, StandardReaderFragment())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) {
|
override fun onInitReader(pages: List<MangaPage>, mode: ReaderMode, state: ReaderState) {
|
||||||
presenter.loadChapter(state)
|
val currentReader = reader
|
||||||
|
when (mode) {
|
||||||
|
ReaderMode.WEBTOON -> if (currentReader !is WebtoonReaderFragment) {
|
||||||
|
supportFragmentManager.commit {
|
||||||
|
replace(R.id.container, WebtoonReaderFragment())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> if (currentReader !is StandardReaderFragment) {
|
||||||
|
supportFragmentManager.commit {
|
||||||
|
replace(R.id.container, StandardReaderFragment())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,9 +103,20 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
|
|||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putParcelable(EXTRA_STATE, state)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
R.id.action_settings -> {
|
R.id.action_settings -> {
|
||||||
ReaderConfigDialog.show(supportFragmentManager)
|
ReaderConfigDialog.show(
|
||||||
|
supportFragmentManager, when (reader) {
|
||||||
|
is StandardReaderFragment -> ReaderMode.STANDARD
|
||||||
|
is WebtoonReaderFragment -> ReaderMode.WEBTOON
|
||||||
|
else -> ReaderMode.UNKNOWN
|
||||||
|
}
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.action_chapters -> {
|
R.id.action_chapters -> {
|
||||||
@@ -136,10 +156,6 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
|
|||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPagesReady(pages: List<MangaPage>, index: Int) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoadingStateChanged(isLoading: Boolean) {
|
override fun onLoadingStateChanged(isLoading: Boolean) {
|
||||||
layout_loading.isVisible = isLoading
|
layout_loading.isVisible = isLoading
|
||||||
}
|
}
|
||||||
@@ -202,12 +218,11 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onChapterChanged(chapter: MangaChapter) {
|
override fun onChapterChanged(chapter: MangaChapter) {
|
||||||
presenter.loadChapter(
|
state = state.copy(
|
||||||
state.copy(
|
chapterId = chapter.id,
|
||||||
chapterId = chapter.id,
|
page = 0
|
||||||
page = 0
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
presenter.loadChapter(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPageSelected(page: MangaPage) {
|
override fun onPageSelected(page: MangaPage) {
|
||||||
@@ -219,6 +234,14 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onReaderModeChanged(mode: ReaderMode) {
|
||||||
|
reader?.let {
|
||||||
|
state = state.copy(page = it.currentPageIndex)
|
||||||
|
}
|
||||||
|
presenter.saveState(state, mode)
|
||||||
|
recreate()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPageSaved(uri: Uri?) {
|
override fun onPageSaved(uri: Uri?) {
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
Snackbar.make(container, R.string.page_saved, Snackbar.LENGTH_LONG)
|
Snackbar.make(container, R.string.page_saved, Snackbar.LENGTH_LONG)
|
||||||
|
|||||||
@@ -4,33 +4,65 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import kotlinx.android.synthetic.main.dialog_list_mode.button_ok
|
||||||
|
import kotlinx.android.synthetic.main.dialog_reader_config.*
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||||
import org.koitharu.kotatsu.ui.common.AlertDialogFragment
|
import org.koitharu.kotatsu.ui.common.AlertDialogFragment
|
||||||
|
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||||
|
|
||||||
class ReaderConfigDialog : AlertDialogFragment(R.layout.dialog_reader_config),
|
class ReaderConfigDialog : AlertDialogFragment(R.layout.dialog_reader_config),
|
||||||
View.OnClickListener {
|
View.OnClickListener {
|
||||||
|
|
||||||
|
private lateinit var mode: ReaderMode
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
mode = arguments?.getInt(ARG_MODE, ReaderMode.UNKNOWN.id)
|
||||||
|
?.let { ReaderMode.valueOf(it) }
|
||||||
|
?.takeUnless { it == ReaderMode.UNKNOWN }
|
||||||
|
?: ReaderMode.STANDARD
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBuildDialog(builder: AlertDialog.Builder) {
|
override fun onBuildDialog(builder: AlertDialog.Builder) {
|
||||||
builder//.setTitle(R.string.list_mode)
|
builder.setTitle(R.string.read_mode)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
button_standard.isChecked = mode == ReaderMode.STANDARD
|
||||||
|
button_webtoon.isChecked = mode == ReaderMode.WEBTOON
|
||||||
|
|
||||||
|
button_ok.setOnClickListener(this)
|
||||||
|
button_standard.setOnClickListener(this)
|
||||||
|
button_webtoon.setOnClickListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
R.id.button_ok -> dismiss()
|
R.id.button_ok -> {
|
||||||
|
((parentFragment as? Callback)
|
||||||
|
?: (activity as? Callback))?.onReaderModeChanged(mode)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
R.id.button_standard -> mode = ReaderMode.STANDARD
|
||||||
|
R.id.button_webtoon -> mode = ReaderMode.WEBTOON
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
|
||||||
|
fun onReaderModeChanged(mode: ReaderMode)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val TAG = "ReaderConfigDialog"
|
private const val TAG = "ReaderConfigDialog"
|
||||||
|
private const val ARG_MODE = "mode"
|
||||||
|
|
||||||
fun show(fm: FragmentManager) = ReaderConfigDialog().show(fm, TAG)
|
fun show(fm: FragmentManager, mode: ReaderMode) = ReaderConfigDialog().withArgs(1) {
|
||||||
|
putInt(ARG_MODE, mode.id)
|
||||||
|
}.show(fm, TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,8 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.koitharu.kotatsu.BuildConfig
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
import org.koitharu.kotatsu.core.model.MangaPage
|
import org.koitharu.kotatsu.core.model.MangaPage
|
||||||
|
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||||
|
import org.koitharu.kotatsu.domain.MangaPreferencesRepository
|
||||||
import org.koitharu.kotatsu.domain.MangaProviderFactory
|
import org.koitharu.kotatsu.domain.MangaProviderFactory
|
||||||
import org.koitharu.kotatsu.domain.history.HistoryRepository
|
import org.koitharu.kotatsu.domain.history.HistoryRepository
|
||||||
import org.koitharu.kotatsu.ui.common.BasePresenter
|
import org.koitharu.kotatsu.ui.common.BasePresenter
|
||||||
@@ -27,14 +29,14 @@ class ReaderPresenter : BasePresenter<ReaderView>() {
|
|||||||
presenterScope.launch {
|
presenterScope.launch {
|
||||||
viewState.onLoadingStateChanged(isLoading = true)
|
viewState.onLoadingStateChanged(isLoading = true)
|
||||||
try {
|
try {
|
||||||
val pages = withContext(Dispatchers.IO) {
|
val (pages, mode) = withContext(Dispatchers.IO) {
|
||||||
val repo = MangaProviderFactory.create(state.manga.source)
|
val repo = MangaProviderFactory.create(state.manga.source)
|
||||||
val chapter = state.chapter ?: repo.getDetails(state.manga).chapters
|
val chapter = state.chapter ?: repo.getDetails(state.manga).chapters
|
||||||
?.first { it.id == state.chapterId }
|
?.first { it.id == state.chapterId }
|
||||||
?: throw RuntimeException("Chapter ${state.chapterId} not found")
|
?: throw RuntimeException("Chapter ${state.chapterId} not found")
|
||||||
repo.getPages(chapter)
|
repo.getPages(chapter) to MangaPreferencesRepository().getReaderMode(state.manga.id)
|
||||||
}
|
}
|
||||||
viewState.onPagesReady(pages, state.page)
|
viewState.onInitReader(pages, mode, state)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -46,13 +48,19 @@ class ReaderPresenter : BasePresenter<ReaderView>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveState(state: ReaderState) {
|
fun saveState(state: ReaderState, mode: ReaderMode? = null) {
|
||||||
presenterScope.launch(Dispatchers.IO) {
|
presenterScope.launch(Dispatchers.IO) {
|
||||||
HistoryRepository().addOrUpdate(
|
HistoryRepository().addOrUpdate(
|
||||||
manga = state.manga,
|
manga = state.manga,
|
||||||
chapterId = state.chapterId,
|
chapterId = state.chapterId,
|
||||||
page = state.page
|
page = state.page
|
||||||
)
|
)
|
||||||
|
if (mode != null) {
|
||||||
|
MangaPreferencesRepository().saveData(
|
||||||
|
mangaId = state.manga.id,
|
||||||
|
mode = mode
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import moxy.MvpView
|
|||||||
import moxy.viewstate.strategy.alias.AddToEndSingle
|
import moxy.viewstate.strategy.alias.AddToEndSingle
|
||||||
import moxy.viewstate.strategy.alias.OneExecution
|
import moxy.viewstate.strategy.alias.OneExecution
|
||||||
import org.koitharu.kotatsu.core.model.MangaPage
|
import org.koitharu.kotatsu.core.model.MangaPage
|
||||||
|
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||||
|
|
||||||
interface ReaderView : MvpView {
|
interface ReaderView : MvpView {
|
||||||
|
|
||||||
@AddToEndSingle
|
@AddToEndSingle
|
||||||
fun onPagesReady(pages: List<MangaPage>, index: Int)
|
fun onInitReader(pages: List<MangaPage>, mode: ReaderMode, state: ReaderState)
|
||||||
|
|
||||||
@AddToEndSingle
|
@AddToEndSingle
|
||||||
fun onLoadingStateChanged(isLoading: Boolean)
|
fun onLoadingStateChanged(isLoading: Boolean)
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import kotlinx.android.synthetic.main.fragment_reader_standard.*
|
|||||||
import moxy.ktx.moxyPresenter
|
import moxy.ktx.moxyPresenter
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.model.MangaPage
|
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.BaseReaderFragment
|
||||||
import org.koitharu.kotatsu.ui.reader.PageLoader
|
import org.koitharu.kotatsu.ui.reader.PageLoader
|
||||||
import org.koitharu.kotatsu.ui.reader.ReaderPresenter
|
import org.koitharu.kotatsu.ui.reader.ReaderPresenter
|
||||||
|
import org.koitharu.kotatsu.ui.reader.ReaderState
|
||||||
|
|
||||||
class StandardReaderFragment : BaseReaderFragment(R.layout.fragment_reader_standard) {
|
class StandardReaderFragment : BaseReaderFragment(R.layout.fragment_reader_standard) {
|
||||||
|
|
||||||
@@ -29,10 +31,10 @@ class StandardReaderFragment : BaseReaderFragment(R.layout.fragment_reader_stand
|
|||||||
pager.offscreenPageLimit = 2
|
pager.offscreenPageLimit = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPagesReady(pages: List<MangaPage>, index: Int) {
|
override fun onInitReader(pages: List<MangaPage>, mode: ReaderMode, state: ReaderState) {
|
||||||
adapter?.let {
|
adapter?.let {
|
||||||
it.replaceData(pages)
|
it.replaceData(pages)
|
||||||
pager.setCurrentItem(index, false)
|
pager.setCurrentItem(state.page, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
app/src/main/res/layout/fragment_reader_webtoon.xml
Normal file
9
app/src/main/res/layout/fragment_reader_webtoon.xml
Normal file
@@ -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" />
|
||||||
@@ -82,4 +82,5 @@
|
|||||||
<string name="downloading_d_percent">Downloading: %d%%</string>
|
<string name="downloading_d_percent">Downloading: %d%%</string>
|
||||||
<string name="standard">Standard</string>
|
<string name="standard">Standard</string>
|
||||||
<string name="webtoon">Webtoon</string>
|
<string name="webtoon">Webtoon</string>
|
||||||
|
<string name="read_mode">Read mode</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user