Show not downloaded chapters in local manga

This commit is contained in:
Koitharu
2021-07-24 10:51:26 +03:00
parent e8e95a485b
commit 7f5ef227eb
17 changed files with 234 additions and 58 deletions

View File

@@ -15,7 +15,6 @@ import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener 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.core.model.MangaSource
import org.koitharu.kotatsu.databinding.FragmentChaptersBinding import org.koitharu.kotatsu.databinding.FragmentChaptersBinding
import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter 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 import org.koitharu.kotatsu.reader.ui.ReaderState
class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(), class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
OnListItemClickListener<MangaChapter>, ActionMode.Callback, AdapterView.OnItemSelectedListener { OnListItemClickListener<ChapterListItem>,
ActionMode.Callback,
AdapterView.OnItemSelectedListener {
private val viewModel by sharedViewModel<DetailsViewModel>() private val viewModel by sharedViewModel<DetailsViewModel>()
@@ -105,9 +106,9 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun onItemClick(item: MangaChapter, view: View) { override fun onItemClick(item: ChapterListItem, view: View) {
if (selectionDecoration?.checkedItemsCount != 0) { if (selectionDecoration?.checkedItemsCount != 0) {
selectionDecoration?.toggleItemChecked(item.id) selectionDecoration?.toggleItemChecked(item.chapter.id)
if (selectionDecoration?.checkedItemsCount == 0) { if (selectionDecoration?.checkedItemsCount == 0) {
actionMode?.finish() actionMode?.finish()
} else { } else {
@@ -116,6 +117,10 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
} }
return return
} }
if (item.isMissing) {
(activity as? DetailsActivity)?.showChapterMissingDialog(item.chapter.id)
return
}
val options = ActivityOptions.makeScaleUpAnimation( val options = ActivityOptions.makeScaleUpAnimation(
view, view,
0, 0,
@@ -127,17 +132,17 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
ReaderActivity.newIntent( ReaderActivity.newIntent(
view.context, view.context,
viewModel.manga.value ?: return, viewModel.manga.value ?: return,
ReaderState(item.id, 0, 0) ReaderState(item.chapter.id, 0, 0)
), options.toBundle() ), options.toBundle()
) )
} }
override fun onItemLongClick(item: MangaChapter, view: View): Boolean { override fun onItemLongClick(item: ChapterListItem, view: View): Boolean {
if (actionMode == null) { if (actionMode == null) {
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this) actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
} }
return actionMode?.also { return actionMode?.also {
selectionDecoration?.setItemIsChecked(item.id, true) selectionDecoration?.setItemIsChecked(item.chapter.id, true)
binding.recyclerViewChapters.invalidateItemDecorations() binding.recyclerViewChapters.invalidateItemDecorations()
it.invalidate() it.invalidate()
} != null } != null
@@ -148,7 +153,7 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
R.id.action_save -> { R.id.action_save -> {
DownloadService.start( DownloadService.start(
context ?: return false, context ?: return false,
viewModel.manga.value ?: return false, viewModel.getRemoteManga() ?: viewModel.manga.value ?: return false,
selectionDecoration?.checkedItemsIds selectionDecoration?.checkedItemsIds
) )
mode.finish() mode.finish()
@@ -174,17 +179,20 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
val manga = viewModel.manga.value val manga = viewModel.manga.value
mode.menuInflater.inflate(R.menu.mode_chapters, menu) mode.menuInflater.inflate(R.menu.mode_chapters, menu)
menu.findItem(R.id.action_save).isVisible = manga?.source != MangaSource.LOCAL
mode.title = manga?.title mode.title = manga?.title
return true return true
} }
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { 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( mode.subtitle = resources.getQuantityString(
R.plurals.chapters_from_x, R.plurals.chapters_from_x,
count, items.size,
count, items.size,
chaptersAdapter?.itemCount ?: 0 chaptersAdapter?.itemCount ?: 0
) )
return true return true

View File

@@ -34,8 +34,11 @@ import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.os.ShortcutsRepository import org.koitharu.kotatsu.core.os.ShortcutsRepository
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
import org.koitharu.kotatsu.download.ui.service.DownloadService 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.search.ui.global.GlobalSearchActivity
import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.buildAlertDialog
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class DetailsActivity : BaseActivity<ActivityDetailsBinding>(), class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
@@ -228,6 +231,33 @@ class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
binding.pager.isUserInputEnabled = true 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 { companion object {
const val ACTION_MANGA_VIEW = "${BuildConfig.APPLICATION_ID}.action.VIEW_MANGA" const val ACTION_MANGA_VIEW = "${BuildConfig.APPLICATION_ID}.action.VIEW_MANGA"

View File

@@ -128,23 +128,32 @@ class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickList
} }
private fun onLoadingStateChanged(isLoading: Boolean) { private fun onLoadingStateChanged(isLoading: Boolean) {
binding.progressBar.isVisible = isLoading if (isLoading) {
binding.progressBar.show()
} else {
binding.progressBar.hide()
}
} }
override fun onClick(v: View) { override fun onClick(v: View) {
val manga = viewModel.manga.value val manga = viewModel.manga.value ?: return
when (v.id) { when (v.id) {
R.id.button_favorite -> { R.id.button_favorite -> {
FavouriteCategoriesDialog.show(childFragmentManager, manga ?: return) FavouriteCategoriesDialog.show(childFragmentManager, manga)
} }
R.id.button_read -> { R.id.button_read -> {
startActivity( val chapterId = viewModel.readingHistory.value?.chapterId
ReaderActivity.newIntent( if (chapterId != null && manga.chapters?.none { x -> x.id == chapterId } == true) {
context ?: return, (activity as? DetailsActivity)?.showChapterMissingDialog(chapterId)
manga ?: return, } else {
null startActivity(
ReaderActivity.newIntent(
context ?: return,
manga,
null
)
) )
) }
} }
} }
} }

View File

@@ -11,7 +11,11 @@ import org.koitharu.kotatsu.base.domain.MangaIntent
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException
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.MangaSource
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings 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.details.ui.model.toListItem
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.domain.ChapterExtra import org.koitharu.kotatsu.history.domain.ChapterExtra
@@ -29,7 +33,7 @@ class DetailsViewModel(
private val localMangaRepository: LocalMangaRepository, private val localMangaRepository: LocalMangaRepository,
private val trackingRepository: TrackingRepository, private val trackingRepository: TrackingRepository,
private val mangaDataRepository: MangaDataRepository, private val mangaDataRepository: MangaDataRepository,
private val settings: AppSettings private val settings: AppSettings,
) : BaseViewModel() { ) : BaseViewModel() {
private val mangaData = MutableStateFlow<Manga?>(intent.manga) private val mangaData = MutableStateFlow<Manga?>(intent.manga)
@@ -53,6 +57,18 @@ class DetailsViewModel(
trackingRepository.getNewChaptersCount(mangaId) trackingRepository.getNewChaptersCount(mangaId)
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0)
private val remoteManga = MutableStateFlow<Manga?>(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() private val chaptersReversed = settings.observe()
.filter { it == AppSettings.KEY_REVERSE_CHAPTERS } .filter { it == AppSettings.KEY_REVERSE_CHAPTERS }
.map { settings.chaptersReverse } .map { settings.chaptersReverse }
@@ -85,24 +101,19 @@ class DetailsViewModel(
val chapters = combine( val chapters = combine(
mangaData.map { it?.chapters.orEmpty() }, mangaData.map { it?.chapters.orEmpty() },
remoteManga,
history.map { it?.chapterId }, history.map { it?.chapterId },
newChapters, newChapters,
chaptersReversed,
selectedBranch selectedBranch
) { chapters, currentId, newCount, reversed, branch -> ) { chapters, sourceManga, currentId, newCount, branch ->
val currentIndex = chapters.indexOfFirst { it.id == currentId } val sourceChapters = sourceManga?.chapters
val firstNewIndex = chapters.size - newCount if (sourceChapters.isNullOrEmpty()) {
val res = chapters.mapIndexed { index, chapter -> mapChapters(chapters, currentId, newCount, branch)
chapter.toListItem( } else {
when { mapChaptersWithSource(chapters, sourceChapters, currentId, newCount, branch)
index >= firstNewIndex -> ChapterExtra.NEW }
index == currentIndex -> ChapterExtra.CURRENT }.combine(chaptersReversed) { list, reversed ->
index < currentIndex -> ChapterExtra.READ if (reversed) list.asReversed() else list
else -> ChapterExtra.UNREAD
}
)
}.filter { it.chapter.branch == branch }
if (reversed) res.asReversed() else res
}.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
init { init {
@@ -121,6 +132,12 @@ class DetailsViewModel(
?.maxByOrNull { it.value.size }?.key ?.maxByOrNull { it.value.size }?.key
} }
mangaData.value = manga 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?) { fun setSelectedBranch(branch: String?) {
selectedBranch.value = branch selectedBranch.value = branch
} }
fun getRemoteManga(): Manga? {
return remoteManga.value
}
private fun mapChapters(
chapters: 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
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<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]
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
}
} }

View File

@@ -3,23 +3,22 @@ package org.koitharu.kotatsu.details.ui.adapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener 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.databinding.ItemChapterBinding
import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.history.domain.ChapterExtra import org.koitharu.kotatsu.history.domain.ChapterExtra
import org.koitharu.kotatsu.utils.ext.getThemeColor import org.koitharu.kotatsu.utils.ext.getThemeColor
fun chapterListItemAD( fun chapterListItemAD(
clickListener: OnListItemClickListener<MangaChapter> clickListener: OnListItemClickListener<ChapterListItem>,
) = adapterDelegateViewBinding<ChapterListItem, ChapterListItem, ItemChapterBinding>( ) = adapterDelegateViewBinding<ChapterListItem, ChapterListItem, ItemChapterBinding>(
{ inflater, parent -> ItemChapterBinding.inflate(inflater, parent, false) } { inflater, parent -> ItemChapterBinding.inflate(inflater, parent, false) }
) { ) {
itemView.setOnClickListener { itemView.setOnClickListener {
clickListener.onItemClick(item.chapter, it) clickListener.onItemClick(item, it)
} }
itemView.setOnLongClickListener { itemView.setOnLongClickListener {
clickListener.onItemLongClick(item.chapter, it) clickListener.onItemLongClick(item, it)
} }
bind { payload -> bind { payload ->
@@ -43,5 +42,7 @@ fun chapterListItemAD(
binding.textViewNumber.setTextColor(context.getThemeColor(android.R.attr.textColorPrimaryInverse)) 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
} }
} }

View File

@@ -3,12 +3,11 @@ package org.koitharu.kotatsu.details.ui.adapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import kotlin.jvm.internal.Intrinsics import kotlin.jvm.internal.Intrinsics
class ChaptersAdapter( class ChaptersAdapter(
onItemClickListener: OnListItemClickListener<MangaChapter> onItemClickListener: OnListItemClickListener<ChapterListItem>,
) : AsyncListDifferDelegationAdapter<ChapterListItem>(DiffCallback()) { ) : AsyncListDifferDelegationAdapter<ChapterListItem>(DiffCallback()) {
init { init {
@@ -38,7 +37,7 @@ class ChaptersAdapter(
} }
override fun getChangePayload(oldItem: ChapterListItem, newItem: ChapterListItem): Any? { 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 newItem.extra
} }
return null return null

View File

@@ -5,5 +5,6 @@ import org.koitharu.kotatsu.history.domain.ChapterExtra
data class ChapterListItem( data class ChapterListItem(
val chapter: MangaChapter, val chapter: MangaChapter,
val extra: ChapterExtra val extra: ChapterExtra,
val isMissing: Boolean,
) )

View File

@@ -3,7 +3,11 @@ package org.koitharu.kotatsu.details.ui.model
import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.history.domain.ChapterExtra import org.koitharu.kotatsu.history.domain.ChapterExtra
fun MangaChapter.toListItem(extra: ChapterExtra) = ChapterListItem( fun MangaChapter.toListItem(
extra: ChapterExtra,
isMissing: Boolean,
) = ChapterListItem(
chapter = this, chapter = this,
extra = extra extra = extra,
isMissing = isMissing,
) )

View File

@@ -47,6 +47,7 @@ class DownloadService : BaseService() {
private val jobCount = MutableStateFlow(0) private val jobCount = MutableStateFlow(0)
private val mutex = Mutex() private val mutex = Mutex()
private val controlReceiver = ControlReceiver() private val controlReceiver = ControlReceiver()
private var binder: DownloadBinder? = null
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@@ -75,11 +76,12 @@ class DownloadService : BaseService() {
override fun onBind(intent: Intent): IBinder { override fun onBind(intent: Intent): IBinder {
super.onBind(intent) super.onBind(intent)
return DownloadBinder() return binder ?: DownloadBinder(this).also { binder = it }
} }
override fun onDestroy() { override fun onDestroy() {
unregisterReceiver(controlReceiver) unregisterReceiver(controlReceiver)
binder = null
super.onDestroy() super.onDestroy()
} }
@@ -141,10 +143,10 @@ class DownloadService : BaseService() {
} }
} }
inner class DownloadBinder : Binder() { class DownloadBinder(private val service: DownloadService) : Binder() {
val downloads: Flow<Collection<JobStateFlow<DownloadManager.State>>> val downloads: Flow<Collection<JobStateFlow<DownloadManager.State>>>
get() = jobCount.mapLatest { jobs.values } get() = service.jobCount.mapLatest { service.jobs.values }
} }
companion object { companion object {
@@ -160,6 +162,9 @@ class DownloadService : BaseService() {
private const val EXTRA_CANCEL_ID = "cancel_id" private const val EXTRA_CANCEL_ID = "cancel_id"
fun start(context: Context, manga: Manga, chaptersIds: Collection<Long>? = null) { fun start(context: Context, manga: Manga, chaptersIds: Collection<Long>? = null) {
if (chaptersIds?.isEmpty() == true) {
return
}
confirmDataTransfer(context) { confirmDataTransfer(context) {
val intent = Intent(context, DownloadService::class.java) val intent = Intent(context, DownloadService::class.java)
intent.putExtra(EXTRA_MANGA, manga) intent.putExtra(EXTRA_MANGA, manga)

View File

@@ -98,7 +98,10 @@ class LocalMangaRepository(private val context: Context) : MangaRepository {
entryName = index.getCoverEntry() entryName = index.getCoverEntry()
?: findFirstEntry(zip.entries(), isImage = true)?.name.orEmpty() ?: 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 // fallback

View File

@@ -15,16 +15,17 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.databinding.DialogChaptersBinding import org.koitharu.kotatsu.databinding.DialogChaptersBinding
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter 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.details.ui.model.toListItem
import org.koitharu.kotatsu.history.domain.ChapterExtra import org.koitharu.kotatsu.history.domain.ChapterExtra
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class ChaptersDialog : AlertDialogFragment<DialogChaptersBinding>(), class ChaptersDialog : AlertDialogFragment<DialogChaptersBinding>(),
OnListItemClickListener<MangaChapter> { OnListItemClickListener<ChapterListItem> {
override fun onInflateView( override fun onInflateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup? container: ViewGroup?,
) = DialogChaptersBinding.inflate(inflater, container, false) ) = DialogChaptersBinding.inflate(inflater, container, false)
override fun onBuildDialog(builder: AlertDialog.Builder) { override fun onBuildDialog(builder: AlertDialog.Builder) {
@@ -51,7 +52,8 @@ class ChaptersDialog : AlertDialogFragment<DialogChaptersBinding>(),
index < currentPosition -> ChapterExtra.READ index < currentPosition -> ChapterExtra.READ
index == currentPosition -> ChapterExtra.CURRENT index == currentPosition -> ChapterExtra.CURRENT
else -> ChapterExtra.UNREAD else -> ChapterExtra.UNREAD
} },
isMissing = false
) )
}) { }) {
if (currentPosition >= 0) { if (currentPosition >= 0) {
@@ -66,11 +68,11 @@ class ChaptersDialog : AlertDialogFragment<DialogChaptersBinding>(),
} }
} }
override fun onItemClick(item: MangaChapter, view: View) { override fun onItemClick(item: ChapterListItem, view: View) {
((parentFragment as? OnChapterChangeListener) ((parentFragment as? OnChapterChangeListener)
?: (activity as? OnChapterChangeListener))?.let { ?: (activity as? OnChapterChangeListener))?.let {
dismiss() dismiss()
it.onChapterChanged(item) it.onChapterChanged(item.chapter)
} }
} }

View File

@@ -1,8 +1,10 @@
package org.koitharu.kotatsu.utils.ext package org.koitharu.kotatsu.utils.ext
import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.Network import android.net.Network
import android.net.NetworkRequest import android.net.NetworkRequest
import androidx.appcompat.app.AlertDialog
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume import kotlin.coroutines.resume
@@ -19,4 +21,8 @@ suspend fun ConnectivityManager.waitForNetwork(): Network {
unregisterNetworkCallback(callback) unregisterNetworkCallback(callback)
} }
} }
}
inline fun buildAlertDialog(context: Context, block: AlertDialog.Builder.() -> Unit): AlertDialog {
return AlertDialog.Builder(context).apply(block).create()
} }

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.utils.ext
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.transform
fun <T> Flow<T>.onFirst(action: suspend (T) -> Unit): Flow<T> { fun <T> Flow<T>.onFirst(action: suspend (T) -> Unit): Flow<T> {
var isFirstCall = true var isFirstCall = true
@@ -16,4 +17,10 @@ fun <T> Flow<T>.onFirst(action: suspend (T) -> Unit): Flow<T> {
inline fun <T, R> Flow<List<T>>.mapItems(crossinline transform: (T) -> R): Flow<List<R>> { inline fun <T, R> Flow<List<T>>.mapItems(crossinline transform: (T) -> R): Flow<List<R>> {
return map { list -> list.map(transform) } return map { list -> list.map(transform) }
}
inline fun <T> Flow<T?>.filterNotNull(
crossinline predicate: suspend (T) -> Boolean,
): Flow<T> = transform { value ->
if (value != null && predicate(value)) return@transform emit(value)
} }

View File

@@ -263,13 +263,15 @@
</LinearLayout> </LinearLayout>
<ProgressBar <com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar" android:id="@+id/progressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal" style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:indeterminate="true" android:indeterminate="true"
android:visibility="gone" android:visibility="gone"
app:showAnimationBehavior="inward"
app:hideAnimationBehavior="outward"
app:layout_constraintBottom_toTopOf="parent" app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@@ -270,13 +270,15 @@
tools:ignore="UnusedAttribute" tools:ignore="UnusedAttribute"
tools:text="@tools:sample/lorem/random[25]" /> tools:text="@tools:sample/lorem/random[25]" />
<ProgressBar <com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar" android:id="@+id/progressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal" style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:indeterminate="true" android:indeterminate="true"
android:visibility="gone" android:visibility="gone"
app:showAnimationBehavior="inward"
app:hideAnimationBehavior="outward"
app:layout_constraintBottom_toTopOf="parent" app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@@ -277,6 +277,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:indeterminate="true" android:indeterminate="true"
android:visibility="gone" android:visibility="gone"
app:showAnimationBehavior="inward"
app:hideAnimationBehavior="outward"
app:layout_constraintBottom_toTopOf="parent" app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@@ -222,4 +222,6 @@
<string name="read_more">Read more</string> <string name="read_more">Read more</string>
<string name="queued">Queued</string> <string name="queued">Queued</string>
<string name="text_downloads_holder">There are currently no active downloads</string> <string name="text_downloads_holder">There are currently no active downloads</string>
<string name="chapter_is_missing_text">This chapter is missing on your device. Download it or read online</string>
<string name="chapter_is_missing">Chapter is missing</string>
</resources> </resources>