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 c175c1e83..6944e9f93 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 @@ -87,6 +87,10 @@ fun Collection.mapSortedByCount(isDescending: Boolean = true, mapper: return sorted.map { it.first } } +fun Collection.contains(element: CharSequence?, ignoreCase: Boolean): Boolean = any { x -> + (x == null && element == null) || (x != null && element != null && x.contains(element, ignoreCase)) +} + fun Collection.indexOfContains(element: CharSequence?, ignoreCase: Boolean): Int = indexOfFirst { x -> (x == null && element == null) || (x != null && element != null && x.contains(element, ignoreCase)) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/AuthorSpan.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/AuthorSpan.kt new file mode 100644 index 000000000..b917c3b4f --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/AuthorSpan.kt @@ -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) + } +} 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 1591f625d..1efb40479 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 @@ -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, - 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?> { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/domain/RecoverMangaUseCase.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/domain/RecoverMangaUseCase.kt index cb61e7f72..a5228f473 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/domain/RecoverMangaUseCase.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/domain/RecoverMangaUseCase.kt @@ -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, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt index 52e4e1303..78ecefd83 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt @@ -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, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/domain/model/LocalManga.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/domain/model/LocalManga.kt index ed0749933..b8afb8c4a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/domain/model/LocalManga.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/domain/model/LocalManga.kt @@ -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): Boolean { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/data/ModelMapping.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/data/ModelMapping.kt index 2cd9ffcb3..4aea7b6d9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/data/ModelMapping.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/data/ModelMapping.kt @@ -8,20 +8,6 @@ fun Manga.filterChapters(branch: String?): Manga { return withChapters(chapters = chapters?.filter { it.branch == branch }) } -private fun Manga.withChapters(chapters: List?) = 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?) = copy( chapters = chapters, - source = source, -) \ No newline at end of file +) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/domain/SearchV2Helper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/domain/SearchV2Helper.kt index b12dfc8eb..b7553d572 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/domain/SearchV2Helper.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/domain/SearchV2Helper.kt @@ -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 -> diff --git a/app/src/main/res/layout/layout_details_table.xml b/app/src/main/res/layout/layout_details_table.xml index 3840dd2c7..c3e25ba0b 100644 --- a/app/src/main/res/layout/layout_details_table.xml +++ b/app/src/main/res/layout/layout_details_table.xml @@ -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" />