diff --git a/app/build.gradle b/app/build.gradle
index e8093e261..0daab315f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -83,7 +83,7 @@ afterEvaluate {
}
dependencies {
//noinspection GradleDependency
- implementation('com.github.KotatsuApp:kotatsu-parsers:f2354957e6') {
+ implementation('com.github.KotatsuApp:kotatsu-parsers:f410df40f1') {
exclude group: 'org.json', module: 'json'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c0f146385..31d3ffc16 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -68,7 +68,7 @@
+ android:value="org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity" />
-
(),
override fun onItemClick(item: MangaAlternativeModel, view: View) {
when (view.id) {
- R.id.chip_source -> startActivity(SearchActivity.newIntent(this, item.manga.source, viewModel.manga.title))
+ R.id.chip_source -> startActivity(
+ MangaListActivity.newIntent(
+ this,
+ item.manga.source,
+ MangaListFilter(query = viewModel.manga.title),
+ ),
+ )
+
R.id.button_migrate -> confirmMigration(item.manga)
else -> startActivity(DetailsActivity.newIntent(this, item.manga))
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt
index aa5bd3330..78c4bd3da 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt
@@ -1,10 +1,13 @@
package org.koitharu.kotatsu.core.model
import android.net.Uri
+import android.text.SpannableStringBuilder
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.collection.MutableObjectIntMap
import androidx.core.os.LocaleListCompat
+import androidx.core.text.buildSpannedString
+import androidx.core.text.strikeThrough
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.iterator
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
@@ -12,6 +15,7 @@ import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.Demographic
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
+import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.formatSimple
import org.koitharu.kotatsu.parsers.util.mapToSet
@@ -152,3 +156,26 @@ fun Manga.chaptersCount(): Int {
}
return max
}
+
+fun MangaListFilter.getSummary() = buildSpannedString {
+ if (!query.isNullOrEmpty()) {
+ append(query)
+ if (tags.isNotEmpty() || tagsExclude.isNotEmpty()) {
+ append(' ')
+ append('(')
+ appendTagsSummary(this@getSummary)
+ append(')')
+ }
+ } else {
+ appendTagsSummary(this@getSummary)
+ }
+}
+
+private fun SpannableStringBuilder.appendTagsSummary(filter: MangaListFilter) {
+ filter.tags.joinTo(this) { it.title }
+ if (filter.tagsExclude.isNotEmpty()) {
+ strikeThrough {
+ filter.tagsExclude.joinTo(this) { it.title }
+ }
+ }
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaListFilter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaListFilter.kt
new file mode 100644
index 000000000..6b0eb85ed
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaListFilter.kt
@@ -0,0 +1,53 @@
+package org.koitharu.kotatsu.core.model.parcelable
+
+import android.os.Parcel
+import android.os.Parcelable
+import kotlinx.parcelize.Parceler
+import kotlinx.parcelize.Parcelize
+import kotlinx.parcelize.TypeParceler
+import org.koitharu.kotatsu.core.util.ext.readEnumSet
+import org.koitharu.kotatsu.core.util.ext.readParcelableCompat
+import org.koitharu.kotatsu.core.util.ext.readSerializableCompat
+import org.koitharu.kotatsu.core.util.ext.writeEnumSet
+import org.koitharu.kotatsu.parsers.model.ContentRating
+import org.koitharu.kotatsu.parsers.model.ContentType
+import org.koitharu.kotatsu.parsers.model.Demographic
+import org.koitharu.kotatsu.parsers.model.MangaListFilter
+import org.koitharu.kotatsu.parsers.model.MangaState
+
+object MangaListFilterParceler : Parceler {
+
+ override fun MangaListFilter.write(parcel: Parcel, flags: Int) {
+ parcel.writeString(query)
+ parcel.writeParcelable(ParcelableMangaTags(tags), 0)
+ parcel.writeParcelable(ParcelableMangaTags(tagsExclude), 0)
+ parcel.writeSerializable(locale)
+ parcel.writeSerializable(originalLocale)
+ parcel.writeEnumSet(states)
+ parcel.writeEnumSet(contentRating)
+ parcel.writeEnumSet(types)
+ parcel.writeEnumSet(demographics)
+ parcel.writeInt(year)
+ parcel.writeInt(yearFrom)
+ parcel.writeInt(yearTo)
+ }
+
+ override fun create(parcel: Parcel) = MangaListFilter(
+ query = parcel.readString(),
+ tags = parcel.readParcelableCompat()?.tags.orEmpty(),
+ tagsExclude = parcel.readParcelableCompat()?.tags.orEmpty(),
+ locale = parcel.readSerializableCompat(),
+ originalLocale = parcel.readSerializableCompat(),
+ states = parcel.readEnumSet().orEmpty(),
+ contentRating = parcel.readEnumSet().orEmpty(),
+ types = parcel.readEnumSet().orEmpty(),
+ demographics = parcel.readEnumSet().orEmpty(),
+ year = parcel.readInt(),
+ yearFrom = parcel.readInt(),
+ yearTo = parcel.readInt(),
+ )
+}
+
+@Parcelize
+@TypeParceler
+data class ParcelableMangaListFilter(val filter: MangaListFilter) : Parcelable
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/os/AppShortcutManager.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/os/AppShortcutManager.kt
index 71aad8f3f..414a8a24f 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/os/AppShortcutManager.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/os/AppShortcutManager.kt
@@ -180,7 +180,7 @@ class AppShortcutManager @Inject constructor(
.setLongLabel(title)
.setIcon(icon)
.setLongLived(true)
- .setIntent(MangaListActivity.newIntent(context, source))
+ .setIntent(MangaListActivity.newIntent(context, source, null))
.build()
}
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ChipsView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ChipsView.kt
index 873746bce..bb0f7972d 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ChipsView.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ChipsView.kt
@@ -36,11 +36,6 @@ class ChipsView @JvmOverloads constructor(
children.forEach { it.isClickable = isChipClickable }
}
var onChipCloseClickListener: OnChipCloseClickListener? = null
- set(value) {
- field = value
- val isCloseIconVisible = value != null
- children.forEach { (it as? Chip)?.isCloseIconVisible = isCloseIconVisible }
- }
init {
val ta = context.obtainStyledAttributes(attrs, R.styleable.ChipsView, defStyleAttr, 0)
@@ -98,6 +93,7 @@ class ChipsView @JvmOverloads constructor(
@ColorRes val tint: Int = 0,
val isChecked: Boolean = false,
val isDropdown: Boolean = false,
+ val isCloseable: Boolean = false,
val data: Any? = null,
)
@@ -139,7 +135,7 @@ class ChipsView @JvmOverloads constructor(
isChipIconVisible = true
}
isCheckedIconVisible = model.isChecked
- isCloseIconVisible = if (onChipCloseClickListener != null || model.isDropdown) {
+ isCloseIconVisible = if (model.isCloseable || model.isDropdown) {
setCloseIconResource(
if (model.isDropdown) R.drawable.ic_expand_more else materialR.drawable.ic_m3_chip_close,
)
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt
index 8933ae4db..34f3f440e 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt
@@ -12,6 +12,7 @@ import androidx.core.os.BundleCompat
import androidx.core.os.ParcelCompat
import androidx.lifecycle.SavedStateHandle
import java.io.Serializable
+import java.util.EnumSet
// https://issuetracker.google.com/issues/240585930
@@ -53,6 +54,31 @@ inline fun Bundle.requireSerializable(key: String): T
}
}
+fun > Parcel.writeEnumSet(set: Set?) {
+ if (set == null) {
+ writeValue(null)
+ } else {
+ val array = IntArray(set.size)
+ set.forEachIndexed { i, e -> array[i] = e.ordinal }
+ writeIntArray(array)
+ }
+}
+
+inline fun > Parcel.readEnumSet(): Set? = readEnumSet(E::class.java)
+
+fun > Parcel.readEnumSet(cls: Class): Set? {
+ val array = createIntArray() ?: return null
+ if (array.isEmpty()) {
+ return emptySet()
+ }
+ val enumValues = cls.enumConstants ?: return null
+ val set = EnumSet.noneOf(cls)
+ array.forEach { e ->
+ set.add(enumValues[e])
+ }
+ return set
+}
+
fun SavedStateHandle.require(key: String): T {
return checkNotNull(get(key)) {
"Value $key not found in SavedStateHandle or has a wrong type"
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt
index edcb7845a..109d605a4 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt
@@ -25,6 +25,12 @@ fun Collection.asArrayList(): ArrayList = if (this is ArrayList<*>) {
ArrayList(this)
}
+fun > Set.asEnumSet(cls: Class): EnumSet = if (this is EnumSet<*>) {
+ this as EnumSet
+} else {
+ EnumSet.noneOf(cls).apply { addAll(this@asEnumSet) }
+}
+
fun Map.findKeyByValue(value: V): K? {
for ((k, v) in entries) {
if (v == value) {
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt
index 0d689804f..6a3dfab94 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt
@@ -98,13 +98,13 @@ import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver
import org.koitharu.kotatsu.local.ui.info.LocalInfoDialog
import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.ellipsize
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.MangaListActivity
-import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet
import javax.inject.Inject
import com.google.android.material.R as materialR
@@ -213,10 +213,10 @@ class DetailsActivity :
R.id.chip_author -> {
val manga = viewModel.manga.value ?: return
startActivity(
- SearchActivity.newIntent(
+ MangaListActivity.newIntent(
context = v.context,
source = manga.source,
- query = manga.author ?: return,
+ filter = MangaListFilter(query = manga.author),
),
)
}
@@ -227,6 +227,7 @@ class DetailsActivity :
MangaListActivity.newIntent(
context = v.context,
source = manga.source,
+ filter = null,
),
)
}
@@ -286,7 +287,8 @@ class DetailsActivity :
override fun onChipClick(chip: Chip, data: Any?) {
val tag = data as? MangaTag ?: return
- startActivity(MangaListActivity.newIntent(this, setOf(tag)))
+ // TODO dialog
+ startActivity(MangaListActivity.newIntent(this, tag.source, MangaListFilter(tags = setOf(tag))))
}
override fun onLongClick(v: View): Boolean = when (v.id) {
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadNotificationFactory.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadNotificationFactory.kt
index 05cdbd931..03f23e5cf 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadNotificationFactory.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadNotificationFactory.kt
@@ -265,7 +265,7 @@ class DownloadNotificationFactory @AssistedInject constructor(
if (manga != null) {
DetailsActivity.newIntent(context, manga)
} else {
- MangaListActivity.newIntent(context, LocalMangaSource)
+ MangaListActivity.newIntent(context, LocalMangaSource, null)
},
PendingIntent.FLAG_CANCEL_CURRENT,
false,
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt
index 743253b59..2f4ea0c24 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt
@@ -126,7 +126,7 @@ class ExploreFragment :
override fun onClick(v: View) {
val intent = when (v.id) {
- R.id.button_local -> MangaListActivity.newIntent(v.context, LocalMangaSource)
+ R.id.button_local -> MangaListActivity.newIntent(v.context, LocalMangaSource, null)
R.id.button_bookmarks -> AllBookmarksActivity.newIntent(v.context)
R.id.button_more -> SuggestionsActivity.newIntent(v.context)
R.id.button_downloads -> Intent(v.context, DownloadsActivity::class.java)
@@ -144,7 +144,7 @@ class ExploreFragment :
if (sourceSelectionController?.onItemClick(item.id) == true) {
return
}
- val intent = MangaListActivity.newIntent(view.context, item.source)
+ val intent = MangaListActivity.newIntent(view.context, item.source, null)
startActivity(intent)
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt
index 059546880..0bdbae1f4 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt
@@ -59,8 +59,8 @@ class FilterCoordinator @Inject constructor(
private val currentSortOrder = MutableStateFlow(repository.defaultSortOrder)
private val availableSortOrders = repository.sortOrders
- private val capabilities = repository.filterCapabilities
private val filterOptions = SuspendLazy { repository.getFilterOptions() }
+ val capabilities = repository.filterCapabilities
val mangaSource: MangaSource
get() = repository.source
@@ -69,7 +69,7 @@ class FilterCoordinator @Inject constructor(
get() = !currentListFilter.value.isEmpty()
val query: StateFlow = currentListFilter.map { it.query }
- .stateIn(coroutineScope, SharingStarted.Lazily, null)
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
val sortOrder: StateFlow> = currentSortOrder.map { selected ->
FilterProperty(
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderFragment.kt
index e34b872ad..57662e12c 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderFragment.kt
@@ -22,7 +22,8 @@ import javax.inject.Inject
import com.google.android.material.R as materialR
@AndroidEntryPoint
-class FilterHeaderFragment : BaseFragment(), ChipsView.OnChipClickListener {
+class FilterHeaderFragment : BaseFragment(), ChipsView.OnChipClickListener,
+ ChipsView.OnChipCloseClickListener {
@Inject
lateinit var filterHeaderProducer: FilterHeaderProducer
@@ -37,6 +38,7 @@ class FilterHeaderFragment : BaseFragment(), ChipsV
override fun onViewBindingCreated(binding: FragmentFilterHeaderBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
binding.chipsTags.onChipClickListener = this
+ binding.chipsTags.onChipCloseClickListener = this
filterHeaderProducer.observeHeader(filter)
.flowOn(Dispatchers.Default)
.observe(viewLifecycleOwner, ::onDataChanged)
@@ -45,11 +47,16 @@ class FilterHeaderFragment : BaseFragment(), ChipsV
override fun onWindowInsetsChanged(insets: Insets) = Unit
override fun onChipClick(chip: Chip, data: Any?) {
- val tag = data as? MangaTag
- if (tag == null) {
- TagsCatalogSheet.show(parentFragmentManager, isExcludeTag = false)
- } else {
- filter.toggleTag(tag, !chip.isChecked)
+ when (data) {
+ is MangaTag -> filter.toggleTag(data, !chip.isChecked)
+ is String -> Unit
+ null -> TagsCatalogSheet.show(parentFragmentManager, isExcludeTag = false)
+ }
+ }
+
+ override fun onChipCloseClick(chip: Chip, data: Any?) {
+ when (data) {
+ is String -> filter.setQuery(null)
}
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderProducer.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderProducer.kt
index 02ea5169f..f43811745 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderProducer.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderProducer.kt
@@ -2,25 +2,25 @@ package org.koitharu.kotatsu.filter.ui
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.mapLatest
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel
import org.koitharu.kotatsu.filter.ui.model.FilterProperty
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
-import java.util.LinkedList
import javax.inject.Inject
+import com.google.android.material.R as materialR
class FilterHeaderProducer @Inject constructor(
private val searchRepository: MangaSearchRepository,
) {
fun observeHeader(filterCoordinator: FilterCoordinator): Flow {
- return filterCoordinator.tags.mapLatest {
+ return combine(filterCoordinator.tags, filterCoordinator.query) { tags, query ->
createChipsList(
source = filterCoordinator.mangaSource,
- property = it,
+ property = tags,
+ query = query,
limit = 8,
)
}.combine(filterCoordinator.observe()) { chipList, snapshot ->
@@ -35,6 +35,7 @@ class FilterHeaderProducer @Inject constructor(
private suspend fun createChipsList(
source: MangaSource,
property: FilterProperty,
+ query: String?,
limit: Int,
): List {
val selectedTags = property.selectedItems.toMutableSet()
@@ -49,7 +50,7 @@ class FilterHeaderProducer @Inject constructor(
if (tags.isEmpty() && selectedTags.isEmpty()) {
return emptyList()
}
- val result = LinkedList()
+ val result = ArrayDeque(tags.size + selectedTags.size + 1)
for (tag in tags) {
val model = ChipsView.ChipModel(
title = tag.title,
@@ -70,6 +71,16 @@ class FilterHeaderProducer @Inject constructor(
)
result.addFirst(model)
}
+ if (!query.isNullOrEmpty()) {
+ result.addFirst(
+ ChipsView.ChipModel(
+ title = query,
+ icon = materialR.drawable.abc_ic_search_api_material,
+ isCloseable = true,
+ data = query,
+ ),
+ )
+ }
return result
}
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt
index 3a9f02614..9184b0f8a 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt
@@ -60,6 +60,7 @@ import org.koitharu.kotatsu.list.ui.size.DynamicItemSizeResolver
import org.koitharu.kotatsu.main.ui.MainActivity
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.search.ui.MangaListActivity
@@ -164,7 +165,8 @@ abstract class MangaListFragment :
override fun onTagClick(manga: Manga, tag: MangaTag, view: View) {
if (selectionController?.onItemClick(manga.id) != true) {
- val intent = MangaListActivity.newIntent(context ?: return, setOf(tag))
+ // TODO dialog
+ val intent = MangaListActivity.newIntent(view.context, tag.source, MangaListFilter(tags = setOf(tag)))
startActivity(intent)
}
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/preview/PreviewFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/preview/PreviewFragment.kt
index 4512e2488..132f69382 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/preview/PreviewFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/preview/PreviewFragment.kt
@@ -31,10 +31,10 @@ import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.search.ui.MangaListActivity
-import org.koitharu.kotatsu.search.ui.SearchActivity
import javax.inject.Inject
@AndroidEntryPoint
@@ -83,10 +83,10 @@ class PreviewFragment : BaseFragment(), View.OnClickList
}
R.id.textView_author -> startActivity(
- SearchActivity.newIntent(
+ MangaListActivity.newIntent(
context = v.context,
source = manga.source,
- query = manga.author ?: return,
+ filter = MangaListFilter(query = manga.author),
),
)
@@ -107,7 +107,7 @@ class PreviewFragment : BaseFragment(), View.OnClickList
val tag = data as? MangaTag ?: return
val filter = (activity as? FilterCoordinator.Owner)?.filterCoordinator
if (filter == null) {
- startActivity(MangaListActivity.newIntent(requireContext(), setOf(tag)))
+ startActivity(MangaListActivity.newIntent(chip.context, tag.source, MangaListFilter(tags = setOf(tag))))
} else {
filter.toggleTag(tag, true)
closeSelf()
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt
index 44794afa4..41b7307b0 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt
@@ -26,6 +26,7 @@ import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.filter.ui.sheet.FilterSheetFragment
import org.koitharu.kotatsu.list.ui.MangaListFragment
+import org.koitharu.kotatsu.remotelist.ui.MangaSearchMenuProvider
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import org.koitharu.kotatsu.settings.storage.RequestStorageManagerPermissionContract
import org.koitharu.kotatsu.settings.storage.directories.MangaDirectoriesActivity
@@ -61,6 +62,7 @@ class LocalListFragment : MangaListFragment(), FilterCoordinator.Owner {
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
addMenuProvider(LocalListMenuProvider(binding.root.context, this::onEmptyActionClick))
+ addMenuProvider(MangaSearchMenuProvider(filterCoordinator, viewModel))
viewModel.onMangaRemoved.observeEvent(viewLifecycleOwner) { onItemRemoved() }
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt
index ac8993e9e..08217f7fa 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt
@@ -16,7 +16,6 @@ import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.explore.domain.ExploreRepository
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
-import org.koitharu.kotatsu.filter.ui.FilterHeaderProducer
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -41,7 +40,6 @@ class LocalListViewModel @Inject constructor(
exploreRepository: ExploreRepository,
@LocalStorageChanges private val localStorageChanges: SharedFlow,
private val localStorageManager: LocalStorageManager,
- filterHeaderProducer: FilterHeaderProducer,
sourcesRepository: MangaSourcesRepository,
) : RemoteListViewModel(
savedStateHandle,
@@ -109,8 +107,10 @@ class LocalListViewModel @Inject constructor(
}
}
- override fun createEmptyState(canResetFilter: Boolean): EmptyState {
- return EmptyState(
+ override fun createEmptyState(canResetFilter: Boolean): EmptyState = if (canResetFilter) {
+ super.createEmptyState(canResetFilter)
+ } else {
+ EmptyState(
icon = R.drawable.ic_empty_local,
textPrimary = R.string.text_local_holder_primary,
textSecondary = R.string.text_local_holder_secondary,
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt
index 0db277b0b..3360ab460 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt
@@ -60,6 +60,7 @@ import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.main.ui.welcome.WelcomeSheet
import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
@@ -265,7 +266,7 @@ class MainActivity : BaseActivity(), AppBarOwner, BottomNav
}
override fun onTagClick(tag: MangaTag) {
- startActivity(MangaListActivity.newIntent(this, setOf(tag)))
+ startActivity(MangaListActivity.newIntent(this, tag.source, MangaListFilter(tags = setOf(tag))))
}
override fun onQueryChanged(query: String) {
@@ -277,7 +278,7 @@ class MainActivity : BaseActivity(), AppBarOwner, BottomNav
}
override fun onSourceClick(source: MangaSource) {
- val intent = MangaListActivity.newIntent(this, source)
+ val intent = MangaListActivity.newIntent(this, source, null)
startActivity(intent)
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/MangaSearchMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/MangaSearchMenuProvider.kt
new file mode 100644
index 000000000..08dc09970
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/MangaSearchMenuProvider.kt
@@ -0,0 +1,69 @@
+package org.koitharu.kotatsu.remotelist.ui
+
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import androidx.appcompat.widget.SearchView
+import androidx.core.view.MenuProvider
+import androidx.core.view.inputmethod.EditorInfoCompat
+import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.ui.util.ReversibleAction
+import org.koitharu.kotatsu.core.util.ext.call
+import org.koitharu.kotatsu.filter.ui.FilterCoordinator
+import org.koitharu.kotatsu.list.ui.MangaListViewModel
+import org.koitharu.kotatsu.parsers.model.MangaListFilter
+
+class MangaSearchMenuProvider(
+ private val filter: FilterCoordinator,
+ private val viewModel: MangaListViewModel,
+) : MenuProvider, MenuItem.OnActionExpandListener, SearchView.OnQueryTextListener {
+
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ menuInflater.inflate(R.menu.opt_search, menu)
+ val menuItem = menu.findItem(R.id.action_search)
+ menuItem.setOnActionExpandListener(this)
+ val searchView = menuItem.actionView as SearchView
+ searchView.setOnQueryTextListener(this)
+ searchView.queryHint = menuItem.title
+ }
+
+ override fun onPrepareMenu(menu: Menu) {
+ super.onPrepareMenu(menu)
+ menu.findItem(R.id.action_search)?.isVisible = filter.capabilities.isSearchSupported
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean = false
+
+ override fun onQueryTextSubmit(query: String?): Boolean {
+ val snapshot = filter.snapshot()
+ if (!query.isNullOrEmpty() && !filter.capabilities.isSearchWithFiltersSupported && snapshot.listFilter.hasNonSearchOptions()) {
+ filter.set(MangaListFilter(query = query))
+ viewModel.onActionDone.call(
+ ReversibleAction(R.string.filter_search_warning) { filter.set(snapshot.listFilter) },
+ )
+ } else {
+ filter.setQuery(query)
+ }
+ return true
+ }
+
+ override fun onQueryTextChange(newText: String?): Boolean = false
+
+ override fun onMenuItemActionExpand(item: MenuItem): Boolean {
+ (item.actionView as? SearchView)?.run {
+ post { adjustSearchView() }
+ }
+ return true
+ }
+
+ override fun onMenuItemActionCollapse(item: MenuItem): Boolean = true
+
+ private fun SearchView.adjustSearchView() {
+ imeOptions = if (viewModel.isIncognitoModeEnabled) {
+ imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING
+ } else {
+ imeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv()
+ }
+ setQuery(filter.query.value, false)
+ }
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt
index b8d7ff663..5776e1444 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt
@@ -6,9 +6,7 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.appcompat.view.ActionMode
-import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider
-import androidx.core.view.inputmethod.EditorInfoCompat
import androidx.fragment.app.viewModels
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
@@ -28,9 +26,7 @@ import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.filter.ui.sheet.FilterSheetFragment
import org.koitharu.kotatsu.list.ui.MangaListFragment
-import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.settings.SettingsActivity
@AndroidEntryPoint
@@ -44,6 +40,7 @@ class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner {
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
addMenuProvider(RemoteListMenuProvider())
+ addMenuProvider(MangaSearchMenuProvider(filterCoordinator, viewModel))
viewModel.isRandomLoading.observe(viewLifecycleOwner, MenuInvalidator(requireActivity()))
viewModel.onOpenManga.observeEvent(viewLifecycleOwner) {
startActivity(DetailsActivity.newIntent(binding.root.context, it))
@@ -86,19 +83,10 @@ class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner {
.show()
}
- private inner class RemoteListMenuProvider :
- MenuProvider,
- SearchView.OnQueryTextListener,
- MenuItem.OnActionExpandListener {
+ private inner class RemoteListMenuProvider : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_list_remote, 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 onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
@@ -127,43 +115,9 @@ class RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner {
override fun onPrepareMenu(menu: Menu) {
super.onPrepareMenu(menu)
- menu.findItem(R.id.action_search)?.isVisible = viewModel.isSearchAvailable
menu.findItem(R.id.action_random)?.isEnabled = !viewModel.isRandomLoading.value
menu.findItem(R.id.action_filter_reset)?.isVisible = filterCoordinator.isFilterApplied
}
-
- override fun onQueryTextSubmit(query: String?): Boolean {
- if (query.isNullOrEmpty()) {
- return false
- }
- val intent = SearchActivity.newIntent(
- context = this@RemoteListFragment.context ?: return false,
- source = viewModel.source,
- query = query,
- )
- startActivity(intent)
- return true
- }
-
- override fun onQueryTextChange(newText: String?): Boolean = false
-
- override fun onMenuItemActionExpand(item: MenuItem): Boolean {
- (activity as? AppBarOwner)?.appBar?.setExpanded(false, true)
- (item.actionView as? SearchView)?.run {
- imeOptions = if (viewModel.isIncognitoModeEnabled) {
- imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING
- } else {
- imeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv()
- }
- }
- return true
- }
-
- override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
- val searchView = (item.actionView as? SearchView) ?: return false
- searchView.setQuery("", false)
- return true
- }
}
companion object {
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt
index 30906a4c5..3727776b1 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt
@@ -70,9 +70,6 @@ open class RemoteListViewModel @Inject constructor(
private var loadingJob: Job? = null
private var randomJob: Job? = null
- val isSearchAvailable: Boolean
- get() = repository.filterCapabilities.isSearchSupported
-
val browserUrl: String?
get() = (repository as? ParserMangaRepository)?.domain?.let { "https://$it" }
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt
index d7bd95c92..989828470 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt
@@ -23,10 +23,11 @@ import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.LocalMangaSource
import org.koitharu.kotatsu.core.model.MangaSource
+import org.koitharu.kotatsu.core.model.getSummary
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.model.isNsfw
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
-import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaTags
+import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaListFilter
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.model.titleRes
@@ -45,7 +46,7 @@ import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.parsers.model.MangaTag
+import org.koitharu.kotatsu.parsers.util.isNullOrEmpty
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import kotlin.math.absoluteValue
import com.google.android.material.R as materialR
@@ -63,28 +64,23 @@ class MangaListActivity :
"Cannot find FilterCoordinator.Owner fragment in ${supportFragmentManager.fragments}"
}.filterCoordinator
- private var source: MangaSource? = null
+ private lateinit var source: MangaSource
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityMangaListBinding.inflate(layoutInflater))
- val tags = intent.getParcelableExtraCompat(EXTRA_TAGS)?.tags
+ val filter = intent.getParcelableExtraCompat(EXTRA_FILTER)?.filter
+ source = MangaSource(intent.getStringExtra(EXTRA_SOURCE))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (viewBinding.containerFilterHeader != null) {
viewBinding.appbar.addOnOffsetChangedListener(this)
}
- source = intent.getStringExtra(EXTRA_SOURCE)?.let(::MangaSource) ?: tags?.firstOrNull()?.source
- val src = source
- if (src == null) {
- finishAfterTransition()
- } else {
- viewBinding.buttonOrder?.setOnClickListener(this)
- title = src.getTitle(this)
- initList(src, tags)
- }
+ viewBinding.buttonOrder?.setOnClickListener(this)
+ title = source.getTitle(this)
+ initList(source, filter)
}
- override fun isNsfwContent(): Flow = flowOf(source?.isNsfw() == true)
+ override fun isNsfwContent(): Flow = flowOf(source.isNsfw())
override fun onWindowInsetsChanged(insets: Insets) {
viewBinding.root.updatePadding(
@@ -119,7 +115,7 @@ class MangaListActivity :
fun hidePreview() = setSideFragment(FilterSheetFragment::class.java, null)
- private fun initList(source: MangaSource, tags: Set?) {
+ private fun initList(source: MangaSource, filter: MangaListFilter?) {
val fm = supportFragmentManager
val existingFragment = fm.findFragmentById(R.id.container)
if (existingFragment is FilterCoordinator.Owner) {
@@ -134,8 +130,8 @@ class MangaListActivity :
}
replace(R.id.container, fragment)
runOnCommit { initFilter(fragment) }
- if (!tags.isNullOrEmpty()) {
- runOnCommit(ApplyFilterRunnable(fragment, tags))
+ if (filter != null) {
+ runOnCommit(ApplyFilterRunnable(fragment, filter))
}
}
}
@@ -161,11 +157,12 @@ class MangaListActivity :
filterBadge.setMaxCharacterCount(0)
filter.observe().observe(this) { snapshot ->
chipSort.setTextAndVisible(snapshot.sortOrder.titleRes)
- filterBadge.counter = if (snapshot.listFilter.isEmpty()) 0 else 1
+ filterBadge.counter = if (snapshot.listFilter.hasNonSearchOptions()) 1 else 0
+ supportActionBar?.subtitle = snapshot.listFilter.query
}
} else {
filter.observe().map {
- it.listFilter.tags.joinToString { tag -> tag.title }
+ it.listFilter.getSummary()
}.flowOn(Dispatchers.Default)
.observe(this) {
supportActionBar?.subtitle = it
@@ -189,26 +186,28 @@ class MangaListActivity :
private class ApplyFilterRunnable(
private val filterOwner: FilterCoordinator.Owner,
- private val tags: Set,
+ private val filter: MangaListFilter,
) : Runnable {
override fun run() {
- filterOwner.filterCoordinator.set(MangaListFilter(tags = tags))
+ filterOwner.filterCoordinator.set(filter)
}
}
companion object {
- private const val EXTRA_TAGS = "tags"
+ private const val EXTRA_FILTER = "filter"
private const val EXTRA_SOURCE = "source"
- const val ACTION_MANGA_EXPLORE = "${BuildConfig.APPLICATION_ID}.action.EXPLORE_MANGA"
+ private const val ACTION_MANGA_EXPLORE = "${BuildConfig.APPLICATION_ID}.action.EXPLORE_MANGA"
- fun newIntent(context: Context, tags: Set) = Intent(context, MangaListActivity::class.java)
- .setAction(ACTION_MANGA_EXPLORE)
- .putExtra(EXTRA_TAGS, ParcelableMangaTags(tags))
-
- fun newIntent(context: Context, source: MangaSource) = Intent(context, MangaListActivity::class.java)
- .setAction(ACTION_MANGA_EXPLORE)
- .putExtra(EXTRA_SOURCE, source.name)
+ fun newIntent(context: Context, source: MangaSource, filter: MangaListFilter?): Intent =
+ Intent(context, MangaListActivity::class.java)
+ .setAction(ACTION_MANGA_EXPLORE)
+ .putExtra(EXTRA_SOURCE, source.name)
+ .apply {
+ if (!filter.isNullOrEmpty()) {
+ putExtra(EXTRA_FILTER, ParcelableMangaListFilter(filter))
+ }
+ }
}
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchActivity.kt
deleted file mode 100644
index ec63cf80e..000000000
--- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchActivity.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.koitharu.kotatsu.search.ui
-
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import androidx.activity.viewModels
-import androidx.appcompat.widget.SearchView
-import androidx.core.graphics.Insets
-import androidx.core.view.SoftwareKeyboardControllerCompat
-import androidx.core.view.inputmethod.EditorInfoCompat
-import androidx.core.view.updatePadding
-import androidx.fragment.app.commit
-import dagger.hilt.android.AndroidEntryPoint
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.core.model.MangaSource
-import org.koitharu.kotatsu.core.model.getTitle
-import org.koitharu.kotatsu.core.ui.BaseActivity
-import org.koitharu.kotatsu.core.util.ext.observe
-import org.koitharu.kotatsu.databinding.ActivitySearchBinding
-import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
-
-@AndroidEntryPoint
-class SearchActivity : BaseActivity(), SearchView.OnQueryTextListener {
-
- private val searchSuggestionViewModel by viewModels()
- private lateinit var source: MangaSource
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(ActivitySearchBinding.inflate(layoutInflater))
- source = MangaSource(intent.getStringExtra(EXTRA_SOURCE))
- val query = intent.getStringExtra(EXTRA_QUERY)
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged)
- with(viewBinding.searchView) {
- queryHint = getString(R.string.search_on_s, source.getTitle(context))
- setOnQueryTextListener(this@SearchActivity)
-
- if (query.isNullOrBlank()) {
- requestFocus()
- SoftwareKeyboardControllerCompat(this).show()
- } else {
- setQuery(query, true)
- }
- }
- }
-
- override fun onWindowInsetsChanged(insets: Insets) {
- viewBinding.toolbar.updatePadding(
- left = insets.left,
- right = insets.right,
- top = insets.top,
- )
- viewBinding.container.updatePadding(
- bottom = insets.bottom,
- )
- }
-
- override fun onQueryTextSubmit(query: String?): Boolean {
- val q = query?.trim()
- if (q.isNullOrEmpty()) {
- return false
- }
- title = query
- supportFragmentManager.commit {
- setReorderingAllowed(true)
- replace(R.id.container, SearchFragment.newInstance(source, q))
- }
- viewBinding.searchView.clearFocus()
- searchSuggestionViewModel.saveQuery(q)
- return true
- }
-
- override fun onQueryTextChange(newText: String?): Boolean = false
-
- private fun onIncognitoModeChanged(isIncognito: Boolean) {
- var options = viewBinding.searchView.imeOptions
- options = if (isIncognito) {
- options or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING
- } else {
- options and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv()
- }
- viewBinding.searchView.imeOptions = options
- }
-
- companion object {
-
- private const val EXTRA_SOURCE = "source"
- private const val EXTRA_QUERY = "query"
-
- fun newIntent(context: Context, source: MangaSource, query: String?) =
- Intent(context, SearchActivity::class.java)
- .putExtra(EXTRA_SOURCE, source.name)
- .putExtra(EXTRA_QUERY, query)
- }
-}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchFragment.kt
deleted file mode 100644
index 7662f9eb9..000000000
--- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchFragment.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.koitharu.kotatsu.search.ui
-
-import android.view.Menu
-import androidx.appcompat.view.ActionMode
-import androidx.fragment.app.viewModels
-import dagger.hilt.android.AndroidEntryPoint
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.core.ui.list.ListSelectionController
-import org.koitharu.kotatsu.core.util.ext.withArgs
-import org.koitharu.kotatsu.list.ui.MangaListFragment
-import org.koitharu.kotatsu.parsers.model.MangaSource
-
-@AndroidEntryPoint
-class SearchFragment : MangaListFragment() {
-
- override val viewModel by viewModels()
-
- override fun onScrolledToEnd() {
- viewModel.loadNextPage()
- }
-
- override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
- mode.menuInflater.inflate(R.menu.mode_remote, menu)
- return super.onCreateActionMode(controller, mode, menu)
- }
-
- companion object {
-
- const val ARG_QUERY = "query"
- const val ARG_SOURCE = "source"
-
- fun newInstance(source: MangaSource, query: String) = SearchFragment().withArgs(2) {
- putString(ARG_SOURCE, source.name)
- putString(ARG_QUERY, query)
- }
- }
-}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchViewModel.kt
deleted file mode 100644
index e1ceb0db2..000000000
--- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchViewModel.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-package org.koitharu.kotatsu.search.ui
-
-import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.plus
-import org.koitharu.kotatsu.R
-import org.koitharu.kotatsu.core.model.MangaSource
-import org.koitharu.kotatsu.core.model.distinctById
-import org.koitharu.kotatsu.core.parser.MangaRepository
-import org.koitharu.kotatsu.core.prefs.AppSettings
-import org.koitharu.kotatsu.core.util.ext.require
-import org.koitharu.kotatsu.core.util.ext.sizeOrZero
-import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
-import org.koitharu.kotatsu.list.domain.MangaListMapper
-import org.koitharu.kotatsu.list.ui.MangaListViewModel
-import org.koitharu.kotatsu.list.ui.model.EmptyState
-import org.koitharu.kotatsu.list.ui.model.ListModel
-import org.koitharu.kotatsu.list.ui.model.LoadingFooter
-import org.koitharu.kotatsu.list.ui.model.LoadingState
-import org.koitharu.kotatsu.list.ui.model.toErrorFooter
-import org.koitharu.kotatsu.list.ui.model.toErrorState
-import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.MangaListFilter
-import javax.inject.Inject
-
-@HiltViewModel
-class SearchViewModel @Inject constructor(
- savedStateHandle: SavedStateHandle,
- repositoryFactory: MangaRepository.Factory,
- settings: AppSettings,
- private val mangaListMapper: MangaListMapper,
- downloadScheduler: DownloadWorker.Scheduler,
-) : MangaListViewModel(settings, downloadScheduler) {
-
- private val query = savedStateHandle.require(SearchFragment.ARG_QUERY)
- private val repository = repositoryFactory.create(MangaSource(savedStateHandle[SearchFragment.ARG_SOURCE]))
- private val mangaList = MutableStateFlow?>(null)
- private val hasNextPage = MutableStateFlow(false)
- private val listError = MutableStateFlow(null)
- private var loadingJob: Job? = null
-
- override val content = combine(
- mangaList.map { it?.skipNsfwIfNeeded() },
- observeListModeWithTriggers(),
- listError,
- hasNextPage,
- ) { list, mode, error, hasNext ->
- when {
- list.isNullOrEmpty() && error != null -> listOf(error.toErrorState(canRetry = true))
- list == null -> listOf(LoadingState)
- list.isEmpty() -> listOf(
- EmptyState(
- icon = R.drawable.ic_empty_common,
- textPrimary = R.string.nothing_found,
- textSecondary = R.string.text_search_holder_secondary,
- actionStringRes = 0,
- ),
- )
-
- else -> {
- val result = ArrayList(list.size + 1)
- mangaListMapper.toListModelList(result, list, mode)
- when {
- error != null -> result += error.toErrorFooter()
- hasNext -> result += LoadingFooter()
- }
- result
- }
- }
- }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
-
- init {
- loadList(append = false)
- }
-
- override fun onRefresh() {
- loadList(append = false)
- }
-
- override fun onRetry() {
- loadList(append = !mangaList.value.isNullOrEmpty())
- }
-
- fun loadNextPage() {
- if (hasNextPage.value && listError.value == null) {
- loadList(append = true)
- }
- }
-
- private fun loadList(append: Boolean) {
- if (loadingJob?.isActive == true) {
- return
- }
- loadingJob = launchLoadingJob(Dispatchers.Default) {
- try {
- listError.value = null
- val list = repository.getList(
- offset = if (append) mangaList.value.sizeOrZero() else 0,
- order = null,
- filter = MangaListFilter(query = query),
- )
- val prevList = mangaList.value.orEmpty()
- if (!append) {
- mangaList.value = list.distinctById()
- } else if (list.isNotEmpty()) {
- mangaList.value = (prevList + list).distinctById()
- }
- hasNextPage.value = if (append) {
- prevList != mangaList.value
- } else {
- list.isNotEmpty()
- }
- } catch (e: CancellationException) {
- throw e
- } catch (e: Throwable) {
- listError.value = e
- }
- }
- }
-}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt
index eed362e67..a719c4f20 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt
@@ -34,10 +34,10 @@ import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.size.DynamicItemSizeResolver
import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.search.ui.MangaListActivity
-import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.search.ui.multi.adapter.MultiSearchAdapter
import javax.inject.Inject
@@ -63,7 +63,13 @@ class MultiSearchActivity :
title = viewModel.query
val itemCLickListener = OnListItemClickListener { item, view ->
- startActivity(SearchActivity.newIntent(view.context, item.source, viewModel.query))
+ startActivity(
+ MangaListActivity.newIntent(
+ view.context,
+ item.source,
+ MangaListFilter(query = viewModel.query),
+ ),
+ )
}
val sizeResolver = DynamicItemSizeResolver(resources, settings, adjustWidth = true)
val selectionDecoration = MangaSelectionDecoration(this)
@@ -125,7 +131,7 @@ class MultiSearchActivity :
override fun onTagClick(manga: Manga, tag: MangaTag, view: View) {
if (!selectionController.onItemClick(manga.id)) {
- val intent = MangaListActivity.newIntent(this, setOf(tag))
+ val intent = MangaListActivity.newIntent(this, manga.source, MangaListFilter(tags = setOf(tag)))
startActivity(intent)
}
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogActivity.kt
index 30005249b..a655cb410 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogActivity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogActivity.kt
@@ -89,7 +89,7 @@ class SourcesCatalogActivity : BaseActivity(),
}
override fun onItemClick(item: SourceCatalogItem.Source, view: View) {
- startActivity(MangaListActivity.newIntent(this, item.source))
+ startActivity(MangaListActivity.newIntent(this, item.source, null))
}
override fun onItemLongClick(item: SourceCatalogItem.Source, view: View): Boolean {
diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml
deleted file mode 100644
index 494f0935a..000000000
--- a/app/src/main/res/layout/activity_search.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/menu/opt_list_remote.xml b/app/src/main/res/menu/opt_list_remote.xml
index 768334fea..c1f6cdde1 100644
--- a/app/src/main/res/menu/opt_list_remote.xml
+++ b/app/src/main/res/menu/opt_list_remote.xml
@@ -4,16 +4,10 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
-
-
diff --git a/app/src/main/res/menu/opt_search.xml b/app/src/main/res/menu/opt_search.xml
new file mode 100644
index 000000000..49d226ff9
--- /dev/null
+++ b/app/src/main/res/menu/opt_search.xml
@@ -0,0 +1,14 @@
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9fef1ac61..6f78da58e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -726,4 +726,5 @@
Josei
Years
Any
+ This source does not support search with filters. Your filters have been cleared