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 0cb2c87d7..c2205bf30 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 @@ -15,7 +15,6 @@ import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.databinding.FragmentChaptersBinding import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter @@ -27,7 +26,9 @@ import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderState class ChaptersFragment : BaseFragment(), - OnListItemClickListener, ActionMode.Callback, AdapterView.OnItemSelectedListener { + OnListItemClickListener, + ActionMode.Callback, + AdapterView.OnItemSelectedListener { private val viewModel by sharedViewModel() @@ -105,9 +106,9 @@ class ChaptersFragment : BaseFragment(), else -> super.onOptionsItemSelected(item) } - override fun onItemClick(item: MangaChapter, view: View) { + override fun onItemClick(item: ChapterListItem, view: View) { if (selectionDecoration?.checkedItemsCount != 0) { - selectionDecoration?.toggleItemChecked(item.id) + selectionDecoration?.toggleItemChecked(item.chapter.id) if (selectionDecoration?.checkedItemsCount == 0) { actionMode?.finish() } else { @@ -116,6 +117,10 @@ class ChaptersFragment : BaseFragment(), } return } + if (item.isMissing) { + (activity as? DetailsActivity)?.showChapterMissingDialog(item.chapter.id) + return + } val options = ActivityOptions.makeScaleUpAnimation( view, 0, @@ -127,17 +132,17 @@ class ChaptersFragment : BaseFragment(), ReaderActivity.newIntent( view.context, viewModel.manga.value ?: return, - ReaderState(item.id, 0, 0) + ReaderState(item.chapter.id, 0, 0) ), options.toBundle() ) } - override fun onItemLongClick(item: MangaChapter, view: View): Boolean { + override fun onItemLongClick(item: ChapterListItem, view: View): Boolean { if (actionMode == null) { actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this) } return actionMode?.also { - selectionDecoration?.setItemIsChecked(item.id, true) + selectionDecoration?.setItemIsChecked(item.chapter.id, true) binding.recyclerViewChapters.invalidateItemDecorations() it.invalidate() } != null @@ -148,7 +153,7 @@ class ChaptersFragment : BaseFragment(), R.id.action_save -> { DownloadService.start( context ?: return false, - viewModel.manga.value ?: return false, + viewModel.getRemoteManga() ?: viewModel.manga.value ?: return false, selectionDecoration?.checkedItemsIds ) mode.finish() @@ -174,17 +179,20 @@ class ChaptersFragment : BaseFragment(), override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { val manga = viewModel.manga.value mode.menuInflater.inflate(R.menu.mode_chapters, menu) - menu.findItem(R.id.action_save).isVisible = manga?.source != MangaSource.LOCAL mode.title = manga?.title return true } override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { - val count = selectionDecoration?.checkedItemsCount ?: return false + val selectedIds = selectionDecoration?.checkedItemsIds ?: return false + val items = chaptersAdapter?.items?.filter { x -> x.chapter.id in selectedIds }.orEmpty() + menu.findItem(R.id.action_save).isVisible = items.none { x -> + x.chapter.source == MangaSource.LOCAL + } mode.subtitle = resources.getQuantityString( R.plurals.chapters_from_x, - count, - count, + items.size, + items.size, chaptersAdapter?.itemCount ?: 0 ) return true 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 4a9f6e1ba..84745b56d 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 @@ -34,8 +34,11 @@ import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.os.ShortcutsRepository import org.koitharu.kotatsu.databinding.ActivityDetailsBinding import org.koitharu.kotatsu.download.ui.service.DownloadService +import org.koitharu.kotatsu.reader.ui.ReaderActivity +import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity import org.koitharu.kotatsu.utils.ShareHelper +import org.koitharu.kotatsu.utils.ext.buildAlertDialog import org.koitharu.kotatsu.utils.ext.getDisplayMessage class DetailsActivity : BaseActivity(), @@ -228,6 +231,33 @@ class DetailsActivity : BaseActivity(), binding.pager.isUserInputEnabled = true } + fun showChapterMissingDialog(chapterId: Long) { + val remoteManga = viewModel.getRemoteManga() + if (remoteManga == null) { + Snackbar.make(binding.pager, R.string.chapter_is_missing, Snackbar.LENGTH_LONG) + .show() + return + } + buildAlertDialog(this) { + 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( + this@DetailsActivity, + remoteManga, + ReaderState(chapterId, 0, 0) + ) + ) + } + setNeutralButton(R.string.download) { _, _ -> + DownloadService.start(this@DetailsActivity, remoteManga, setOf(chapterId)) + } + setCancelable(true) + }.show() + } + companion object { const val ACTION_MANGA_VIEW = "${BuildConfig.APPLICATION_ID}.action.VIEW_MANGA" diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt index e71171d51..05ed0a9c4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt @@ -128,23 +128,32 @@ class DetailsFragment : BaseFragment(), View.OnClickList } private fun onLoadingStateChanged(isLoading: Boolean) { - binding.progressBar.isVisible = isLoading + if (isLoading) { + binding.progressBar.show() + } else { + binding.progressBar.hide() + } } override fun onClick(v: View) { - val manga = viewModel.manga.value + val manga = viewModel.manga.value ?: return when (v.id) { R.id.button_favorite -> { - FavouriteCategoriesDialog.show(childFragmentManager, manga ?: return) + FavouriteCategoriesDialog.show(childFragmentManager, manga) } R.id.button_read -> { - startActivity( - ReaderActivity.newIntent( - context ?: return, - manga ?: return, - null + val chapterId = viewModel.readingHistory.value?.chapterId + if (chapterId != null && manga.chapters?.none { x -> x.id == chapterId } == true) { + (activity as? DetailsActivity)?.showChapterMissingDialog(chapterId) + } else { + startActivity( + ReaderActivity.newIntent( + context ?: return, + manga, + null + ) ) - ) + } } } } 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 4c0292ad5..ebd377a50 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 @@ -11,7 +11,11 @@ import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaChapter +import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.details.ui.model.toListItem import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.history.domain.ChapterExtra @@ -29,7 +33,7 @@ class DetailsViewModel( private val localMangaRepository: LocalMangaRepository, private val trackingRepository: TrackingRepository, private val mangaDataRepository: MangaDataRepository, - private val settings: AppSettings + private val settings: AppSettings, ) : BaseViewModel() { private val mangaData = MutableStateFlow(intent.manga) @@ -53,6 +57,18 @@ class DetailsViewModel( trackingRepository.getNewChaptersCount(mangaId) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0) + private val remoteManga = MutableStateFlow(null) + /*private val remoteManga = mangaData.mapLatest { + if (it?.source == MangaSource.LOCAL) { + runCatching { + val m = localMangaRepository.getRemoteManga(it) ?: return@mapLatest null + MangaRepository(m.source).getDetails(m) + }.getOrNull() + } else { + null + } + }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)*/ + private val chaptersReversed = settings.observe() .filter { it == AppSettings.KEY_REVERSE_CHAPTERS } .map { settings.chaptersReverse } @@ -85,24 +101,19 @@ class DetailsViewModel( val chapters = combine( mangaData.map { it?.chapters.orEmpty() }, + remoteManga, history.map { it?.chapterId }, newChapters, - chaptersReversed, selectedBranch - ) { chapters, currentId, newCount, reversed, branch -> - val currentIndex = chapters.indexOfFirst { it.id == currentId } - val firstNewIndex = chapters.size - newCount - val res = chapters.mapIndexed { index, chapter -> - chapter.toListItem( - when { - index >= firstNewIndex -> ChapterExtra.NEW - index == currentIndex -> ChapterExtra.CURRENT - index < currentIndex -> ChapterExtra.READ - else -> ChapterExtra.UNREAD - } - ) - }.filter { it.chapter.branch == branch } - if (reversed) res.asReversed() else res + ) { chapters, sourceManga, currentId, newCount, branch -> + val sourceChapters = sourceManga?.chapters + if (sourceChapters.isNullOrEmpty()) { + mapChapters(chapters, currentId, newCount, branch) + } else { + mapChaptersWithSource(chapters, sourceChapters, currentId, newCount, branch) + } + }.combine(chaptersReversed) { list, reversed -> + if (reversed) list.asReversed() else list }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) init { @@ -121,6 +132,12 @@ class DetailsViewModel( ?.maxByOrNull { it.value.size }?.key } mangaData.value = manga + if (manga.source == MangaSource.LOCAL) { + remoteManga.value = runCatching { + val m = localMangaRepository.getRemoteManga(manga) ?: return@runCatching null + MangaRepository(m.source).getDetails(m) + }.getOrNull() + } } } @@ -142,4 +159,80 @@ class DetailsViewModel( fun setSelectedBranch(branch: String?) { selectedBranch.value = branch } + + fun getRemoteManga(): Manga? { + return remoteManga.value + } + + private fun mapChapters( + chapters: 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 + for (i in chapters.indices) { + val chapter = chapters[i] + if (chapter.branch != branch) { + continue + } + result += chapter.toListItem( + extra = when { + i >= firstNewIndex -> ChapterExtra.NEW + i == currentIndex -> ChapterExtra.CURRENT + i < currentIndex -> ChapterExtra.READ + else -> ChapterExtra.UNREAD + }, + isMissing = false + ) + } + 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] + if (chapter.branch != branch) { + continue + } + val localChapter = chaptersMap.remove(chapter.id) + result += localChapter?.toListItem( + extra = when { + i >= firstNewIndex -> ChapterExtra.NEW + i == currentIndex -> ChapterExtra.CURRENT + i < currentIndex -> ChapterExtra.READ + else -> ChapterExtra.UNREAD + }, + isMissing = false + ) ?: chapter.toListItem( + extra = when { + i >= firstNewIndex -> ChapterExtra.NEW + i == currentIndex -> ChapterExtra.CURRENT + i < currentIndex -> ChapterExtra.READ + else -> ChapterExtra.UNREAD + }, + isMissing = true + ) + } + if (chaptersMap.isNotEmpty()) { // some chapters on device but not online source + result.ensureCapacity(result.size + chaptersMap.size) + chaptersMap.values.mapTo(result) { + it.toListItem(ChapterExtra.UNREAD, false) + } + result.sortBy { it.chapter.number } + } + return result + } } \ No newline at end of file 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 d339216c0..983f322a8 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 @@ -3,23 +3,22 @@ package org.koitharu.kotatsu.details.ui.adapter import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.databinding.ItemChapterBinding import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.history.domain.ChapterExtra import org.koitharu.kotatsu.utils.ext.getThemeColor fun chapterListItemAD( - clickListener: OnListItemClickListener + clickListener: OnListItemClickListener, ) = adapterDelegateViewBinding( { inflater, parent -> ItemChapterBinding.inflate(inflater, parent, false) } ) { itemView.setOnClickListener { - clickListener.onItemClick(item.chapter, it) + clickListener.onItemClick(item, it) } itemView.setOnLongClickListener { - clickListener.onItemLongClick(item.chapter, it) + clickListener.onItemLongClick(item, it) } bind { payload -> @@ -43,5 +42,7 @@ fun chapterListItemAD( binding.textViewNumber.setTextColor(context.getThemeColor(android.R.attr.textColorPrimaryInverse)) } } + binding.textViewTitle.alpha = if (item.isMissing) 0.3f else 1f + binding.textViewNumber.alpha = if (item.isMissing) 0.3f else 1f } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersAdapter.kt index b98a427e7..46dc930d1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersAdapter.kt @@ -3,12 +3,11 @@ package org.koitharu.kotatsu.details.ui.adapter import androidx.recyclerview.widget.DiffUtil import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.details.ui.model.ChapterListItem import kotlin.jvm.internal.Intrinsics class ChaptersAdapter( - onItemClickListener: OnListItemClickListener + onItemClickListener: OnListItemClickListener, ) : AsyncListDifferDelegationAdapter(DiffCallback()) { init { @@ -38,7 +37,7 @@ class ChaptersAdapter( } override fun getChangePayload(oldItem: ChapterListItem, newItem: ChapterListItem): Any? { - if (oldItem.extra != newItem.extra) { + if (oldItem.extra != newItem.extra && oldItem.chapter == newItem.chapter) { return newItem.extra } return null 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 5fad6e03a..82f00decf 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 @@ -5,5 +5,6 @@ import org.koitharu.kotatsu.history.domain.ChapterExtra data class ChapterListItem( val chapter: MangaChapter, - val extra: ChapterExtra + val extra: ChapterExtra, + val isMissing: Boolean, ) 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 dc1df8e0f..0a1609989 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 @@ -3,7 +3,11 @@ package org.koitharu.kotatsu.details.ui.model import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.history.domain.ChapterExtra -fun MangaChapter.toListItem(extra: ChapterExtra) = ChapterListItem( +fun MangaChapter.toListItem( + extra: ChapterExtra, + isMissing: Boolean, +) = ChapterListItem( chapter = this, - extra = extra + extra = extra, + isMissing = isMissing, ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt index bda34d8e2..d44204533 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt @@ -47,6 +47,7 @@ class DownloadService : BaseService() { private val jobCount = MutableStateFlow(0) private val mutex = Mutex() private val controlReceiver = ControlReceiver() + private var binder: DownloadBinder? = null override fun onCreate() { super.onCreate() @@ -75,11 +76,12 @@ class DownloadService : BaseService() { override fun onBind(intent: Intent): IBinder { super.onBind(intent) - return DownloadBinder() + return binder ?: DownloadBinder(this).also { binder = it } } override fun onDestroy() { unregisterReceiver(controlReceiver) + binder = null super.onDestroy() } @@ -141,10 +143,10 @@ class DownloadService : BaseService() { } } - inner class DownloadBinder : Binder() { + class DownloadBinder(private val service: DownloadService) : Binder() { val downloads: Flow>> - get() = jobCount.mapLatest { jobs.values } + get() = service.jobCount.mapLatest { service.jobs.values } } companion object { @@ -160,6 +162,9 @@ class DownloadService : BaseService() { private const val EXTRA_CANCEL_ID = "cancel_id" fun start(context: Context, manga: Manga, chaptersIds: Collection? = null) { + if (chaptersIds?.isEmpty() == true) { + return + } confirmDataTransfer(context) { val intent = Intent(context, DownloadService::class.java) intent.putExtra(EXTRA_MANGA, manga) diff --git a/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt index 59be0a2e5..baaab7400 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt @@ -98,7 +98,10 @@ class LocalMangaRepository(private val context: Context) : MangaRepository { entryName = index.getCoverEntry() ?: findFirstEntry(zip.entries(), isImage = true)?.name.orEmpty() ), - chapters = info.chapters?.map { c -> c.copy(url = fileUri) } + chapters = info.chapters?.map { c -> + c.copy(url = fileUri, + source = MangaSource.LOCAL) + } ) } // fallback diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersDialog.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersDialog.kt index 667d7113a..78d713fff 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersDialog.kt @@ -15,16 +15,17 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.databinding.DialogChaptersBinding import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter +import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.details.ui.model.toListItem import org.koitharu.kotatsu.history.domain.ChapterExtra import org.koitharu.kotatsu.utils.ext.withArgs class ChaptersDialog : AlertDialogFragment(), - OnListItemClickListener { + OnListItemClickListener { override fun onInflateView( inflater: LayoutInflater, - container: ViewGroup? + container: ViewGroup?, ) = DialogChaptersBinding.inflate(inflater, container, false) override fun onBuildDialog(builder: AlertDialog.Builder) { @@ -51,7 +52,8 @@ class ChaptersDialog : AlertDialogFragment(), index < currentPosition -> ChapterExtra.READ index == currentPosition -> ChapterExtra.CURRENT else -> ChapterExtra.UNREAD - } + }, + isMissing = false ) }) { if (currentPosition >= 0) { @@ -66,11 +68,11 @@ class ChaptersDialog : AlertDialogFragment(), } } - override fun onItemClick(item: MangaChapter, view: View) { + override fun onItemClick(item: ChapterListItem, view: View) { ((parentFragment as? OnChapterChangeListener) ?: (activity as? OnChapterChangeListener))?.let { dismiss() - it.onChapterChanged(item) + it.onChapterChanged(item.chapter) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt index b9cc851c4..87278b1d7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt @@ -1,8 +1,10 @@ package org.koitharu.kotatsu.utils.ext +import android.content.Context import android.net.ConnectivityManager import android.net.Network import android.net.NetworkRequest +import androidx.appcompat.app.AlertDialog import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume @@ -19,4 +21,8 @@ suspend fun ConnectivityManager.waitForNetwork(): Network { unregisterNetworkCallback(callback) } } +} + +inline fun buildAlertDialog(context: Context, block: AlertDialog.Builder.() -> Unit): AlertDialog { + return AlertDialog.Builder(context).apply(block).create() } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FlowExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/FlowExt.kt index a3de68094..ef2f7a422 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FlowExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/FlowExt.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.utils.ext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.transform fun Flow.onFirst(action: suspend (T) -> Unit): Flow { var isFirstCall = true @@ -16,4 +17,10 @@ fun Flow.onFirst(action: suspend (T) -> Unit): Flow { inline fun Flow>.mapItems(crossinline transform: (T) -> R): Flow> { return map { list -> list.map(transform) } +} + +inline fun Flow.filterNotNull( + crossinline predicate: suspend (T) -> Boolean, +): Flow = transform { value -> + if (value != null && predicate(value)) return@transform emit(value) } \ No newline at end of file diff --git a/app/src/main/res/layout-w600dp-land/fragment_details.xml b/app/src/main/res/layout-w600dp-land/fragment_details.xml index 51f3213ed..30484f1a8 100644 --- a/app/src/main/res/layout-w600dp-land/fragment_details.xml +++ b/app/src/main/res/layout-w600dp-land/fragment_details.xml @@ -263,13 +263,15 @@ - - Read more Queued There are currently no active downloads + This chapter is missing on your device. Download it or read online + Chapter is missing \ No newline at end of file