Added library sort by mean Tracker score (#10005)
This commit is contained in:
parent
aca36f9625
commit
5d91b77c93
7 changed files with 49 additions and 15 deletions
|
@ -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 },
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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() }
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Reference in a new issue