Multiple authors support
This commit is contained in:
@@ -87,6 +87,10 @@ fun <T, R> Collection<T>.mapSortedByCount(isDescending: Boolean = true, mapper:
|
||||
return sorted.map { it.first }
|
||||
}
|
||||
|
||||
fun Collection<CharSequence?>.contains(element: CharSequence?, ignoreCase: Boolean): Boolean = any { x ->
|
||||
(x == null && element == null) || (x != null && element != null && x.contains(element, ignoreCase))
|
||||
}
|
||||
|
||||
fun Collection<CharSequence?>.indexOfContains(element: CharSequence?, ignoreCase: Boolean): Int = indexOfFirst { x ->
|
||||
(x == null && element == null) || (x != null && element != null && x.contains(element, ignoreCase))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import android.text.Spannable
|
||||
import android.text.TextPaint
|
||||
import android.text.style.ClickableSpan
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
|
||||
class AuthorSpan(private val listener: OnAuthorClickListener) : ClickableSpan() {
|
||||
|
||||
override fun onClick(widget: View) {
|
||||
val text = (widget as? TextView)?.text as? Spannable ?: return
|
||||
val start = text.getSpanStart(this)
|
||||
val end = text.getSpanEnd(this)
|
||||
val selected = text.substring(start, end).trim()
|
||||
if (selected.isNotEmpty()) {
|
||||
listener.onAuthorClick(selected)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateDrawState(ds: TextPaint) {
|
||||
ds.setColor(ds.linkColor)
|
||||
}
|
||||
|
||||
fun interface OnAuthorClickListener {
|
||||
|
||||
fun onAuthorClick(author: String)
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,15 @@ package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.SpannedString
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.inSpans
|
||||
import androidx.core.text.method.LinkMovementMethodCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isGone
|
||||
@@ -118,7 +121,7 @@ class DetailsActivity :
|
||||
View.OnClickListener,
|
||||
View.OnLayoutChangeListener, ViewTreeObserver.OnDrawListener,
|
||||
ChipsView.OnChipClickListener, OnListItemClickListener<Bookmark>,
|
||||
SwipeRefreshLayout.OnRefreshListener {
|
||||
SwipeRefreshLayout.OnRefreshListener, AuthorSpan.OnAuthorClickListener {
|
||||
|
||||
@Inject
|
||||
lateinit var shortcutManager: AppShortcutManager
|
||||
@@ -138,7 +141,6 @@ class DetailsActivity :
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||
viewBinding.chipFavorite.setOnClickListener(this)
|
||||
infoBinding.textViewLocal.setOnClickListener(this)
|
||||
infoBinding.textViewAuthor.setOnClickListener(this)
|
||||
infoBinding.textViewSource.setOnClickListener(this)
|
||||
viewBinding.imageViewCover.setOnClickListener(this)
|
||||
viewBinding.textViewTitle.setOnClickListener(this)
|
||||
@@ -148,6 +150,7 @@ class DetailsActivity :
|
||||
viewBinding.textViewDescription.addOnLayoutChangeListener(this)
|
||||
viewBinding.swipeRefreshLayout.setOnRefreshListener(this)
|
||||
viewBinding.textViewDescription.viewTreeObserver.addOnDrawListener(this)
|
||||
infoBinding.textViewAuthor.movementMethod = LinkMovementMethodCompat.getInstance()
|
||||
viewBinding.textViewDescription.movementMethod = LinkMovementMethodCompat.getInstance()
|
||||
viewBinding.chipsTags.onChipClickListener = this
|
||||
TitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView)
|
||||
@@ -199,29 +202,23 @@ class DetailsActivity :
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.textView_author -> {
|
||||
val manga = viewModel.manga.value
|
||||
val author = manga?.author ?: return
|
||||
router.showAuthorDialog(author, manga.source)
|
||||
}
|
||||
|
||||
R.id.textView_source -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val manga = viewModel.getMangaOrNull() ?: return
|
||||
router.openList(manga.source, null, null)
|
||||
}
|
||||
|
||||
R.id.textView_local -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val manga = viewModel.getMangaOrNull() ?: return
|
||||
router.showLocalInfoDialog(manga)
|
||||
}
|
||||
|
||||
R.id.chip_favorite -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val manga = viewModel.getMangaOrNull() ?: return
|
||||
router.showFavoriteDialog(manga)
|
||||
}
|
||||
|
||||
R.id.imageView_cover -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val manga = viewModel.getMangaOrNull() ?: return
|
||||
router.openImage(
|
||||
url = viewModel.coverUrl.value ?: return,
|
||||
source = manga.source,
|
||||
@@ -245,17 +242,17 @@ class DetailsActivity :
|
||||
}
|
||||
|
||||
R.id.button_scrobbling_more -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val manga = viewModel.getMangaOrNull() ?: return
|
||||
router.showScrobblingSelectorSheet(manga, null)
|
||||
}
|
||||
|
||||
R.id.button_related_more -> {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
val manga = viewModel.getMangaOrNull() ?: return
|
||||
router.openRelated(manga)
|
||||
}
|
||||
|
||||
R.id.textView_title -> {
|
||||
val title = viewModel.manga.value?.title?.nullIfEmpty() ?: return
|
||||
val title = viewModel.getMangaOrNull()?.title?.nullIfEmpty() ?: return
|
||||
buildAlertDialog(this) {
|
||||
setMessage(title)
|
||||
setNegativeButton(R.string.close, null)
|
||||
@@ -267,6 +264,10 @@ class DetailsActivity :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAuthorClick(author: String) {
|
||||
router.showAuthorDialog(author, viewModel.getMangaOrNull()?.source ?: return)
|
||||
}
|
||||
|
||||
override fun onChipClick(chip: Chip, data: Any?) {
|
||||
val tag = data as? MangaTag ?: return
|
||||
router.showTagDialog(tag)
|
||||
@@ -415,7 +416,7 @@ class DetailsActivity :
|
||||
TextDrawable.compound(infoBinding.textViewTranslation, it)
|
||||
}
|
||||
infoBinding.textViewTranslationLabel.isVisible = infoBinding.textViewTranslation.isVisible
|
||||
textViewAuthor.textAndVisible = manga.author
|
||||
textViewAuthor.textAndVisible = manga.getAuthorsString()
|
||||
textViewAuthorLabel.isVisible = textViewAuthor.isVisible
|
||||
if (manga.hasRating) {
|
||||
ratingBarRating.rating = manga.rating * ratingBarRating.numStars
|
||||
@@ -537,6 +538,24 @@ class DetailsActivity :
|
||||
return getString(R.string.chapters_time_pattern, this, timeFormatted)
|
||||
}
|
||||
|
||||
private fun Manga.getAuthorsString(): SpannedString? {
|
||||
if (authors.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
return buildSpannedString {
|
||||
authors.forEach { a ->
|
||||
if (a.isNotEmpty()) {
|
||||
if (isNotEmpty()) {
|
||||
append(", ")
|
||||
}
|
||||
inSpans(AuthorSpan(this@DetailsActivity)) {
|
||||
append(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.nullIfEmpty()
|
||||
}
|
||||
|
||||
private class PrefetchObserver(
|
||||
private val context: Context,
|
||||
) : FlowCollector<List<ChapterListItem>?> {
|
||||
|
||||
@@ -36,15 +36,15 @@ class RecoverMangaUseCase @Inject constructor(
|
||||
) = Manga(
|
||||
id = broken.id,
|
||||
title = current.title,
|
||||
altTitle = current.altTitle,
|
||||
altTitles = current.altTitles,
|
||||
url = current.url,
|
||||
publicUrl = current.publicUrl,
|
||||
rating = current.rating,
|
||||
isNsfw = current.isNsfw,
|
||||
contentRating = current.contentRating,
|
||||
coverUrl = current.coverUrl,
|
||||
tags = current.tags,
|
||||
state = current.state,
|
||||
author = current.author,
|
||||
authors = current.authors,
|
||||
largeCoverUrl = current.largeCoverUrl,
|
||||
description = current.description,
|
||||
chapters = current.chapters,
|
||||
|
||||
@@ -32,7 +32,7 @@ fun mangaListDetailedItemAD(
|
||||
|
||||
bind { payloads ->
|
||||
binding.textViewTitle.text = item.title
|
||||
binding.textViewAuthor.textAndVisible = item.manga.author
|
||||
binding.textViewAuthor.textAndVisible = item.manga.authors.joinToString(", ")
|
||||
binding.progressView.setProgress(
|
||||
value = item.progress,
|
||||
animate = ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads,
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.local.domain.model
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import org.koitharu.kotatsu.core.util.ext.contains
|
||||
import org.koitharu.kotatsu.core.util.ext.creationTime
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
@@ -26,8 +27,8 @@ data class LocalManga(
|
||||
|
||||
fun isMatchesQuery(query: String): Boolean {
|
||||
return manga.title.contains(query, ignoreCase = true) ||
|
||||
manga.altTitle?.contains(query, ignoreCase = true) == true ||
|
||||
manga.author?.contains(query, ignoreCase = true) == true
|
||||
manga.altTitles.contains(query, ignoreCase = true) ||
|
||||
manga.authors.contains(query, ignoreCase = true)
|
||||
}
|
||||
|
||||
fun containsTags(tags: Collection<String>): Boolean {
|
||||
|
||||
@@ -8,20 +8,6 @@ fun Manga.filterChapters(branch: String?): Manga {
|
||||
return withChapters(chapters = chapters?.filter { it.branch == branch })
|
||||
}
|
||||
|
||||
private fun Manga.withChapters(chapters: List<MangaChapter>?) = Manga(
|
||||
id = id,
|
||||
title = title,
|
||||
altTitle = altTitle,
|
||||
url = url,
|
||||
publicUrl = publicUrl,
|
||||
rating = rating,
|
||||
isNsfw = isNsfw,
|
||||
coverUrl = coverUrl,
|
||||
tags = tags,
|
||||
state = state,
|
||||
author = author,
|
||||
largeCoverUrl = largeCoverUrl,
|
||||
description = description,
|
||||
private fun Manga.withChapters(chapters: List<MangaChapter>?) = copy(
|
||||
chapters = chapters,
|
||||
source = source,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.koitharu.kotatsu.core.model.isNsfw
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.contains
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
@@ -83,7 +84,7 @@ class SearchV2Helper @AssistedInject constructor(
|
||||
}
|
||||
|
||||
SearchKind.AUTHOR -> retainAll { m ->
|
||||
m.author.isNullOrEmpty() || m.author.equals(query, ignoreCase = true)
|
||||
m.authors.isEmpty() || m.authors.contains(query, ignoreCase = true)
|
||||
}
|
||||
|
||||
SearchKind.SIMPLE, // no filtering expected
|
||||
@@ -99,7 +100,7 @@ class SearchV2Helper @AssistedInject constructor(
|
||||
}
|
||||
|
||||
SearchKind.AUTHOR -> sortByDescending { m ->
|
||||
m.author?.equals(query, ignoreCase = true) == true
|
||||
m.authors.contains(query, ignoreCase = true)
|
||||
}
|
||||
|
||||
SearchKind.TAG -> sortByDescending { m ->
|
||||
|
||||
@@ -64,11 +64,8 @@
|
||||
android:id="@+id/textView_author"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/custom_selectable_item_background"
|
||||
android:padding="4dp"
|
||||
android:singleLine="true"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textAppearance="?textAppearanceBodyMedium"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/textView_author_label"
|
||||
@@ -87,7 +84,7 @@
|
||||
android:text="@string/translation"
|
||||
android:textAppearance="?textAppearanceTitleSmall"
|
||||
app:layout_constraintStart_toStartOf="@id/card_details"
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_author_label" />
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_author" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_translation"
|
||||
|
||||
Reference in New Issue
Block a user