From a8f5714b35e4a3cc7d90b2fbacf996e5f9ba11c4 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 22 May 2023 18:08:05 +0300 Subject: [PATCH] Refactor chapters mapping --- .../details/ui/ChaptersBottomSheetMediator.kt | 3 + .../kotatsu/details/ui/ChaptersFragment.kt | 6 +- .../kotatsu/details/ui/ChaptersMapper.kt | 60 +++++++ .../kotatsu/details/ui/DetailsActivity.kt | 34 +--- .../kotatsu/details/ui/DetailsViewModel.kt | 93 ++++++----- .../details/ui/MangaDetailsDelegate.kt | 149 +++++------------- .../details/ui/adapter/ChapterListItemAD.kt | 29 ++-- .../details/ui/model/ChapterListItem.kt | 25 +-- .../ui/model/ListModelConversionExt.kt | 3 - .../kotatsu/reader/ui/ChaptersBottomSheet.kt | 1 - .../main/res/drawable/bg_badge_primary.xml | 11 ++ app/src/main/res/values/strings.xml | 2 + 12 files changed, 199 insertions(+), 217 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersMapper.kt create mode 100644 app/src/main/res/drawable/bg_badge_primary.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersBottomSheetMediator.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersBottomSheetMediator.kt index c6921d6e8..2e0929ae9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersBottomSheetMediator.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersBottomSheetMediator.kt @@ -32,6 +32,9 @@ class ChaptersBottomSheetMediator( override fun onExpansionStateChanged(headerBar: BottomSheetHeaderBar, isExpanded: Boolean) { isEnabled = isExpanded + if (!isExpanded) { + unlock() + } } override fun onLayoutChange( diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt index 93d79c01e..e82993114 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt @@ -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) { 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( diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersMapper.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersMapper.kt new file mode 100644 index 000000000..618d623e1 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersMapper.kt @@ -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 { + 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(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 +} diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index 0a97180c6..f090a47fa 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -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 { 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) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 69f666b20..0d3101f28 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -78,6 +78,13 @@ class DetailsViewModel @Inject constructor( val onShowToast = SingleLiveEvent() val onDownloadStarted = SingleLiveEvent() + 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 = 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> = 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 = 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?) { 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 concat(a: List?, b: List?): List { + return when { + a == null && b == null -> emptyList() + a == null && b != null -> b + a != null && b == null -> a + a != null && b != null -> buildList(a.size + b.size) { + addAll(a) + addAll(b) + } + + else -> error("This shouldn't have happened") + } + } } diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt index c866a2c52..eee5e04e6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt @@ -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(null) + private val localMangaStateFlow = MutableStateFlow(null) - val selectedBranch = MutableStateFlow(null) + val onlineManga = combine( + onlineMangaStateFlow, + networkState, + ) { m, s -> m.takeIf { s } } + .stateIn(viewModelScope, SharingStarted.Lazily, null) + val localManga = localMangaStateFlow.asStateFlow() - // Remote manga for saved and saved for remote - val relatedManga = MutableStateFlow(null) - val manga: StateFlow - get() = mangaData + val selectedBranch = MutableStateFlow(null) 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() - } - - fun mapChapters( - manga: Manga?, - related: Manga?, - history: MangaHistory?, - newCount: Int, - branch: String?, - ): List { - 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) - } else { - mapChapters(chapters, relatedChapters, history?.chapterId, newCount, branch) - } - } - - private fun mapChapters( - chapters: List, - downloadedChapters: List?, - currentId: Long?, - newCount: Int, - branch: String?, - ): List { - val result = ArrayList(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 + }.onSuccess { + if (it != null) { + publishManga(it) } - 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, - sourceChapters: List, - currentId: Long?, - newCount: Int, - branch: String?, - ): List { - val chaptersMap = chapters.associateByTo(HashMap(chapters.size)) { it.id } - val result = ArrayList(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 + fun publishManga(manga: Manga) { + if (manga.source == MangaSource.LOCAL) { + localMangaStateFlow + } else { + onlineMangaStateFlow + }.value = manga } } diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt index ea7dcdd54..6ff818ccf 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt @@ -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, @@ -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 } } diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt index 35b7aec12..2a62acb1e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt @@ -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 } } diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt index 8f555e39c..73d70d3df 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt @@ -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, diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersBottomSheet.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersBottomSheet.kt index 29caaca0a..568a79e1e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersBottomSheet.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersBottomSheet.kt @@ -46,7 +46,6 @@ class ChaptersBottomSheet : BaseBottomSheet(), OnListItemC isCurrent = index == currentPosition, isUnread = index > currentPosition, isNew = false, - isMissing = false, isDownloaded = false, ) } diff --git a/app/src/main/res/drawable/bg_badge_primary.xml b/app/src/main/res/drawable/bg_badge_primary.xml new file mode 100644 index 000000000..1393b8638 --- /dev/null +++ b/app/src/main/res/drawable/bg_badge_primary.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index df7cf2741..2a5de47a9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,6 +47,7 @@ Theme Light Dark + Follow system Pages Clear @@ -397,6 +398,7 @@ Pause Resume Paused + Remove completed Cancel all Download only via Wi-Fi