Refactor chapters mapping
This commit is contained in:
@@ -32,6 +32,9 @@ class ChaptersBottomSheetMediator(
|
||||
|
||||
override fun onExpansionStateChanged(headerBar: BottomSheetHeaderBar, isExpanded: Boolean) {
|
||||
isEnabled = isExpanded
|
||||
if (!isExpanded) {
|
||||
unlock()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLayoutChange(
|
||||
|
||||
@@ -73,10 +73,6 @@ class ChaptersFragment :
|
||||
if (selectionController?.onItemClick(item.chapter.id) == true) {
|
||||
return
|
||||
}
|
||||
if (item.hasFlag(ChapterListItem.FLAG_MISSING)) {
|
||||
(activity as? DetailsActivity)?.showChapterMissingDialog(item.chapter.id)
|
||||
return
|
||||
}
|
||||
startActivity(
|
||||
ReaderActivity.newIntent(
|
||||
context = view.context,
|
||||
@@ -193,7 +189,7 @@ class ChaptersFragment :
|
||||
private fun onChaptersChanged(list: List<ChapterListItem>) {
|
||||
val adapter = chaptersAdapter ?: return
|
||||
if (adapter.itemCount == 0) {
|
||||
val position = list.indexOfFirst { it.hasFlag(ChapterListItem.FLAG_CURRENT) } - 1
|
||||
val position = list.indexOfFirst { it.isCurrent } - 1
|
||||
if (position > 0) {
|
||||
val offset = (resources.getDimensionPixelSize(R.dimen.chapter_list_item_height) * 0.6).roundToInt()
|
||||
adapter.setItems(
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import org.koitharu.kotatsu.core.model.MangaHistory
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.details.ui.model.toListItem
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
fun mapChapters(
|
||||
remoteManga: Manga?,
|
||||
localManga: Manga?,
|
||||
history: MangaHistory?,
|
||||
newCount: Int,
|
||||
branch: String?,
|
||||
): List<ChapterListItem> {
|
||||
val remoteChapters = remoteManga?.getChapters(branch).orEmpty()
|
||||
val localChapters = localManga?.getChapters(branch).orEmpty()
|
||||
if (remoteChapters.isEmpty() && localChapters.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val currentId = history?.chapterId ?: 0L
|
||||
val newFrom = if (newCount == 0 || remoteChapters.isEmpty()) Int.MAX_VALUE else remoteChapters.size - newCount
|
||||
val chaptersSize = maxOf(remoteChapters.size, localChapters.size)
|
||||
val ids = buildSet(chaptersSize) {
|
||||
remoteChapters.mapTo(this) { it.id }
|
||||
localChapters.mapTo(this) { it.id }
|
||||
}
|
||||
val result = ArrayList<ChapterListItem>(chaptersSize)
|
||||
val localMap = if (localChapters.isNotEmpty()) {
|
||||
localChapters.associateByTo(LinkedHashMap(localChapters.size)) { it.id }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
var isUnread = currentId !in ids
|
||||
for (chapter in remoteChapters) {
|
||||
val local = localMap?.remove(chapter.id)
|
||||
if (chapter.id == currentId) {
|
||||
isUnread = true
|
||||
}
|
||||
result += chapter.toListItem(
|
||||
isCurrent = chapter.id == currentId,
|
||||
isUnread = isUnread,
|
||||
isNew = isUnread && result.size >= newFrom,
|
||||
isDownloaded = local != null,
|
||||
)
|
||||
}
|
||||
if (!localMap.isNullOrEmpty()) {
|
||||
for (chapter in localMap.values) {
|
||||
if (chapter.id == currentId) {
|
||||
isUnread = true
|
||||
}
|
||||
result += chapter.toListItem(
|
||||
isCurrent = chapter.id == currentId,
|
||||
isUnread = isUnread,
|
||||
isNew = false,
|
||||
isDownloaded = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.Observer
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -45,7 +44,6 @@ import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
|
||||
import org.koitharu.kotatsu.main.ui.owners.NoModalBottomSheetOwner
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.reader.ui.thumbnails.PagesThumbnailsSheet
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -244,33 +242,6 @@ class DetailsActivity :
|
||||
viewBadge.counter = newChapters
|
||||
}
|
||||
|
||||
fun showChapterMissingDialog(chapterId: Long) {
|
||||
val remoteManga = viewModel.getRemoteManga()
|
||||
if (remoteManga == null) {
|
||||
val snackbar = makeSnackbar(getString(R.string.chapter_is_missing), Snackbar.LENGTH_SHORT)
|
||||
snackbar.show()
|
||||
return
|
||||
}
|
||||
MaterialAlertDialogBuilder(this).apply {
|
||||
setMessage(R.string.chapter_is_missing_text)
|
||||
setTitle(R.string.chapter_is_missing)
|
||||
setNegativeButton(android.R.string.cancel, null)
|
||||
setPositiveButton(R.string.read) { _, _ ->
|
||||
startActivity(
|
||||
ReaderActivity.newIntent(
|
||||
context = this@DetailsActivity,
|
||||
manga = remoteManga,
|
||||
state = ReaderState(chapterId, 0, 0),
|
||||
),
|
||||
)
|
||||
}
|
||||
setNeutralButton(R.string.download) { _, _ ->
|
||||
viewModel.download(setOf(chapterId))
|
||||
}
|
||||
setCancelable(true)
|
||||
}.show()
|
||||
}
|
||||
|
||||
private fun showBranchPopupMenu() {
|
||||
var dialog: DialogInterface? = null
|
||||
val listener = OnListItemClickListener<MangaBranch> { item, _ ->
|
||||
@@ -291,7 +262,8 @@ class DetailsActivity :
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val chapterId = viewModel.historyInfo.value?.history?.chapterId
|
||||
if (chapterId != null && manga.chapters?.none { x -> x.id == chapterId } == true) {
|
||||
showChapterMissingDialog(chapterId)
|
||||
val snackbar = makeSnackbar(getString(R.string.chapter_is_missing), Snackbar.LENGTH_SHORT)
|
||||
snackbar.show()
|
||||
} else {
|
||||
startActivity(
|
||||
ReaderActivity.newIntent(
|
||||
@@ -339,7 +311,7 @@ class DetailsActivity :
|
||||
}
|
||||
if (!isCalled) {
|
||||
isCalled = true
|
||||
val item = value.find { it.hasFlag(ChapterListItem.FLAG_CURRENT) } ?: value.first()
|
||||
val item = value.find { it.isCurrent } ?: value.first()
|
||||
MangaPrefetchService.prefetchPages(context, item.chapter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,13 @@ class DetailsViewModel @Inject constructor(
|
||||
val onShowToast = SingleLiveEvent<Int>()
|
||||
val onDownloadStarted = SingleLiveEvent<Unit>()
|
||||
|
||||
private val mangaData = combine(
|
||||
delegate.onlineManga,
|
||||
delegate.localManga,
|
||||
) { o, l ->
|
||||
o ?: l
|
||||
}.stateIn(viewModelScope, SharingStarted.Lazily, null)
|
||||
|
||||
private val history = historyRepository.observeOne(delegate.mangaId)
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||
|
||||
@@ -98,16 +105,16 @@ class DetailsViewModel @Inject constructor(
|
||||
private val chaptersReversed = settings.observeAsFlow(AppSettings.KEY_REVERSE_CHAPTERS) { chaptersReverse }
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
|
||||
|
||||
val manga = delegate.manga.filterNotNull().asLiveData(viewModelScope.coroutineContext)
|
||||
val manga = mangaData.filterNotNull().asLiveData(viewModelScope.coroutineContext)
|
||||
val favouriteCategories = favourite.asLiveData(viewModelScope.coroutineContext)
|
||||
val newChaptersCount = newChapters.asLiveData(viewModelScope.coroutineContext)
|
||||
val isChaptersReversed = chaptersReversed.asLiveData(viewModelScope.coroutineContext)
|
||||
|
||||
val historyInfo: LiveData<HistoryInfo> = combine(
|
||||
delegate.manga,
|
||||
mangaData,
|
||||
delegate.selectedBranch,
|
||||
history,
|
||||
historyRepository.observeShouldSkip(delegate.manga),
|
||||
historyRepository.observeShouldSkip(mangaData),
|
||||
) { m, b, h, im ->
|
||||
HistoryInfo(m, b, h, im)
|
||||
}.asFlowLiveData(
|
||||
@@ -115,28 +122,21 @@ class DetailsViewModel @Inject constructor(
|
||||
defaultValue = HistoryInfo(null, null, null, false),
|
||||
)
|
||||
|
||||
val bookmarks = delegate.manga.flatMapLatest {
|
||||
val bookmarks = mangaData.flatMapLatest {
|
||||
if (it != null) bookmarksRepository.observeBookmarks(it) else flowOf(emptyList())
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
|
||||
|
||||
val localSize = combine(
|
||||
delegate.manga,
|
||||
delegate.relatedManga,
|
||||
) { m1, m2 ->
|
||||
val url = when {
|
||||
m1?.source == MangaSource.LOCAL -> m1.url
|
||||
m2?.source == MangaSource.LOCAL -> m2.url
|
||||
else -> null
|
||||
}
|
||||
if (url != null) {
|
||||
val file = url.toUri().toFileOrNull()
|
||||
file?.computeSize() ?: 0L
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, 0)
|
||||
val localSize = delegate.localManga
|
||||
.map {
|
||||
if (it != null) {
|
||||
val file = it.url.toUri().toFileOrNull()
|
||||
file?.computeSize() ?: 0L
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, 0)
|
||||
|
||||
val description = delegate.manga
|
||||
val description = mangaData
|
||||
.distinctUntilChangedBy { it?.description.orEmpty() }
|
||||
.transformLatest {
|
||||
val description = it?.description
|
||||
@@ -159,10 +159,12 @@ class DetailsViewModel @Inject constructor(
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
|
||||
|
||||
val branches: LiveData<List<MangaBranch>> = combine(
|
||||
delegate.manga,
|
||||
delegate.onlineManga,
|
||||
delegate.localManga,
|
||||
delegate.selectedBranch,
|
||||
) { m, b ->
|
||||
val chapters = m?.chapters ?: return@combine emptyList()
|
||||
) { m, l, b ->
|
||||
val chapters = concat(m?.chapters, l?.chapters)
|
||||
if (chapters.isEmpty()) return@combine emptyList()
|
||||
chapters.groupBy { x -> x.branch }
|
||||
.map { x -> MangaBranch(x.key, x.value.size, x.key == b) }
|
||||
.sortedWith(BranchComparator())
|
||||
@@ -172,21 +174,24 @@ class DetailsViewModel @Inject constructor(
|
||||
.asFlowLiveData(viewModelScope.coroutineContext, null)
|
||||
|
||||
val isChaptersEmpty: LiveData<Boolean> = combine(
|
||||
delegate.manga,
|
||||
delegate.onlineManga,
|
||||
delegate.localManga,
|
||||
isLoading.asFlow(),
|
||||
) { m, loading ->
|
||||
m != null && m.chapters.isNullOrEmpty() && !loading
|
||||
) { manga, local, loading ->
|
||||
(manga != null && manga.chapters.isNullOrEmpty()) &&
|
||||
(local != null && local.chapters.isNullOrEmpty()) &&
|
||||
!loading
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext, false)
|
||||
|
||||
val chapters = combine(
|
||||
combine(
|
||||
delegate.manga,
|
||||
delegate.relatedManga,
|
||||
delegate.onlineManga,
|
||||
delegate.localManga,
|
||||
history,
|
||||
delegate.selectedBranch,
|
||||
newChapters,
|
||||
) { manga, related, history, branch, news ->
|
||||
delegate.mapChapters(manga, related, history, news, branch)
|
||||
) { manga, local, history, branch, news ->
|
||||
mapChapters(manga, local, history, news, branch)
|
||||
},
|
||||
chaptersReversed,
|
||||
chaptersQuery,
|
||||
@@ -211,7 +216,7 @@ class DetailsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun deleteLocal() {
|
||||
val m = delegate.manga.value
|
||||
val m = delegate.localManga.value
|
||||
if (m == null) {
|
||||
onShowToast.call(R.string.file_not_found)
|
||||
return
|
||||
@@ -244,7 +249,7 @@ class DetailsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun getRemoteManga(): Manga? {
|
||||
return delegate.relatedManga.value?.takeUnless { it.source == MangaSource.LOCAL }
|
||||
return delegate.onlineManga.value
|
||||
}
|
||||
|
||||
fun performChapterSearch(query: String?) {
|
||||
@@ -274,7 +279,7 @@ class DetailsViewModel @Inject constructor(
|
||||
|
||||
fun markChapterAsCurrent(chapterId: Long) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
val manga = checkNotNull(delegate.manga.value)
|
||||
val manga = checkNotNull(mangaData.value)
|
||||
val chapters = checkNotNull(manga.getChapters(selectedBranchValue))
|
||||
val chapterIndex = chapters.indexOfFirst { it.id == chapterId }
|
||||
check(chapterIndex in chapters.indices) { "Chapter not found" }
|
||||
@@ -286,7 +291,7 @@ class DetailsViewModel @Inject constructor(
|
||||
fun download(chaptersIds: Set<Long>?) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
downloadScheduler.schedule(
|
||||
getRemoteManga() ?: checkNotNull(manga.value),
|
||||
delegate.onlineManga.value ?: checkNotNull(manga.value),
|
||||
chaptersIds,
|
||||
)
|
||||
onDownloadStarted.emitCall(Unit)
|
||||
@@ -308,7 +313,7 @@ class DetailsViewModel @Inject constructor(
|
||||
|
||||
private suspend fun onDownloadComplete(downloadedManga: LocalManga?) {
|
||||
downloadedManga ?: return
|
||||
val currentManga = delegate.manga.value ?: return
|
||||
val currentManga = mangaData.value ?: return
|
||||
if (currentManga.id != downloadedManga.manga.id) {
|
||||
return
|
||||
}
|
||||
@@ -319,7 +324,7 @@ class DetailsViewModel @Inject constructor(
|
||||
runCatchingCancellable {
|
||||
localMangaRepository.getDetails(downloadedManga.manga)
|
||||
}.onSuccess {
|
||||
delegate.relatedManga.value = it
|
||||
delegate.publishManga(it)
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
}
|
||||
@@ -348,4 +353,18 @@ class DetailsViewModel @Inject constructor(
|
||||
}
|
||||
return scrobbler
|
||||
}
|
||||
|
||||
private fun <T> concat(a: List<T>?, b: List<T>?): List<T> {
|
||||
return when {
|
||||
a == null && b == null -> emptyList<T>()
|
||||
a == null && b != null -> b
|
||||
a != null && b == null -> a
|
||||
a != null && b != null -> buildList<T>(a.size + b.size) {
|
||||
addAll(a)
|
||||
addAll(b)
|
||||
}
|
||||
|
||||
else -> error("This shouldn't have happened")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import dagger.hilt.android.ViewModelLifecycle
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.koitharu.kotatsu.core.model.MangaHistory
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import org.koitharu.kotatsu.core.model.getPreferredBranch
|
||||
import org.koitharu.kotatsu.core.os.NetworkState
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaIntent
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.details.ui.model.toListItem
|
||||
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||
@@ -24,31 +26,44 @@ import javax.inject.Inject
|
||||
@ViewModelScoped
|
||||
class MangaDetailsDelegate @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
lifecycle: ViewModelLifecycle,
|
||||
private val mangaDataRepository: MangaDataRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
networkState: NetworkState,
|
||||
) {
|
||||
private val viewModelScope = RetainedLifecycleCoroutineScope(lifecycle)
|
||||
|
||||
private val intent = MangaIntent(savedStateHandle)
|
||||
private val mangaData = MutableStateFlow(intent.manga)
|
||||
private val onlineMangaStateFlow = MutableStateFlow<Manga?>(null)
|
||||
private val localMangaStateFlow = MutableStateFlow<Manga?>(null)
|
||||
|
||||
val onlineManga = combine(
|
||||
onlineMangaStateFlow,
|
||||
networkState,
|
||||
) { m, s -> m.takeIf { s } }
|
||||
.stateIn(viewModelScope, SharingStarted.Lazily, null)
|
||||
val localManga = localMangaStateFlow.asStateFlow()
|
||||
|
||||
val selectedBranch = MutableStateFlow<String?>(null)
|
||||
|
||||
// Remote manga for saved and saved for remote
|
||||
val relatedManga = MutableStateFlow<Manga?>(null)
|
||||
val manga: StateFlow<Manga?>
|
||||
get() = mangaData
|
||||
val mangaId = intent.manga?.id ?: intent.mangaId
|
||||
|
||||
init {
|
||||
intent.manga?.let {
|
||||
publishManga(it)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun doLoad() {
|
||||
var manga = mangaDataRepository.resolveIntent(intent) ?: throw NotFoundException("Cannot find manga", "")
|
||||
mangaData.value = manga
|
||||
publishManga(manga)
|
||||
manga = mangaRepositoryFactory.create(manga.source).getDetails(manga)
|
||||
// find default branch
|
||||
val hist = historyRepository.getOne(manga)
|
||||
selectedBranch.value = manga.getPreferredBranch(hist)
|
||||
mangaData.value = manga
|
||||
relatedManga.value = runCatchingCancellable {
|
||||
publishManga(manga)
|
||||
runCatchingCancellable {
|
||||
if (manga.source == MangaSource.LOCAL) {
|
||||
val m = localMangaRepository.getRemoteManga(manga) ?: return@runCatchingCancellable null
|
||||
mangaRepositoryFactory.create(m.source).getDetails(m)
|
||||
@@ -57,106 +72,18 @@ class MangaDetailsDelegate @Inject constructor(
|
||||
}
|
||||
}.onFailure { error ->
|
||||
error.printStackTraceDebug()
|
||||
}.getOrNull()
|
||||
}.onSuccess {
|
||||
if (it != null) {
|
||||
publishManga(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun mapChapters(
|
||||
manga: Manga?,
|
||||
related: Manga?,
|
||||
history: MangaHistory?,
|
||||
newCount: Int,
|
||||
branch: String?,
|
||||
): List<ChapterListItem> {
|
||||
val chapters = manga?.chapters ?: return emptyList()
|
||||
val relatedChapters = related?.chapters
|
||||
return if (related?.source != MangaSource.LOCAL && !relatedChapters.isNullOrEmpty()) {
|
||||
mapChaptersWithSource(chapters, relatedChapters, history?.chapterId, newCount, branch)
|
||||
fun publishManga(manga: Manga) {
|
||||
if (manga.source == MangaSource.LOCAL) {
|
||||
localMangaStateFlow
|
||||
} else {
|
||||
mapChapters(chapters, relatedChapters, history?.chapterId, newCount, branch)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapChapters(
|
||||
chapters: List<MangaChapter>,
|
||||
downloadedChapters: List<MangaChapter>?,
|
||||
currentId: Long?,
|
||||
newCount: Int,
|
||||
branch: String?,
|
||||
): List<ChapterListItem> {
|
||||
val result = ArrayList<ChapterListItem>(chapters.size)
|
||||
val currentIndex = chapters.indexOfFirst { it.id == currentId }
|
||||
val firstNewIndex = chapters.size - newCount
|
||||
val downloadedIds = downloadedChapters?.mapTo(HashSet(downloadedChapters.size)) { it.id }
|
||||
for (i in chapters.indices) {
|
||||
val chapter = chapters[i]
|
||||
if (chapter.branch != branch) {
|
||||
continue
|
||||
}
|
||||
result += chapter.toListItem(
|
||||
isCurrent = i == currentIndex,
|
||||
isUnread = i > currentIndex,
|
||||
isNew = i >= firstNewIndex,
|
||||
isMissing = false,
|
||||
isDownloaded = downloadedIds?.contains(chapter.id) == true,
|
||||
)
|
||||
}
|
||||
if (result.size < chapters.size / 2) {
|
||||
result.trimToSize()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun mapChaptersWithSource(
|
||||
chapters: List<MangaChapter>,
|
||||
sourceChapters: List<MangaChapter>,
|
||||
currentId: Long?,
|
||||
newCount: Int,
|
||||
branch: String?,
|
||||
): List<ChapterListItem> {
|
||||
val chaptersMap = chapters.associateByTo(HashMap(chapters.size)) { it.id }
|
||||
val result = ArrayList<ChapterListItem>(sourceChapters.size)
|
||||
val currentIndex = sourceChapters.indexOfFirst { it.id == currentId }
|
||||
val firstNewIndex = sourceChapters.size - newCount
|
||||
for (i in sourceChapters.indices) {
|
||||
val chapter = sourceChapters[i]
|
||||
val localChapter = chaptersMap.remove(chapter.id)
|
||||
if (chapter.branch != branch) {
|
||||
continue
|
||||
}
|
||||
result += localChapter?.toListItem(
|
||||
isCurrent = i == currentIndex,
|
||||
isUnread = i > currentIndex,
|
||||
isNew = i >= firstNewIndex,
|
||||
isMissing = false,
|
||||
isDownloaded = false,
|
||||
) ?: chapter.toListItem(
|
||||
isCurrent = i == currentIndex,
|
||||
isUnread = i > currentIndex,
|
||||
isNew = i >= firstNewIndex,
|
||||
isMissing = true,
|
||||
isDownloaded = false,
|
||||
)
|
||||
}
|
||||
if (chaptersMap.isNotEmpty()) { // some chapters on device but not online source
|
||||
result.ensureCapacity(result.size + chaptersMap.size)
|
||||
chaptersMap.values.mapNotNullTo(result) {
|
||||
if (it.branch == branch) {
|
||||
it.toListItem(
|
||||
isCurrent = false,
|
||||
isUnread = true,
|
||||
isNew = false,
|
||||
isMissing = false,
|
||||
isDownloaded = false,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
result.sortBy { it.chapter.number }
|
||||
}
|
||||
if (result.size < sourceChapters.size / 2) {
|
||||
result.trimToSize()
|
||||
}
|
||||
return result
|
||||
onlineMangaStateFlow
|
||||
}.value = manga
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,7 @@ import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ItemChapterBinding
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_CURRENT
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_DOWNLOADED
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_MISSING
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_NEW
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_UNREAD
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
fun chapterListItemAD(
|
||||
clickListener: OnListItemClickListener<ChapterListItem>,
|
||||
@@ -31,15 +27,15 @@ fun chapterListItemAD(
|
||||
binding.textViewNumber.text = item.chapter.number.toString()
|
||||
binding.textViewDescription.textAndVisible = item.description()
|
||||
}
|
||||
when (item.status) {
|
||||
FLAG_UNREAD -> {
|
||||
binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_default)
|
||||
binding.textViewNumber.setTextColor(context.getThemeColor(com.google.android.material.R.attr.colorOnTertiary))
|
||||
when {
|
||||
item.isCurrent -> {
|
||||
binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_primary)
|
||||
binding.textViewNumber.setTextColor(context.getThemeColor(materialR.attr.colorOnPrimary))
|
||||
}
|
||||
|
||||
FLAG_CURRENT -> {
|
||||
binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_accent)
|
||||
binding.textViewNumber.setTextColor(context.getThemeColor(android.R.attr.textColorPrimaryInverse))
|
||||
item.isUnread -> {
|
||||
binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_default)
|
||||
binding.textViewNumber.setTextColor(context.getThemeColor(materialR.attr.colorOnTertiary))
|
||||
}
|
||||
|
||||
else -> {
|
||||
@@ -47,12 +43,7 @@ fun chapterListItemAD(
|
||||
binding.textViewNumber.setTextColor(context.getThemeColor(android.R.attr.textColorTertiary))
|
||||
}
|
||||
}
|
||||
val isMissing = item.hasFlag(FLAG_MISSING)
|
||||
binding.textViewTitle.alpha = if (isMissing) 0.3f else 1f
|
||||
binding.textViewDescription.alpha = if (isMissing) 0.3f else 1f
|
||||
binding.textViewNumber.alpha = if (isMissing) 0.3f else 1f
|
||||
|
||||
binding.imageViewDownloaded.isVisible = item.hasFlag(FLAG_DOWNLOADED)
|
||||
binding.imageViewNew.isVisible = item.hasFlag(FLAG_NEW)
|
||||
binding.imageViewDownloaded.isVisible = item.isDownloaded
|
||||
binding.imageViewNew.isVisible = item.isNew
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,17 @@ class ChapterListItem(
|
||||
return field
|
||||
}
|
||||
|
||||
val status: Int
|
||||
get() = flags and MASK_STATUS
|
||||
val isCurrent: Boolean
|
||||
get() = hasFlag(FLAG_CURRENT)
|
||||
|
||||
fun hasFlag(flag: Int): Boolean {
|
||||
return (flags and flag) == flag
|
||||
}
|
||||
val isUnread: Boolean
|
||||
get() = hasFlag(FLAG_UNREAD)
|
||||
|
||||
val isDownloaded: Boolean
|
||||
get() = hasFlag(FLAG_DOWNLOADED)
|
||||
|
||||
val isNew: Boolean
|
||||
get() = hasFlag(FLAG_NEW)
|
||||
|
||||
fun description(): CharSequence? {
|
||||
val scanlator = chapter.scanlator?.takeUnless { it.isBlank() }
|
||||
@@ -38,6 +43,10 @@ class ChapterListItem(
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasFlag(flag: Int): Boolean {
|
||||
return (flags and flag) == flag
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
@@ -46,9 +55,7 @@ class ChapterListItem(
|
||||
|
||||
if (chapter != other.chapter) return false
|
||||
if (flags != other.flags) return false
|
||||
if (uploadDateMs != other.uploadDateMs) return false
|
||||
|
||||
return true
|
||||
return uploadDateMs == other.uploadDateMs
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
@@ -63,8 +70,6 @@ class ChapterListItem(
|
||||
const val FLAG_UNREAD = 2
|
||||
const val FLAG_CURRENT = 4
|
||||
const val FLAG_NEW = 8
|
||||
const val FLAG_MISSING = 16
|
||||
const val FLAG_DOWNLOADED = 32
|
||||
const val MASK_STATUS = FLAG_UNREAD or FLAG_CURRENT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.details.ui.model
|
||||
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_CURRENT
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_DOWNLOADED
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_MISSING
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_NEW
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_UNREAD
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
@@ -11,14 +10,12 @@ fun MangaChapter.toListItem(
|
||||
isCurrent: Boolean,
|
||||
isUnread: Boolean,
|
||||
isNew: Boolean,
|
||||
isMissing: Boolean,
|
||||
isDownloaded: Boolean,
|
||||
): ChapterListItem {
|
||||
var flags = 0
|
||||
if (isCurrent) flags = flags or FLAG_CURRENT
|
||||
if (isUnread) flags = flags or FLAG_UNREAD
|
||||
if (isNew) flags = flags or FLAG_NEW
|
||||
if (isMissing) flags = flags or FLAG_MISSING
|
||||
if (isDownloaded) flags = flags or FLAG_DOWNLOADED
|
||||
return ChapterListItem(
|
||||
chapter = this,
|
||||
|
||||
@@ -46,7 +46,6 @@ class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemC
|
||||
isCurrent = index == currentPosition,
|
||||
isUnread = index > currentPosition,
|
||||
isNew = false,
|
||||
isMissing = false,
|
||||
isDownloaded = false,
|
||||
)
|
||||
}
|
||||
|
||||
11
app/src/main/res/drawable/bg_badge_primary.xml
Normal file
11
app/src/main/res/drawable/bg_badge_primary.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="?colorPrimary" />
|
||||
<padding
|
||||
android:bottom="2dp"
|
||||
android:left="2dp"
|
||||
android:right="2dp"
|
||||
android:top="2dp" />
|
||||
</shape>
|
||||
@@ -47,6 +47,7 @@
|
||||
<string name="theme">Theme</string>
|
||||
<string name="light">Light</string>
|
||||
<string name="dark">Dark</string>
|
||||
<!-- Should be as abstract as possible -->
|
||||
<string name="automatic">Follow system</string>
|
||||
<string name="pages">Pages</string>
|
||||
<string name="clear">Clear</string>
|
||||
@@ -397,6 +398,7 @@
|
||||
<string name="pause">Pause</string>
|
||||
<string name="resume">Resume</string>
|
||||
<string name="paused">Paused</string>
|
||||
<!-- Menu item; action to remove completed items -->
|
||||
<string name="remove_completed">Remove completed</string>
|
||||
<string name="cancel_all">Cancel all</string>
|
||||
<string name="downloads_wifi_only">Download only via Wi-Fi</string>
|
||||
|
||||
Reference in New Issue
Block a user