Added library sort by mean Tracker score (#10005)

This commit is contained in:
Caleb Morris 2023-11-04 12:31:59 -07:00 committed by GitHub
parent aca36f9625
commit 5d91b77c93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 49 additions and 15 deletions

View file

@ -144,6 +144,13 @@ private fun ColumnScope.SortPage(
val sortingMode = category.sort.type val sortingMode = category.sort.type
val sortDescending = !category.sort.isAscending val sortDescending = !category.sort.isAscending
val trackerSortOption =
if (screenModel.trackers.isEmpty()) {
emptyList()
} else {
listOf(R.string.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
}
listOf( listOf(
R.string.action_sort_alpha to LibrarySort.Type.Alphabetical, R.string.action_sort_alpha to LibrarySort.Type.Alphabetical,
R.string.action_sort_total to LibrarySort.Type.TotalChapters, R.string.action_sort_total to LibrarySort.Type.TotalChapters,
@ -153,7 +160,7 @@ private fun ColumnScope.SortPage(
R.string.action_sort_latest_chapter to LibrarySort.Type.LatestChapter, R.string.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
R.string.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate, R.string.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
R.string.action_sort_date_added to LibrarySort.Type.DateAdded, R.string.action_sort_date_added to LibrarySort.Type.DateAdded,
).map { (titleRes, mode) -> ).plus(trackerSortOption).map { (titleRes, mode) ->
SortItem( SortItem(
label = stringResource(titleRes), label = stringResource(titleRes),
sortDescending = sortDescending.takeIf { sortingMode == mode }, sortDescending = sortDescending.takeIf { sortingMode == mode },

View file

@ -30,6 +30,8 @@ class TrackerManager {
val trackers = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga, mangaUpdates, kavita, suwayomi) val trackers = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga, mangaUpdates, kavita, suwayomi)
fun loggedInTrackers() = trackers.filter { it.isLoggedIn }
fun get(id: Long) = trackers.find { it.id == id } fun get(id: Long) = trackers.find { it.id == id }
fun hasLoggedIn() = trackers.any { it.isLoggedIn } fun hasLoggedIn() = trackers.any { it.isLoggedIn }

View file

@ -61,6 +61,7 @@ import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.model.applyFilter import tachiyomi.domain.manga.model.applyFilter
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracksPerManga import tachiyomi.domain.track.interactor.GetTracksPerManga
import tachiyomi.domain.track.model.Track
import tachiyomi.source.local.isLocal import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -104,7 +105,7 @@ class LibraryScreenModel(
) { searchQuery, library, tracks, loggedInTrackers, _ -> ) { searchQuery, library, tracks, loggedInTrackers, _ ->
library library
.applyFilters(tracks, loggedInTrackers) .applyFilters(tracks, loggedInTrackers)
.applySort() .applySort(tracks)
.mapValues { (_, value) -> .mapValues { (_, value) ->
if (searchQuery != null) { if (searchQuery != null) {
// Filter query // Filter query
@ -168,7 +169,7 @@ class LibraryScreenModel(
* Applies library filters to the given map of manga. * Applies library filters to the given map of manga.
*/ */
private suspend fun LibraryMap.applyFilters( private suspend fun LibraryMap.applyFilters(
trackMap: Map<Long, List<Long>>, trackMap: Map<Long, List<Track>>,
loggedInTrackers: Map<Long, TriState>, loggedInTrackers: Map<Long, TriState>,
): LibraryMap { ): LibraryMap {
val prefs = getLibraryItemPreferencesFlow().first() val prefs = getLibraryItemPreferencesFlow().first()
@ -213,7 +214,9 @@ class LibraryScreenModel(
val filterFnTracking: (LibraryItem) -> Boolean = tracking@{ item -> val filterFnTracking: (LibraryItem) -> Boolean = tracking@{ item ->
if (isNotLoggedInAnyTrack || trackFiltersIsIgnored) return@tracking true if (isNotLoggedInAnyTrack || trackFiltersIsIgnored) return@tracking true
val mangaTracks = trackMap[item.libraryManga.id].orEmpty() val mangaTracks = trackMap
.mapValues { entry -> entry.value.map { it.syncId } }[item.libraryManga.id]
.orEmpty()
val isExcluded = excludedTracks.isNotEmpty() && mangaTracks.fastAny { it in excludedTracks } val isExcluded = excludedTracks.isNotEmpty() && mangaTracks.fastAny { it in excludedTracks }
val isIncluded = includedTracks.isEmpty() || mangaTracks.fastAny { it in includedTracks } val isIncluded = includedTracks.isEmpty() || mangaTracks.fastAny { it in includedTracks }
@ -236,7 +239,10 @@ class LibraryScreenModel(
/** /**
* Applies library sorting to the given map of manga. * Applies library sorting to the given map of manga.
*/ */
private fun LibraryMap.applySort(): LibraryMap { private fun LibraryMap.applySort(
// Map<MangaId, List<Track>>
trackMap: Map<Long, List<Track>>,
): LibraryMap {
val locale = Locale.getDefault() val locale = Locale.getDefault()
val collator = Collator.getInstance(locale).apply { val collator = Collator.getInstance(locale).apply {
strength = Collator.PRIMARY strength = Collator.PRIMARY
@ -245,6 +251,20 @@ class LibraryScreenModel(
collator.compare(i1.libraryManga.manga.title.lowercase(locale), i2.libraryManga.manga.title.lowercase(locale)) collator.compare(i1.libraryManga.manga.title.lowercase(locale), i2.libraryManga.manga.title.lowercase(locale))
} }
val defaultTrackerScoreSortValue = -1.0
val trackerScores by lazy {
val trackerMap = trackerManager.loggedInTrackers().associateBy { e -> e.id }
trackMap.mapValues { entry ->
when {
entry.value.isEmpty() -> null
else ->
entry.value
.mapNotNull { trackerMap[it.syncId]?.get10PointScore(it) }
.average()
}
}
}
val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 -> val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
val sort = keys.find { it.id == i1.libraryManga.category }!!.sort val sort = keys.find { it.id == i1.libraryManga.category }!!.sort
when (sort.type) { when (sort.type) {
@ -276,6 +296,11 @@ class LibraryScreenModel(
LibrarySort.Type.DateAdded -> { LibrarySort.Type.DateAdded -> {
i1.libraryManga.manga.dateAdded.compareTo(i2.libraryManga.manga.dateAdded) i1.libraryManga.manga.dateAdded.compareTo(i2.libraryManga.manga.dateAdded)
} }
LibrarySort.Type.TrackerMean -> {
val item1Score = trackerScores[i1.libraryManga.id] ?: defaultTrackerScoreSortValue
val item2Score = trackerScores[i2.libraryManga.id] ?: defaultTrackerScoreSortValue
item1Score.compareTo(item2Score)
}
} }
} }
@ -366,7 +391,7 @@ class LibraryScreenModel(
* @return map of track id with the filter value * @return map of track id with the filter value
*/ */
private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> { private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
val loggedInTrackers = trackerManager.trackers.filter { it.isLoggedIn } val loggedInTrackers = trackerManager.loggedInTrackers()
return if (loggedInTrackers.isNotEmpty()) { return if (loggedInTrackers.isNotEmpty()) {
val prefFlows = loggedInTrackers val prefFlows = loggedInTrackers
.map { libraryPreferences.filterTracking(it.id.toInt()).changes() } .map { libraryPreferences.filterTracking(it.id.toInt()).changes() }

View file

@ -30,6 +30,7 @@ data class LibrarySort(
data object LatestChapter : Type(0b00010100) data object LatestChapter : Type(0b00010100)
data object ChapterFetchDate : Type(0b00011000) data object ChapterFetchDate : Type(0b00011000)
data object DateAdded : Type(0b00011100) data object DateAdded : Type(0b00011100)
data object TrackerMean : Type(0b000100000)
companion object { companion object {
fun valueOf(flag: Long): Type { fun valueOf(flag: Long): Type {
@ -75,6 +76,7 @@ data class LibrarySort(
Type.LatestChapter, Type.LatestChapter,
Type.ChapterFetchDate, Type.ChapterFetchDate,
Type.DateAdded, Type.DateAdded,
Type.TrackerMean,
) )
} }
val directions by lazy { setOf(Direction.Ascending, Direction.Descending) } val directions by lazy { setOf(Direction.Ascending, Direction.Descending) }
@ -101,6 +103,7 @@ data class LibrarySort(
"LATEST_CHAPTER" -> Type.LatestChapter "LATEST_CHAPTER" -> Type.LatestChapter
"CHAPTER_FETCH_DATE" -> Type.ChapterFetchDate "CHAPTER_FETCH_DATE" -> Type.ChapterFetchDate
"DATE_ADDED" -> Type.DateAdded "DATE_ADDED" -> Type.DateAdded
"TRACKER_MEAN" -> Type.TrackerMean
else -> Type.Alphabetical else -> Type.Alphabetical
} }
val ascending = if (values[1] == "ASCENDING") Direction.Ascending else Direction.Descending val ascending = if (values[1] == "ASCENDING") Direction.Ascending else Direction.Descending
@ -121,6 +124,7 @@ data class LibrarySort(
Type.LatestChapter -> "LATEST_CHAPTER" Type.LatestChapter -> "LATEST_CHAPTER"
Type.ChapterFetchDate -> "CHAPTER_FETCH_DATE" Type.ChapterFetchDate -> "CHAPTER_FETCH_DATE"
Type.DateAdded -> "DATE_ADDED" Type.DateAdded -> "DATE_ADDED"
Type.TrackerMean -> "TRACKER_MEAN"
} }
val direction = if (direction == Direction.Ascending) "ASCENDING" else "DESCENDING" val direction = if (direction == Direction.Ascending) "ASCENDING" else "DESCENDING"
return "$type,$direction" return "$type,$direction"

View file

@ -2,19 +2,14 @@ package tachiyomi.domain.track.interactor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import tachiyomi.domain.track.model.Track
import tachiyomi.domain.track.repository.TrackRepository import tachiyomi.domain.track.repository.TrackRepository
class GetTracksPerManga( class GetTracksPerManga(
private val trackRepository: TrackRepository, private val trackRepository: TrackRepository,
) { ) {
fun subscribe(): Flow<Map<Long, List<Long>>> { fun subscribe(): Flow<Map<Long, List<Track>>> {
return trackRepository.getTracksAsFlow().map { tracks -> return trackRepository.getTracksAsFlow().map { tracks -> tracks.groupBy { it.mangaId } }
tracks
.groupBy { it.mangaId }
.mapValues { entry ->
entry.value.map { it.syncId }
}
}
} }
} }

View file

@ -12,7 +12,7 @@ class LibraryFlagsTest {
@Test @Test
fun `Check the amount of flags`() { fun `Check the amount of flags`() {
LibraryDisplayMode.values.size shouldBe 4 LibraryDisplayMode.values.size shouldBe 4
LibrarySort.types.size shouldBe 8 LibrarySort.types.size shouldBe 9
LibrarySort.directions.size shouldBe 2 LibrarySort.directions.size shouldBe 2
} }

View file

@ -66,6 +66,7 @@
<string name="action_sort_latest_chapter">Latest chapter</string> <string name="action_sort_latest_chapter">Latest chapter</string>
<string name="action_sort_chapter_fetch_date">Chapter fetch date</string> <string name="action_sort_chapter_fetch_date">Chapter fetch date</string>
<string name="action_sort_date_added">Date added</string> <string name="action_sort_date_added">Date added</string>
<string name="action_sort_tracker_score">Tracker score</string>
<string name="action_search">Search</string> <string name="action_search">Search</string>
<string name="action_search_hint">Search…</string> <string name="action_search_hint">Search…</string>
<string name="action_search_settings">Search settings</string> <string name="action_search_settings">Search settings</string>