Update history repository

This commit is contained in:
Koitharu
2020-02-02 16:31:50 +02:00
parent 7d677833fa
commit aa65f9b02c
33 changed files with 339 additions and 200 deletions

View File

@@ -1,6 +0,0 @@
package org.koitharu.kotatsu.core.model
data class MangaInfo <E>(
val manga: Manga,
val extra: E
)

View File

@@ -0,0 +1,6 @@
package org.koitharu.kotatsu.domain
enum class ChapterExtra {
READ, CURRENT, UNREAD, NEW
}

View File

@@ -11,44 +11,15 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
import java.io.Closeable import java.io.Closeable
import java.util.* import java.util.*
class HistoryRepository() : KoinComponent, class HistoryRepository() : KoinComponent {
MangaRepository, Closeable {
private val db: MangaDatabase by inject() private val db: MangaDatabase by inject()
override val sortOrders: Set<SortOrder> = setOf(SortOrder.NEWEST, SortOrder.POPULARITY) suspend fun getList(offset: Int) : List<Manga> {
override val isSearchAvailable = false
override suspend fun getList(
offset: Int,
query: String?,
sortOrder: SortOrder?,
tag: MangaTag?
): List<Manga> = getHistory(offset, query, sortOrder, tag).map { x -> x.manga }
suspend fun getHistory(
offset: Int,
query: String? = null,
sortOrder: SortOrder? = null,
tag: MangaTag? = null
): List<MangaInfo<MangaHistory>> {
val entities = db.historyDao().getAll(offset, 20, "updated_by") val entities = db.historyDao().getAll(offset, 20, "updated_by")
return entities.map { x -> MangaInfo(x.manga.toManga(), x.history.toMangaHistory()) } return entities.map { it.manga.toManga() }
} }
override suspend fun getDetails(manga: Manga): Manga {
throw UnsupportedOperationException("History repository does not support getDetails() method")
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
throw UnsupportedOperationException("History repository does not support getPages() method")
}
override suspend fun getPageFullUrl(page: MangaPage) = page.url
override suspend fun getTags() = emptySet<MangaTag>()
suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int) { suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int) {
val dao = db.historyDao() val dao = db.historyDao()
val entity = HistoryEntity( val entity = HistoryEntity(
@@ -66,7 +37,7 @@ class HistoryRepository() : KoinComponent,
) )
} }
suspend fun getHistory(manga: Manga): MangaHistory? { suspend fun getOne(manga: Manga): MangaHistory? {
return db.historyDao().getOneOrNull(manga.id)?.let { return db.historyDao().getOneOrNull(manga.id)?.let {
MangaHistory( MangaHistory(
createdAt = Date(it.createdAt), createdAt = Date(it.createdAt),
@@ -80,8 +51,4 @@ class HistoryRepository() : KoinComponent,
suspend fun clear() { suspend fun clear() {
db.historyDao().clear() db.historyDao().clear()
} }
override fun close() {
db.close()
}
} }

View File

@@ -5,8 +5,8 @@ import androidx.recyclerview.widget.RecyclerView
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koitharu.kotatsu.utils.ext.replaceWith import org.koitharu.kotatsu.utils.ext.replaceWith
abstract class BaseRecyclerAdapter<T>(private val onItemClickListener: OnRecyclerItemClickListener<T>? = null) : abstract class BaseRecyclerAdapter<T, E>(private val onItemClickListener: OnRecyclerItemClickListener<T>? = null) :
RecyclerView.Adapter<BaseViewHolder<T>>(), RecyclerView.Adapter<BaseViewHolder<T, E>>(),
KoinComponent { KoinComponent {
private val dataSet = ArrayList<T>() private val dataSet = ArrayList<T>()
@@ -16,8 +16,9 @@ abstract class BaseRecyclerAdapter<T>(private val onItemClickListener: OnRecycle
setHasStableIds(true) setHasStableIds(true)
} }
override fun onBindViewHolder(holder: BaseViewHolder<T>, position: Int) { override fun onBindViewHolder(holder: BaseViewHolder<T, E>, position: Int) {
holder.bind(dataSet[position]) val item = dataSet[position]
holder.bind(item, getExtra(item, position))
} }
fun getItem(position: Int) = dataSet[position] fun getItem(position: Int) = dataSet[position]
@@ -64,13 +65,16 @@ abstract class BaseRecyclerAdapter<T>(private val onItemClickListener: OnRecycle
final override fun getItemCount() = dataSet.size final override fun getItemCount() = dataSet.size
final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<T> { final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<T, E> {
return onCreateViewHolder(parent).setOnItemClickListener(onItemClickListener).also(this::onViewHolderCreated) return onCreateViewHolder(parent).setOnItemClickListener(onItemClickListener)
.also(this::onViewHolderCreated)
} }
protected open fun onViewHolderCreated(holder: BaseViewHolder<T>) = Unit protected abstract fun getExtra(item: T, position: Int): E
protected abstract fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder<T> protected open fun onViewHolderCreated(holder: BaseViewHolder<T, E>) = Unit
protected abstract fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder<T, E>
protected abstract fun onGetItemId(item: T): Long protected abstract fun onGetItemId(item: T): Long
} }

View File

@@ -8,7 +8,7 @@ import kotlinx.android.extensions.LayoutContainer
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koitharu.kotatsu.utils.ext.inflate import org.koitharu.kotatsu.utils.ext.inflate
abstract class BaseViewHolder<T> protected constructor(view: View) : abstract class BaseViewHolder<T, E> protected constructor(view: View) :
RecyclerView.ViewHolder(view), LayoutContainer, KoinComponent { RecyclerView.ViewHolder(view), LayoutContainer, KoinComponent {
constructor(parent: ViewGroup, @LayoutRes resId: Int) : this(parent.inflate(resId)) constructor(parent: ViewGroup, @LayoutRes resId: Int) : this(parent.inflate(resId))
@@ -21,14 +21,14 @@ abstract class BaseViewHolder<T> protected constructor(view: View) :
val context get() = itemView.context!! val context get() = itemView.context!!
fun bind(data: T) { fun bind(data: T, extra: E) {
boundData = data boundData = data
onBind(data) onBind(data, extra)
} }
fun requireData() = boundData ?: throw IllegalStateException("Calling requireData() before bind()") fun requireData() = boundData ?: throw IllegalStateException("Calling requireData() before bind()")
fun setOnItemClickListener(listener: OnRecyclerItemClickListener<T>?): BaseViewHolder<T> { fun setOnItemClickListener(listener: OnRecyclerItemClickListener<T>?): BaseViewHolder<T, E> {
if (listener != null) { if (listener != null) {
itemView.setOnClickListener { itemView.setOnClickListener {
listener.onItemClick(boundData ?: return@setOnClickListener, adapterPosition, it) listener.onItemClick(boundData ?: return@setOnClickListener, adapterPosition, it)
@@ -40,5 +40,5 @@ abstract class BaseViewHolder<T> protected constructor(view: View) :
return this return this
} }
abstract fun onBind(data: T) abstract fun onBind(data: T, extra: E)
} }

View File

@@ -4,12 +4,33 @@ import android.view.ViewGroup
import kotlinx.android.synthetic.main.item_chapter.* import kotlinx.android.synthetic.main.item_chapter.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.domain.ChapterExtra
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
import org.koitharu.kotatsu.utils.ext.getThemeColor
class ChapterHolder(parent: ViewGroup) : BaseViewHolder<MangaChapter>(parent, R.layout.item_chapter) { class ChapterHolder(parent: ViewGroup) :
BaseViewHolder<MangaChapter, ChapterExtra>(parent, R.layout.item_chapter) {
override fun onBind(data: MangaChapter) { override fun onBind(data: MangaChapter, extra: ChapterExtra) {
textView_title.text = data.name textView_title.text = data.name
textView_number.text = data.number.toString() textView_number.text = data.number.toString()
when (extra) {
ChapterExtra.UNREAD -> {
textView_number.setBackgroundResource(R.drawable.bg_badge_default)
textView_number.setTextColor(context.getThemeColor(android.R.attr.textColorSecondaryInverse))
}
ChapterExtra.READ -> {
textView_number.setBackgroundResource(R.drawable.bg_badge_outline)
textView_number.setTextColor(context.getThemeColor(android.R.attr.textColorTertiary))
}
ChapterExtra.CURRENT -> {
textView_number.setBackgroundResource(R.drawable.bg_badge_outline_accent)
textView_number.setTextColor(context.getThemeColor(R.attr.colorAccent))
}
ChapterExtra.NEW -> {
textView_number.setBackgroundResource(R.drawable.bg_badge_accent)
textView_number.setTextColor(context.getThemeColor(android.R.attr.textColorPrimaryInverse))
}
}
} }
} }

View File

@@ -1,15 +1,32 @@
package org.koitharu.kotatsu.ui.details package org.koitharu.kotatsu.ui.details
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
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.domain.ChapterExtra
import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
class ChaptersAdapter(onItemClickListener: OnRecyclerItemClickListener<MangaChapter>) : class ChaptersAdapter(onItemClickListener: OnRecyclerItemClickListener<MangaChapter>) :
BaseRecyclerAdapter<MangaChapter>(onItemClickListener) { BaseRecyclerAdapter<MangaChapter, ChapterExtra>(onItemClickListener) {
var currentChapterPosition = RecyclerView.NO_POSITION
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup) = ChapterHolder(parent) override fun onCreateViewHolder(parent: ViewGroup) = ChapterHolder(parent)
override fun onGetItemId(item: MangaChapter) = item.id override fun onGetItemId(item: MangaChapter) = item.id
override fun getExtra(item: MangaChapter, position: Int): ChapterExtra = when {
currentChapterPosition == RecyclerView.NO_POSITION -> ChapterExtra.UNREAD
currentChapterPosition == position -> ChapterExtra.CURRENT
currentChapterPosition < position -> ChapterExtra.UNREAD
currentChapterPosition > position -> ChapterExtra.READ
else -> ChapterExtra.UNREAD
}
} }

View File

@@ -8,9 +8,9 @@ import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_chapters.* import kotlinx.android.synthetic.main.fragment_chapters.*
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.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.MangaInfo
import org.koitharu.kotatsu.ui.common.BaseFragment import org.koitharu.kotatsu.ui.common.BaseFragment
import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
import org.koitharu.kotatsu.ui.reader.ReaderActivity import org.koitharu.kotatsu.ui.reader.ReaderActivity
@@ -21,7 +21,7 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsV
@Suppress("unused") @Suppress("unused")
private val presenter by moxyPresenter { (activity as MangaDetailsActivity).presenter } private val presenter by moxyPresenter { (activity as MangaDetailsActivity).presenter }
private var data: MangaInfo<MangaHistory?>? = null private var manga: Manga? = null
private lateinit var adapter: ChaptersAdapter private lateinit var adapter: ChaptersAdapter
@@ -37,9 +37,9 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsV
recyclerView_chapters.adapter = adapter recyclerView_chapters.adapter = adapter
} }
override fun onMangaUpdated(data: MangaInfo<MangaHistory?>) { override fun onMangaUpdated(manga: Manga) {
this.data = data this.manga = manga
adapter.replaceData(data.manga.chapters.orEmpty()) adapter.replaceData(manga.chapters.orEmpty())
} }
override fun onLoadingStateChanged(isLoading: Boolean) { override fun onLoadingStateChanged(isLoading: Boolean) {
@@ -50,11 +50,17 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsV
} }
override fun onHistoryChanged(history: MangaHistory?) {
adapter.currentChapterPosition = history?.let {
manga?.chapters?.indexOfFirst { x -> x.id == it.chapterId }
} ?: RecyclerView.NO_POSITION
}
override fun onItemClick(item: MangaChapter, position: Int, view: View) { override fun onItemClick(item: MangaChapter, position: Int, view: View) {
startActivity( startActivity(
ReaderActivity.newIntent( ReaderActivity.newIntent(
context ?: return, context ?: return,
data?.manga ?: return, manga ?: return,
item.id item.id
) )
) )

View File

@@ -9,7 +9,6 @@ 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.MangaHistory import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.MangaInfo
import org.koitharu.kotatsu.ui.common.BaseActivity import org.koitharu.kotatsu.ui.common.BaseActivity
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
@@ -25,13 +24,23 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView {
tabs.setupWithViewPager(pager) tabs.setupWithViewPager(pager)
intent?.getParcelableExtra<Manga>(EXTRA_MANGA)?.let { intent?.getParcelableExtra<Manga>(EXTRA_MANGA)?.let {
presenter.loadDetails(it) presenter.loadDetails(it)
presenter.loadHistory(it)
} ?: finish() } ?: finish()
} }
override fun onMangaUpdated(data: MangaInfo<MangaHistory?>) { override fun onResume() {
title = data.manga.title super.onResume()
intent?.getParcelableExtra<Manga>(EXTRA_MANGA)?.let {
presenter.loadHistory(it)
}
} }
override fun onMangaUpdated(manga: Manga) {
title = manga.title
}
override fun onHistoryChanged(history: MangaHistory?) = Unit
override fun onLoadingStateChanged(isLoading: Boolean) = Unit override fun onLoadingStateChanged(isLoading: Boolean) = Unit
override fun onError(e: Exception) { override fun onError(e: Exception) {

View File

@@ -6,7 +6,7 @@ import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter import androidx.fragment.app.FragmentPagerAdapter
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
class MangaDetailsAdapter(private val resources: Resources, fm: FragmentManager) : FragmentPagerAdapter(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { class MangaDetailsAdapter(private val resources: Resources, fm: FragmentManager) : FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getCount() = 2 override fun getCount() = 2

View File

@@ -8,7 +8,6 @@ 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.MangaHistory import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.MangaInfo
import org.koitharu.kotatsu.ui.common.BaseFragment import org.koitharu.kotatsu.ui.common.BaseFragment
import org.koitharu.kotatsu.ui.reader.ReaderActivity import org.koitharu.kotatsu.ui.reader.ReaderActivity
import org.koitharu.kotatsu.utils.ext.setChips import org.koitharu.kotatsu.utils.ext.setChips
@@ -19,46 +18,34 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
@Suppress("unused") @Suppress("unused")
private val presenter by moxyPresenter { (activity as MangaDetailsActivity).presenter } private val presenter by moxyPresenter { (activity as MangaDetailsActivity).presenter }
override fun onMangaUpdated(data: MangaInfo<MangaHistory?>) { private var manga: Manga? = null
imageView_cover.load(data.manga.largeCoverUrl ?: data.manga.coverUrl) private var history: MangaHistory? = null
textView_title.text = data.manga.title
textView_subtitle.text = data.manga.localizedTitle override fun onMangaUpdated(manga: Manga) {
textView_description.text = data.manga.description?.parseAsHtml() this.manga = manga
if (data.manga.rating == Manga.NO_RATING) { imageView_cover.load(manga.largeCoverUrl ?: manga.coverUrl)
textView_title.text = manga.title
textView_subtitle.text = manga.localizedTitle
textView_description.text = manga.description?.parseAsHtml()
if (manga.rating == Manga.NO_RATING) {
ratingBar.isVisible = false ratingBar.isVisible = false
} else { } else {
ratingBar.progress = (ratingBar.max * data.manga.rating).roundToInt() ratingBar.progress = (ratingBar.max * manga.rating).roundToInt()
ratingBar.isVisible = true ratingBar.isVisible = true
} }
chips_tags.setChips(data.manga.tags) { chips_tags.setChips(manga.tags) {
create( create(
text = it.title, text = it.title,
iconRes = R.drawable.ic_chip_tag, iconRes = R.drawable.ic_chip_tag,
tag = it tag = it
) )
} }
if (data.manga.chapters.isNullOrEmpty()) { updateReadButton()
button_read.isEnabled = false }
} else {
button_read.isEnabled = true override fun onHistoryChanged(history: MangaHistory?) {
if (data.extra == null) { this.history = history
button_read.setText(R.string.read) updateReadButton()
button_read.setIconResource(R.drawable.ic_read)
} else {
button_read.setText(R.string.continue_)
button_read.setIconResource(R.drawable.ic_play)
}
val chapterId = data.extra?.chapterId ?: data.manga.chapters.first().id
button_read.setOnClickListener {
startActivity(
ReaderActivity.newIntent(
context ?: return@setOnClickListener,
data.manga,
chapterId
)
)
}
}
} }
override fun onLoadingStateChanged(isLoading: Boolean) { override fun onLoadingStateChanged(isLoading: Boolean) {
@@ -68,4 +55,28 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
override fun onError(e: Exception) { override fun onError(e: Exception) {
} }
private fun updateReadButton() {
if (manga?.chapters.isNullOrEmpty()) {
button_read.isEnabled = false
} else {
button_read.isEnabled = true
if (history == null) {
button_read.setText(R.string.read)
button_read.setIconResource(R.drawable.ic_read)
} else {
button_read.setText(R.string.continue_)
button_read.setIconResource(R.drawable.ic_play)
}
button_read.setOnClickListener {
startActivity(
ReaderActivity.newIntent(
context ?: return@setOnClickListener,
manga ?: return@setOnClickListener,
history
)
)
}
}
}
} }

View File

@@ -1,13 +1,11 @@
package org.koitharu.kotatsu.ui.details package org.koitharu.kotatsu.ui.details
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import moxy.InjectViewState import moxy.InjectViewState
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaInfo
import org.koitharu.kotatsu.domain.HistoryRepository import org.koitharu.kotatsu.domain.HistoryRepository
import org.koitharu.kotatsu.domain.MangaProviderFactory import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.common.BasePresenter import org.koitharu.kotatsu.ui.common.BasePresenter
@@ -15,24 +13,25 @@ import org.koitharu.kotatsu.ui.common.BasePresenter
@InjectViewState @InjectViewState
class MangaDetailsPresenter : BasePresenter<MangaDetailsView>() { class MangaDetailsPresenter : BasePresenter<MangaDetailsView>() {
private lateinit var historyRepository: HistoryRepository
private var isLoaded = false private var isLoaded = false
fun loadDetails(manga: Manga) { override fun onFirstViewAttach() {
if (isLoaded) { historyRepository = HistoryRepository()
super.onFirstViewAttach()
}
fun loadDetails(manga: Manga, force: Boolean = false) {
if (!force && isLoaded) {
return return
} }
viewState.onMangaUpdated(MangaInfo(manga, null)) viewState.onMangaUpdated(manga)
launch { launch {
try { try {
viewState.onLoadingStateChanged(true) viewState.onLoadingStateChanged(true)
val data = withContext(Dispatchers.IO) { val data = withContext(Dispatchers.IO) {
val details = async { MangaProviderFactory.create(manga.source).getDetails(manga)
MangaProviderFactory.create(manga.source).getDetails(manga)
}
val history = async {
HistoryRepository().use { it.getHistory(manga) }
}
MangaInfo(details.await(), history.await())
} }
viewState.onMangaUpdated(data) viewState.onMangaUpdated(data)
isLoaded = true isLoaded = true
@@ -46,4 +45,19 @@ class MangaDetailsPresenter : BasePresenter<MangaDetailsView>() {
} }
} }
} }
fun loadHistory(manga: Manga) {
launch {
try {
val history = withContext(Dispatchers.IO) {
historyRepository.getOne(manga)
}
viewState.onHistoryChanged(history)
} catch (e: Exception) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}
}
}
}
} }

View File

@@ -4,17 +4,20 @@ import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.OneExecutionStateStrategy import moxy.viewstate.strategy.OneExecutionStateStrategy
import moxy.viewstate.strategy.StateStrategyType import moxy.viewstate.strategy.StateStrategyType
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.MangaInfo
interface MangaDetailsView : MvpView { interface MangaDetailsView : MvpView {
@StateStrategyType(AddToEndSingleStrategy::class) @StateStrategyType(AddToEndSingleStrategy::class)
fun onMangaUpdated(data: MangaInfo<MangaHistory?>) fun onMangaUpdated(manga: Manga)
@StateStrategyType(AddToEndSingleStrategy::class) @StateStrategyType(AddToEndSingleStrategy::class)
fun onLoadingStateChanged(isLoading: Boolean) fun onLoadingStateChanged(isLoading: Boolean)
@StateStrategyType(OneExecutionStateStrategy::class) @StateStrategyType(OneExecutionStateStrategy::class)
fun onError(e: Exception) fun onError(e: Exception)
@StateStrategyType(AddToEndSingleStrategy::class)
fun onHistoryChanged(history: MangaHistory?)
} }

View File

@@ -5,17 +5,18 @@ import coil.api.load
import coil.request.RequestDisposable import coil.request.RequestDisposable
import kotlinx.android.synthetic.main.item_manga_grid.* import kotlinx.android.synthetic.main.item_manga_grid.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaInfo import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
class MangaGridHolder<E>(parent: ViewGroup) : BaseViewHolder<MangaInfo<E>>(parent, R.layout.item_manga_grid) { class MangaGridHolder(parent: ViewGroup) : BaseViewHolder<Manga, MangaHistory?>(parent, R.layout.item_manga_grid) {
private var coverRequest: RequestDisposable? = null private var coverRequest: RequestDisposable? = null
override fun onBind(data: MangaInfo<E>) { override fun onBind(data: Manga, extra: MangaHistory?) {
coverRequest?.dispose() coverRequest?.dispose()
textView_title.text = data.manga.title textView_title.text = data.title
coverRequest = imageView_cover.load(data.manga.coverUrl) { coverRequest = imageView_cover.load(data.coverUrl) {
crossfade(true) crossfade(true)
} }
} }

View File

@@ -1,21 +1,24 @@
package org.koitharu.kotatsu.ui.main.list package org.koitharu.kotatsu.ui.main.list
import android.view.ViewGroup import android.view.ViewGroup
import org.koitharu.kotatsu.core.model.MangaInfo import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter
import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
class MangaListAdapter<E>(onItemClickListener: OnRecyclerItemClickListener<MangaInfo<E>>) : class MangaListAdapter(onItemClickListener: OnRecyclerItemClickListener<Manga>) :
BaseRecyclerAdapter<MangaInfo<E>>(onItemClickListener) { BaseRecyclerAdapter<Manga, MangaHistory?>(onItemClickListener) {
var listMode: ListMode = ListMode.LIST var listMode: ListMode = ListMode.LIST
override fun onCreateViewHolder(parent: ViewGroup) = when(listMode) { override fun onCreateViewHolder(parent: ViewGroup) = when(listMode) {
ListMode.LIST -> MangaListHolder<E>(parent) ListMode.LIST -> MangaListHolder(parent)
ListMode.DETAILED_LIST -> MangaListDetailsHolder<E>(parent) ListMode.DETAILED_LIST -> MangaListDetailsHolder(parent)
ListMode.GRID -> MangaGridHolder(parent) ListMode.GRID -> MangaGridHolder(parent)
} }
override fun onGetItemId(item: MangaInfo<E>) = item.manga.id override fun onGetItemId(item: Manga) = item.id
override fun getExtra(item: Manga, position: Int): MangaHistory? = null
} }

View File

@@ -8,30 +8,30 @@ import coil.request.RequestDisposable
import kotlinx.android.synthetic.main.item_manga_list_details.* import kotlinx.android.synthetic.main.item_manga_list_details.*
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.MangaInfo import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
import org.koitharu.kotatsu.utils.ext.textAndVisible import org.koitharu.kotatsu.utils.ext.textAndVisible
import kotlin.math.roundToInt import kotlin.math.roundToInt
class MangaListDetailsHolder<E>(parent: ViewGroup) : BaseViewHolder<MangaInfo<E>>(parent, R.layout.item_manga_list_details) { class MangaListDetailsHolder(parent: ViewGroup) : BaseViewHolder<Manga, MangaHistory?>(parent, R.layout.item_manga_list_details) {
private var coverRequest: RequestDisposable? = null private var coverRequest: RequestDisposable? = null
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onBind(data: MangaInfo<E>) { override fun onBind(data: Manga, extra: MangaHistory?) {
coverRequest?.dispose() coverRequest?.dispose()
textView_title.text = data.manga.title textView_title.text = data.title
textView_subtitle.textAndVisible = data.manga.localizedTitle textView_subtitle.textAndVisible = data.localizedTitle
coverRequest = imageView_cover.load(data.manga.coverUrl) { coverRequest = imageView_cover.load(data.coverUrl) {
crossfade(true) crossfade(true)
} }
if(data.manga.rating == Manga.NO_RATING) { if(data.rating == Manga.NO_RATING) {
textView_rating.isVisible = false textView_rating.isVisible = false
} else { } else {
textView_rating.text = "${(data.manga.rating * 10).roundToInt()}/10" textView_rating.text = "${(data.rating * 10).roundToInt()}/10"
textView_rating.isVisible = true textView_rating.isVisible = true
} }
textView_tags.text = data.manga.tags.joinToString(", ") { textView_tags.text = data.tags.joinToString(", ") {
it.title it.title
} }
} }

View File

@@ -14,7 +14,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_list.* import kotlinx.android.synthetic.main.fragment_list.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaInfo import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.ui.common.BaseFragment import org.koitharu.kotatsu.ui.common.BaseFragment
import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
@@ -27,9 +27,9 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.hasItems import org.koitharu.kotatsu.utils.ext.hasItems
abstract class MangaListFragment <E> : BaseFragment(R.layout.fragment_list), MangaListView<E>, abstract class MangaListFragment <E> : BaseFragment(R.layout.fragment_list), MangaListView<E>,
PaginationScrollListener.Callback, OnRecyclerItemClickListener<MangaInfo<E>> { PaginationScrollListener.Callback, OnRecyclerItemClickListener<Manga> {
private lateinit var adapter: MangaListAdapter<E> private lateinit var adapter: MangaListAdapter
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -73,16 +73,16 @@ abstract class MangaListFragment <E> : BaseFragment(R.layout.fragment_list), Man
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun onItemClick(item: MangaInfo<E>, position: Int, view: View) { override fun onItemClick(item: Manga, position: Int, view: View) {
startActivity(MangaDetailsActivity.newIntent(context ?: return, item.manga)) startActivity(MangaDetailsActivity.newIntent(context ?: return, item))
} }
override fun onListChanged(list: List<MangaInfo<E>>) { override fun onListChanged(list: List<Manga>) {
adapter.replaceData(list) adapter.replaceData(list)
layout_holder.isVisible = list.isEmpty() layout_holder.isVisible = list.isEmpty()
} }
override fun onListAppended(list: List<MangaInfo<E>>) { override fun onListAppended(list: List<Manga>) {
adapter.appendData(list) adapter.appendData(list)
} }

View File

@@ -5,19 +5,20 @@ import coil.api.load
import coil.request.RequestDisposable import coil.request.RequestDisposable
import kotlinx.android.synthetic.main.item_manga_list.* import kotlinx.android.synthetic.main.item_manga_list.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaInfo import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
import org.koitharu.kotatsu.utils.ext.textAndVisible import org.koitharu.kotatsu.utils.ext.textAndVisible
class MangaListHolder<E>(parent: ViewGroup) : BaseViewHolder<MangaInfo<E>>(parent, R.layout.item_manga_list) { class MangaListHolder(parent: ViewGroup) : BaseViewHolder<Manga, MangaHistory?>(parent, R.layout.item_manga_list) {
private var coverRequest: RequestDisposable? = null private var coverRequest: RequestDisposable? = null
override fun onBind(data: MangaInfo<E>) { override fun onBind(data: Manga, extra: MangaHistory?) {
coverRequest?.dispose() coverRequest?.dispose()
textView_title.text = data.manga.title textView_title.text = data.title
textView_subtitle.textAndVisible = data.manga.localizedTitle textView_subtitle.textAndVisible = data.localizedTitle
coverRequest = imageView_cover.load(data.manga.coverUrl) { coverRequest = imageView_cover.load(data.coverUrl) {
crossfade(true) crossfade(true)
} }
} }

View File

@@ -2,15 +2,15 @@ package org.koitharu.kotatsu.ui.main.list
import moxy.MvpView import moxy.MvpView
import moxy.viewstate.strategy.* import moxy.viewstate.strategy.*
import org.koitharu.kotatsu.core.model.MangaInfo import org.koitharu.kotatsu.core.model.Manga
interface MangaListView<E> : MvpView { interface MangaListView<E> : MvpView {
@StateStrategyType(AddToEndSingleTagStrategy::class, tag = "content") @StateStrategyType(AddToEndSingleTagStrategy::class, tag = "content")
fun onListChanged(list: List<MangaInfo<E>>) fun onListChanged(list: List<Manga>)
@StateStrategyType(AddToEndStrategy::class, tag = "content") @StateStrategyType(AddToEndStrategy::class, tag = "content")
fun onListAppended(list: List<MangaInfo<E>>) fun onListAppended(list: List<Manga>)
@StateStrategyType(AddToEndSingleStrategy::class) @StateStrategyType(AddToEndSingleStrategy::class)
fun onLoadingChanged(isLoading: Boolean) fun onLoadingChanged(isLoading: Boolean)

View File

@@ -4,7 +4,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import moxy.InjectViewState import moxy.InjectViewState
import okhttp3.internal.closeQuietly
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.domain.HistoryRepository import org.koitharu.kotatsu.domain.HistoryRepository
@@ -26,7 +25,7 @@ class HistoryListPresenter : BasePresenter<MangaListView<MangaHistory>>() {
viewState.onLoadingChanged(true) viewState.onLoadingChanged(true)
try { try {
val list = withContext(Dispatchers.IO) { val list = withContext(Dispatchers.IO) {
repository.getHistory(offset = offset) repository.getList(offset = offset)
} }
if (offset == 0) { if (offset == 0) {
viewState.onListChanged(list) viewState.onListChanged(list)
@@ -62,9 +61,4 @@ class HistoryListPresenter : BasePresenter<MangaListView<MangaHistory>>() {
} }
} }
} }
override fun onDestroy() {
repository.closeQuietly()
super.onDestroy()
}
} }

View File

@@ -5,7 +5,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import moxy.InjectViewState import moxy.InjectViewState
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.MangaInfo
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaProviderFactory import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.common.BasePresenter import org.koitharu.kotatsu.ui.common.BasePresenter
@@ -21,7 +20,6 @@ class RemoteListPresenter : BasePresenter<MangaListView<Unit>>() {
val list = withContext(Dispatchers.IO) { val list = withContext(Dispatchers.IO) {
MangaProviderFactory.create(source) MangaProviderFactory.create(source)
.getList(offset) .getList(offset)
.map { MangaInfo(it, Unit) }
} }
if (offset == 0) { if (offset == 0) {
viewState.onListChanged(list) viewState.onListChanged(list)

View File

@@ -11,17 +11,17 @@ import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class PageHolder(parent: ViewGroup, private val loader: PageLoader) : BaseViewHolder<MangaPage>(parent, R.layout.item_page), class PageHolder(parent: ViewGroup, private val loader: PageLoader) : BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page),
SubsamplingScaleImageView.OnImageEventListener { SubsamplingScaleImageView.OnImageEventListener {
init { init {
ssiv.setOnImageEventListener(this) ssiv.setOnImageEventListener(this)
button_retry.setOnClickListener { button_retry.setOnClickListener {
onBind(boundData ?: return@setOnClickListener) onBind(boundData ?: return@setOnClickListener, Unit)
} }
} }
override fun onBind(data: MangaPage) { override fun onBind(data: MangaPage, extra: Unit) {
layout_error.isVisible = false layout_error.isVisible = false
progressBar.show() progressBar.show()
ssiv.recycle() ssiv.recycle()

View File

@@ -5,9 +5,11 @@ import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
class PagesAdapter(private val loader: PageLoader) : BaseRecyclerAdapter<MangaPage>() { 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 override fun onGetItemId(item: MangaPage) = item.id
override fun getExtra(item: MangaPage, position: Int) = Unit
} }

View File

@@ -5,11 +5,13 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.activity_reader.* import kotlinx.android.synthetic.main.activity_reader.*
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.MangaHistory
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.common.BaseActivity import org.koitharu.kotatsu.ui.common.BaseActivity
import org.koitharu.kotatsu.utils.ext.showDialog import org.koitharu.kotatsu.utils.ext.showDialog
@@ -18,12 +20,7 @@ class ReaderActivity : BaseActivity(), ReaderView {
private val presenter by moxyPresenter { ReaderPresenter() } private val presenter by moxyPresenter { ReaderPresenter() }
private val manga by lazy(LazyThreadSafetyMode.NONE) { private lateinit var state: ReaderState
intent.getParcelableExtra<Manga>(EXTRA_MANGA)!!
}
private val chapterId by lazy(LazyThreadSafetyMode.NONE) {
intent.getLongExtra(EXTRA_CHAPTER_ID, 0L)
}
private lateinit var loader: PageLoader private lateinit var loader: PageLoader
private lateinit var adapter: PagesAdapter private lateinit var adapter: PagesAdapter
@@ -34,21 +31,24 @@ class ReaderActivity : BaseActivity(), ReaderView {
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
bottomBar.inflateMenu(R.menu.opt_reader_bottom) bottomBar.inflateMenu(R.menu.opt_reader_bottom)
val chapter = manga.chapters?.find { x -> x.id == chapterId } state = savedInstanceState?.getParcelable<ReaderState>(EXTRA_STATE)
if (chapter == null) { ?: intent.getParcelableExtra<ReaderState>(EXTRA_STATE)
// TODO ?: let {
finish() Toast.makeText(this, R.string.error_occurred, Toast.LENGTH_SHORT).show()
return finish()
} return
title = chapter.name }
manga.chapters?.run {
supportActionBar?.subtitle = getString(R.string.chapter_d_of_d, chapter.number, size) title = state.chapter?.name ?: state.manga.title
state.manga.chapters?.run {
supportActionBar?.subtitle =
getString(R.string.chapter_d_of_d, state.chapter?.number ?: 0, size)
} }
loader = PageLoader(this) loader = PageLoader(this)
adapter = PagesAdapter(loader) adapter = PagesAdapter(loader)
pager.adapter = adapter pager.adapter = adapter
presenter.loadChapter(chapter) presenter.loadChapter(state)
} }
override fun onDestroy() { override fun onDestroy() {
@@ -57,7 +57,8 @@ class ReaderActivity : BaseActivity(), ReaderView {
} }
override fun onPause() { override fun onPause() {
presenter.addToHistory(manga, chapterId, pager.currentItem) state = state.copy(page = pager.currentItem)
presenter.saveState(state)
super.onPause() super.onPause()
} }
@@ -66,16 +67,17 @@ class ReaderActivity : BaseActivity(), ReaderView {
return super.onCreateOptionsMenu(menu) return super.onCreateOptionsMenu(menu)
} }
override fun onOptionsItemSelected(item: MenuItem) = when(item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.action_chapters -> { R.id.action_chapters -> {
ChaptersDialog.show(supportFragmentManager, manga.chapters.orEmpty()) ChaptersDialog.show(supportFragmentManager, state.manga.chapters.orEmpty())
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun onPagesReady(pages: List<MangaPage>) { override fun onPagesReady(pages: List<MangaPage>, index: Int) {
adapter.replaceData(pages) adapter.replaceData(pages)
pager.setCurrentItem(index, false)
} }
override fun onLoadingStateChanged(isLoading: Boolean) { override fun onLoadingStateChanged(isLoading: Boolean) {
@@ -92,11 +94,32 @@ class ReaderActivity : BaseActivity(), ReaderView {
companion object { companion object {
private const val EXTRA_MANGA = "manga" private const val EXTRA_STATE = "state"
private const val EXTRA_CHAPTER_ID = "chapter_id"
fun newIntent(context: Context, manga: Manga, chapterId: Long) = Intent(context, ReaderActivity::class.java) fun newIntent(context: Context, state: ReaderState) =
.putExtra(EXTRA_MANGA, manga) Intent(context, ReaderActivity::class.java)
.putExtra(EXTRA_CHAPTER_ID, chapterId) .putExtra(EXTRA_STATE, state)
fun newIntent(context: Context, manga: Manga, chapterId: Long = -1) = newIntent(
context, ReaderState(
manga = manga,
chapterId = if (chapterId == -1L) manga.chapters?.firstOrNull()?.id
?: -1 else chapterId,
page = 0
)
)
fun newIntent(context: Context, manga: Manga, history: MangaHistory?) =
if (history == null) {
newIntent(context, manga)
} else {
newIntent(
context, ReaderState(
manga = manga,
chapterId = history.chapterId,
page = history.page
)
)
}
} }
} }

View File

@@ -14,14 +14,18 @@ import org.koitharu.kotatsu.ui.common.BasePresenter
@InjectViewState @InjectViewState
class ReaderPresenter : BasePresenter<ReaderView>() { class ReaderPresenter : BasePresenter<ReaderView>() {
fun loadChapter(chapter: MangaChapter) { fun loadChapter(state: ReaderState) {
launch { launch {
viewState.onLoadingStateChanged(isLoading = true) viewState.onLoadingStateChanged(isLoading = true)
try { try {
val pages = withContext(Dispatchers.IO) { val pages = withContext(Dispatchers.IO) {
MangaProviderFactory.create(chapter.source).getPages(chapter) val repo = MangaProviderFactory.create(state.manga.source)
val chapter = state.chapter ?: repo.getDetails(state.manga).chapters
?.first { it.id == state.chapterId }
?: throw RuntimeException("Chapter ${state.chapterId} not found")
repo.getPages(chapter)
} }
viewState.onPagesReady(pages) viewState.onPagesReady(pages, state.page)
} catch (e: Exception) { } catch (e: Exception) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
e.printStackTrace() e.printStackTrace()
@@ -33,11 +37,13 @@ class ReaderPresenter : BasePresenter<ReaderView>() {
} }
} }
fun addToHistory(manga: Manga, chapterId: Long, page: Int) { fun saveState(state: ReaderState) {
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
HistoryRepository().use { HistoryRepository().addOrUpdate(
it.addOrUpdate(manga, chapterId, page) manga = state.manga,
} chapterId = state.chapterId,
page = state.page
)
} }
} }

View File

@@ -0,0 +1,20 @@
package org.koitharu.kotatsu.ui.reader
import android.os.Parcelable
import kotlinx.android.parcel.IgnoredOnParcel
import kotlinx.android.parcel.Parcelize
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaChapter
@Parcelize
data class ReaderState(
val manga: Manga,
val chapterId: Long,
val page: Int
) : Parcelable {
@IgnoredOnParcel
val chapter: MangaChapter? by lazy {
manga.chapters?.find { it.id == chapterId }
}
}

View File

@@ -9,7 +9,7 @@ import org.koitharu.kotatsu.core.model.MangaPage
interface ReaderView : MvpView { interface ReaderView : MvpView {
@StateStrategyType(AddToEndSingleStrategy::class) @StateStrategyType(AddToEndSingleStrategy::class)
fun onPagesReady(pages: List<MangaPage>) fun onPagesReady(pages: List<MangaPage>, index: Int)
@StateStrategyType(AddToEndSingleStrategy::class) @StateStrategyType(AddToEndSingleStrategy::class)
fun onLoadingStateChanged(isLoading: Boolean) fun onLoadingStateChanged(isLoading: Boolean)

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="5dp" />
<solid android:color="?colorAccent" />
<padding
android:bottom="2dp"
android:left="2dp"
android:right="2dp"
android:top="2dp" />
</shape>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="5dp" />
<stroke
android:width="2dp"
android:color="?android:textColorTertiary" />
<padding
android:bottom="2dp"
android:left="2dp"
android:right="2dp"
android:top="2dp" />
</shape>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="5dp" />
<stroke
android:width="2dp"
android:color="?android:colorAccent" />
<padding
android:bottom="2dp"
android:left="2dp"
android:right="2dp"
android:top="2dp" />
</shape>

View File

@@ -27,25 +27,27 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:maxLines="3"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover" app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" /> tools:text="@tools:sample/lorem[20]" />
<TextView <TextView
android:id="@+id/textView_subtitle" android:id="@+id/textView_subtitle"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="4dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:maxLines="2"
app:layout_constraintEnd_toEndOf="@id/textView_title" app:layout_constraintEnd_toEndOf="@id/textView_title"
app:layout_constraintStart_toStartOf="@id/textView_title" app:layout_constraintStart_toStartOf="@id/textView_title"
app:layout_constraintTop_toBottomOf="@id/textView_title" app:layout_constraintTop_toBottomOf="@id/textView_title"
tools:text="@tools:sample/lorem" /> tools:text="@tools:sample/lorem[20]" />
<RatingBar <RatingBar
android:id="@+id/ratingBar" android:id="@+id/ratingBar"

View File

@@ -13,7 +13,7 @@
android:id="@+id/textView_number" android:id="@+id/textView_number"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/bg_round_rect" android:background="@drawable/bg_badge_default"
android:gravity="center" android:gravity="center"
android:minWidth="26dp" android:minWidth="26dp"
android:textColor="?android:textColorSecondaryInverse" android:textColor="?android:textColorSecondaryInverse"