diff --git a/.editorconfig b/.editorconfig
index e52454a9d..00754a4a4 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -8,6 +8,7 @@ indent_style = tab
insert_final_newline = false
max_line_length = 120
tab_width = 4
+disabled_rules=no-wildcard-imports,no-unused-imports
[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]
ij_continuation_indent_size = 4
diff --git a/.idea/ktlint.xml b/.idea/ktlint.xml
new file mode 100644
index 000000000..e1ecd151a
--- /dev/null
+++ b/.idea/ktlint.xml
@@ -0,0 +1,7 @@
+
+
+
+ true
+ false
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 3dfb0d13f..1bdb3f201 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -66,7 +66,7 @@ android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
- implementation 'com.github.nv95:kotatsu-parsers:fe243c8acf'
+ implementation 'com.github.nv95:kotatsu-parsers:e15dbf2a4b'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
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 d90b74d01..cd08c6139 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
@@ -7,11 +7,11 @@ import android.widget.AdapterView
import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
+import androidx.appcompat.widget.SearchView
import androidx.core.graphics.Insets
+import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
-import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.divider.MaterialDividerItemDecoration
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment
@@ -27,10 +27,13 @@ import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
-class ChaptersFragment : BaseFragment(),
+class ChaptersFragment :
+ BaseFragment(),
OnListItemClickListener,
ActionMode.Callback,
- AdapterView.OnItemSelectedListener {
+ AdapterView.OnItemSelectedListener,
+ MenuItem.OnActionExpandListener,
+ SearchView.OnQueryTextListener {
private val viewModel by sharedViewModel()
@@ -63,6 +66,10 @@ class ChaptersFragment : BaseFragment(),
viewModel.isChaptersReversed.observe(viewLifecycleOwner) {
activity?.invalidateOptionsMenu()
}
+ viewModel.hasChapters.observe(viewLifecycleOwner) {
+ binding.textViewHolder.isGone = it
+ activity?.invalidateOptionsMenu()
+ }
}
override fun onDestroyView() {
@@ -75,11 +82,18 @@ class ChaptersFragment : BaseFragment(),
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.opt_chapters, menu)
+ val searchMenuItem = menu.findItem(R.id.action_search)
+ searchMenuItem.setOnActionExpandListener(this)
+ val searchView = searchMenuItem.actionView as SearchView
+ searchView.setOnQueryTextListener(this)
+ searchView.setIconifiedByDefault(false)
+ searchView.queryHint = searchMenuItem.title
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
menu.findItem(R.id.action_reversed).isChecked = viewModel.isChaptersReversed.value == true
+ menu.findItem(R.id.action_search).isVisible = viewModel.hasChapters.value == true
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
@@ -117,7 +131,8 @@ class ChaptersFragment : BaseFragment(),
view.context,
viewModel.manga.value ?: return,
ReaderState(item.chapter.id, 0, 0)
- ), options.toBundle()
+ ),
+ options.toBundle()
)
}
@@ -189,6 +204,21 @@ class ChaptersFragment : BaseFragment(),
actionMode = null
}
+ override fun onMenuItemActionExpand(item: MenuItem?): Boolean = true
+
+ override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
+ (item?.actionView as? SearchView)?.setQuery("", false)
+ viewModel.performChapterSearch(null)
+ return true
+ }
+
+ override fun onQueryTextSubmit(query: String?): Boolean = false
+
+ override fun onQueryTextChange(newText: String?): Boolean {
+ viewModel.performChapterSearch(newText)
+ return true
+ }
+
override fun onWindowInsetsChanged(insets: Insets) {
binding.recyclerViewChapters.updatePadding(
bottom = insets.bottom + (binding.spinnerBranches?.height ?: 0),
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 5acdfd109..2d182693d 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
@@ -4,9 +4,7 @@ import android.app.ActivityOptions
import android.os.Bundle
import android.text.Spanned
import android.text.method.LinkMovementMethod
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.view.*
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.net.toUri
@@ -38,12 +36,20 @@ import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.utils.FileSize
import org.koitharu.kotatsu.utils.ext.*
-class DetailsFragment : BaseFragment(), View.OnClickListener,
- View.OnLongClickListener, ChipsView.OnChipClickListener {
+class DetailsFragment :
+ BaseFragment(),
+ View.OnClickListener,
+ View.OnLongClickListener,
+ ChipsView.OnChipClickListener {
private val viewModel by sharedViewModel()
private val coil by inject(mode = LazyThreadSafetyMode.NONE)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ }
+
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -64,6 +70,11 @@ class DetailsFragment : BaseFragment(), View.OnClickList
viewModel.readingHistory.observe(viewLifecycleOwner, ::onHistoryChanged)
}
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+ inflater.inflate(R.menu.opt_details_info, menu)
+ }
+
private fun onMangaUpdated(manga: Manga) {
with(binding) {
// Main
@@ -276,4 +287,4 @@ class DetailsFragment : BaseFragment(), View.OnClickList
.lifecycle(viewLifecycleOwner)
.enqueueWith(coil)
}
-}
+}
\ No newline at end of file
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 e3101c734..074eefeb1 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
@@ -40,7 +40,7 @@ class DetailsViewModel(
) : BaseViewModel() {
private var loadingJob: Job
- private val mangaData = MutableStateFlow(intent.manga)
+ private val mangaData = MutableStateFlow(intent.manga)
private val selectedBranch = MutableStateFlow(null)
private val history = mangaData.mapNotNull { it?.id }
@@ -62,6 +62,7 @@ class DetailsViewModel(
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0)
private val remoteManga = MutableStateFlow(null)
+ private val chaptersQuery = MutableStateFlow("")
private val chaptersReversed = settings.observe()
.filter { it == AppSettings.KEY_REVERSE_CHAPTERS }
@@ -93,21 +94,29 @@ class DetailsViewModel(
branches.indexOf(selected)
}.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
+ val hasChapters = mangaData.map {
+ !(it?.chapters.isNullOrEmpty())
+ }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
+
val chapters = combine(
- mangaData.map { it?.chapters.orEmpty() },
- remoteManga,
- history.map { it?.chapterId },
- newChapters,
- selectedBranch
- ) { chapters, sourceManga, currentId, newCount, branch ->
- val sourceChapters = sourceManga?.chapters
- if (sourceManga?.source != MangaSource.LOCAL && !sourceChapters.isNullOrEmpty()) {
- mapChaptersWithSource(chapters, sourceChapters, currentId, newCount, branch)
- } else {
- mapChapters(chapters, sourceChapters, currentId, newCount, branch)
- }
- }.combine(chaptersReversed) { list, reversed ->
- if (reversed) list.asReversed() else list
+ combine(
+ mangaData.map { it?.chapters.orEmpty() },
+ remoteManga,
+ history.map { it?.chapterId },
+ newChapters,
+ selectedBranch
+ ) { chapters, sourceManga, currentId, newCount, branch ->
+ val sourceChapters = sourceManga?.chapters
+ if (sourceManga?.source != MangaSource.LOCAL && !sourceChapters.isNullOrEmpty()) {
+ mapChaptersWithSource(chapters, sourceChapters, currentId, newCount, branch)
+ } else {
+ mapChapters(chapters, sourceChapters, currentId, newCount, branch)
+ }
+ },
+ chaptersReversed,
+ chaptersQuery,
+ ) { list, reversed, query ->
+ (if (reversed) list.asReversed() else list).filterSearch(query)
}.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
init {
@@ -142,6 +151,10 @@ class DetailsViewModel(
return remoteManga.value
}
+ fun performChapterSearch(query: String?) {
+ chaptersQuery.value = query?.trim().orEmpty()
+ }
+
private fun doLoad() = launchLoadingJob(Dispatchers.Default) {
var manga = mangaDataRepository.resolveIntent(intent)
?: throw MangaNotFoundException("Cannot find manga")
@@ -262,4 +275,13 @@ class DetailsViewModel(
}
return groups.maxByOrNull { it.value.size }?.key
}
-}
+
+ private fun List.filterSearch(query: String): List {
+ if (query.isEmpty() || this.isEmpty()) {
+ return this
+ }
+ return filter {
+ it.chapter.name.contains(query, ignoreCase = true)
+ }
+ }
+}
\ No newline at end of file
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 33a5cc62c..49151127e 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
@@ -5,8 +5,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
-import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.divider.MaterialDividerItemDecoration
import org.koin.android.ext.android.get
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
@@ -35,9 +33,6 @@ class ChaptersBottomSheet : BaseBottomSheet(), OnListItemC
if (!resources.getBoolean(R.bool.is_tablet)) {
binding.toolbar.navigationIcon = null
}
- binding.recyclerView.addItemDecoration(
- MaterialDividerItemDecoration(view.context, RecyclerView.VERTICAL)
- )
val chapters = arguments?.getParcelable(ARG_CHAPTERS)?.chapters
if (chapters.isNullOrEmpty()) {
dismissAllowingStateLoss()
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt
index 6ddd03a91..416b520d2 100644
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt
@@ -66,6 +66,7 @@ fun pageThumbnailAD(
onViewRecycled {
job?.cancel()
+ job = null
binding.imageViewThumb.setImageDrawable(null)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardDialogFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardDialogFragment.kt
index 74dc3ffbe..005469b96 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardDialogFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardDialogFragment.kt
@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.databinding.DialogOnboardBinding
import org.koitharu.kotatsu.settings.onboard.adapter.SourceLocalesAdapter
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
import org.koitharu.kotatsu.utils.ext.observeNotNull
+import org.koitharu.kotatsu.utils.ext.showAllowStateLoss
import org.koitharu.kotatsu.utils.ext.withArgs
class OnboardDialogFragment : AlertDialogFragment(),
@@ -77,7 +78,7 @@ class OnboardDialogFragment : AlertDialogFragment(),
fun showWelcome(fm: FragmentManager) {
OnboardDialogFragment().withArgs(1) {
putBoolean(ARG_WELCOME, true)
- }.show(fm, TAG)
+ }.showAllowStateLoss(fm, TAG)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt
index fd754cb83..cae599f8a 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt
@@ -2,7 +2,9 @@ package org.koitharu.kotatsu.utils.ext
import android.os.Bundle
import android.os.Parcelable
+import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
import androidx.lifecycle.coroutineScope
import java.io.Serializable
@@ -34,4 +36,10 @@ inline fun Fragment.serializableArgument(name: String
fun Fragment.stringArgument(name: String) = lazy(LazyThreadSafetyMode.NONE) {
arguments?.getString(name)
+}
+
+fun DialogFragment.showAllowStateLoss(manager: FragmentManager, tag: String?) {
+ if (!manager.isStateSaved) {
+ show(manager, tag)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/res/layout-w720dp/fragment_chapters.xml b/app/src/main/res/layout-w720dp/fragment_chapters.xml
index 386ce6fb0..7c4195c0d 100644
--- a/app/src/main/res/layout-w720dp/fragment_chapters.xml
+++ b/app/src/main/res/layout-w720dp/fragment_chapters.xml
@@ -25,4 +25,17 @@
android:visibility="gone"
tools:visibility="visible" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_chapters.xml b/app/src/main/res/layout/fragment_chapters.xml
index 33508fba8..d05821f2a 100644
--- a/app/src/main/res/layout/fragment_chapters.xml
+++ b/app/src/main/res/layout/fragment_chapters.xml
@@ -37,8 +37,25 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
- android:indeterminate="true"
android:layout_gravity="center"
+ android:indeterminate="true"
+ android:visibility="gone"
+ tools:visibility="visible" />
+
+
diff --git a/app/src/main/res/menu/opt_chapters.xml b/app/src/main/res/menu/opt_chapters.xml
index 11cd67383..f0bdc7b54 100644
--- a/app/src/main/res/menu/opt_chapters.xml
+++ b/app/src/main/res/menu/opt_chapters.xml
@@ -3,6 +3,14 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
+
+
-
-
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d9e9aca80..995191d21 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -267,4 +267,6 @@
Logged in as %s
18+
Various languages
+ Find chapter
+ No chapters in this manga
\ No newline at end of file