diff --git a/.idea/misc.xml b/.idea/misc.xml index 56dc302cc..489f14cd9 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,8 +4,12 @@ diff --git a/app/build.gradle b/app/build.gradle index d806ee47b..f16fddbc1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -103,7 +103,7 @@ dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6' - testImplementation 'junit:junit:4.13.1' + testImplementation 'junit:junit:4.13.2' testImplementation 'org.json:json:20201115' testImplementation 'org.koin:koin-test:2.2.2' } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaChapter.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaChapter.kt index 62c27c781..0682eadc6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaChapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaChapter.kt @@ -9,5 +9,6 @@ data class MangaChapter( val name: String, val number: Int, val url: String, + val branch: String? = null, val source: MangaSource ) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt index 8458ed0f4..8d2c6c87f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt @@ -90,6 +90,8 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) : chapters = ArrayList(total) for (i in 0 until total) { val item = list.getJSONObject(i) + val chapterId = item.getLong("chapter_id") + val branchName = item.getStringOrNull("username") val url = buildString { append(manga.url) append("/v") @@ -106,9 +108,10 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) : } chapters.add( MangaChapter( - id = generateUid(url), + id = generateUid(chapterId), url = url, source = source, + branch = branchName, number = total - i, name = name ) 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 8ebdf8389..447a90c72 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 @@ -3,13 +3,10 @@ package org.koitharu.kotatsu.details.ui import android.app.ActivityOptions import android.os.Bundle import android.view.* +import android.widget.AdapterView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.core.graphics.Insets -import androidx.core.view.isVisible -import androidx.core.view.updatePadding -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.RecyclerView import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment @@ -17,6 +14,7 @@ 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 import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration import org.koitharu.kotatsu.details.ui.model.ChapterListItem @@ -25,7 +23,7 @@ import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderState class ChaptersFragment : BaseFragment(), - OnListItemClickListener, ActionMode.Callback { + OnListItemClickListener, ActionMode.Callback, AdapterView.OnItemSelectedListener { private val viewModel by sharedViewModel() @@ -53,9 +51,21 @@ class ChaptersFragment : BaseFragment(), setHasFixedSize(true) adapter = chaptersAdapter } + val branchesAdapter = BranchesAdapter() + binding.spinnerBranches.adapter = branchesAdapter + binding.spinnerBranches.onItemSelectedListener = this viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged) viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged) + viewModel.branches.observe(viewLifecycleOwner) { + branchesAdapter.setItems(it) + binding.spinnerBranches.isVisible = it.size > 1 + } + viewModel.selectedBranchIndex.observe(viewLifecycleOwner) { + if (it != -1 && it != binding.spinnerBranches.selectedItemPosition) { + binding.spinnerBranches.setSelection(it) + } + } viewModel.isChaptersReversed.observe(viewLifecycleOwner) { activity?.invalidateOptionsMenu() } @@ -64,6 +74,7 @@ class ChaptersFragment : BaseFragment(), override fun onDestroyView() { chaptersAdapter = null selectionDecoration = null + binding.spinnerBranches.adapter = null super.onDestroyView() } @@ -145,6 +156,12 @@ class ChaptersFragment : BaseFragment(), } } + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + viewModel.setSelectedBranch(binding.spinnerBranches.selectedItem as String?) + } + + override fun onNothingSelected(parent: AdapterView<*>?) = Unit + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { val manga = viewModel.manga.value mode.menuInflater.inflate(R.menu.mode_chapters, menu) @@ -174,7 +191,7 @@ class ChaptersFragment : BaseFragment(), binding.recyclerViewChapters.updatePadding( left = insets.left, right = insets.right, - bottom = insets.bottom + bottom = insets.bottom + binding.spinnerBranches.height ) } 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 247af2b9f..4c0292ad5 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 @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.details.ui +import androidx.lifecycle.asFlow import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers @@ -18,6 +19,7 @@ import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.SingleLiveEvent +import org.koitharu.kotatsu.utils.ext.mapToSet import java.io.IOException class DetailsViewModel( @@ -31,6 +33,7 @@ class DetailsViewModel( ) : BaseViewModel() { private val mangaData = MutableStateFlow(intent.manga) + private val selectedBranch = MutableStateFlow(null) private val history = mangaData.mapNotNull { it?.id } .distinctUntilChanged() @@ -69,12 +72,24 @@ class DetailsViewModel( val onMangaRemoved = SingleLiveEvent() + val branches = mangaData.map { + it?.chapters?.mapToSet { x -> x.branch }?.sortedBy { x -> x }.orEmpty() + }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + + val selectedBranchIndex = combine( + branches.asFlow(), + selectedBranch + ) { branches, selected -> + branches.indexOf(selected) + }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + val chapters = combine( mangaData.map { it?.chapters.orEmpty() }, history.map { it?.chapterId }, newChapters, - chaptersReversed - ) { chapters, currentId, newCount, reversed -> + 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 -> @@ -86,7 +101,7 @@ class DetailsViewModel( else -> ChapterExtra.UNREAD } ) - } + }.filter { it.chapter.branch == branch } if (reversed) res.asReversed() else res }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) @@ -96,6 +111,15 @@ class DetailsViewModel( ?: throw MangaNotFoundException("Cannot find manga") mangaData.value = manga manga = manga.source.repository.getDetails(manga) + // find default branch + val hist = historyRepository.getOne(manga) + selectedBranch.value = if (hist != null) { + manga.chapters?.find { it.id == hist.chapterId }?.branch + } else { + manga.chapters + ?.groupBy { it.branch } + ?.maxByOrNull { it.value.size }?.key + } mangaData.value = manga } } @@ -114,4 +138,8 @@ class DetailsViewModel( fun setChaptersReversed(newValue: Boolean) { settings.chaptersReverse = newValue } + + fun setSelectedBranch(branch: String?) { + selectedBranch.value = branch + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt new file mode 100644 index 000000000..5cee213a7 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt @@ -0,0 +1,45 @@ +package org.koitharu.kotatsu.details.ui.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.TextView +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.utils.ext.replaceWith + +class BranchesAdapter : BaseAdapter() { + + private val dataSet = ArrayList() + + override fun getCount(): Int { + return dataSet.size + } + + override fun getItem(position: Int): Any? { + return dataSet[position] + } + + override fun getItemId(position: Int): Long { + return dataSet[position].hashCode().toLong() + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = convertView ?: LayoutInflater.from(parent.context) + .inflate(R.layout.item_branch, parent, false) + (view as TextView).text = dataSet[position] + return view + } + + override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = convertView ?: LayoutInflater.from(parent.context) + .inflate(R.layout.item_branch_dropdown, parent, false) + (view as TextView).text = dataSet[position] + return view + } + + fun setItems(items: Collection) { + dataSet.replaceWith(items) + notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index 573f9c878..adb4a5914 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -40,7 +40,7 @@ class ReaderViewModel( private var loadingJob: Job? = null private val currentState = MutableStateFlow(null) - private val mangaData = MutableStateFlow(intent.manga) + private val mangaData = MutableStateFlow(intent.manga) private val chapters = LongSparseArray() val readerMode = MutableLiveData() @@ -58,7 +58,7 @@ class ReaderViewModel( ) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default) - val content = MutableLiveData(ReaderContent(emptyList(), null)) + val content = MutableLiveData(ReaderContent(emptyList(), null)) val manga: Manga? get() = mangaData.value @@ -80,7 +80,6 @@ class ReaderViewModel( manga.chapters?.forEach { chapters.put(it.id, it) } - mangaData.value = manga // determine mode val mode = dataRepository.getReaderMode(manga.id) ?: manga.chapters?.randomOrNull()?.let { @@ -96,6 +95,9 @@ class ReaderViewModel( currentState.value = state ?: historyRepository.getOne(manga)?.let { ReaderState.from(it) } ?: ReaderState.initial(manga) + + val branch = chapters[currentState.value?.chapterId ?: 0L].branch + mangaData.value = manga.copy(chapters = manga.chapters?.filter { it.branch == branch }) readerMode.postValue(mode) val pages = loadChapter(requireNotNull(currentState.value).chapterId) diff --git a/app/src/main/res/layout/fragment_chapters.xml b/app/src/main/res/layout/fragment_chapters.xml index c4671d8c2..50a4ed6f6 100644 --- a/app/src/main/res/layout/fragment_chapters.xml +++ b/app/src/main/res/layout/fragment_chapters.xml @@ -1,11 +1,29 @@ - + + + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_branch.xml b/app/src/main/res/layout/item_branch.xml new file mode 100644 index 000000000..ee1a60392 --- /dev/null +++ b/app/src/main/res/layout/item_branch.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_branch_dropdown.xml b/app/src/main/res/layout/item_branch_dropdown.xml new file mode 100644 index 000000000..3c81e914d --- /dev/null +++ b/app/src/main/res/layout/item_branch_dropdown.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8c3b6cad5..4933dce61 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.4.30' + ext.kotlin_version = '1.4.31' repositories { google() jcenter()