mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-19 04:06:43 +02:00
fix slider + list bugs
This commit is contained in:
+39
@@ -90,6 +90,18 @@ class AstraLibraryDataModule : Module() {
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("getTrackPageBefore") Coroutine {
|
||||
sort: String,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
->
|
||||
try {
|
||||
repository().getTrackPageBefore(sort, cursor, limit)
|
||||
} catch (_: StaleRevisionException) {
|
||||
mapOf("error" to "STALE_REVISION")
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("getTrack") Coroutine { path: String ->
|
||||
repository().getTrack(path)
|
||||
}
|
||||
@@ -367,6 +379,19 @@ class AstraLibraryDataModule : Module() {
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("getAlbumPageBefore") Coroutine {
|
||||
sort: String,
|
||||
includeSingles: Boolean,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
->
|
||||
try {
|
||||
repository().getAlbumPageBefore(sort, includeSingles, cursor, limit)
|
||||
} catch (_: StaleRevisionException) {
|
||||
mapOf("error" to "STALE_REVISION")
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("getArtistPage") Coroutine {
|
||||
sort: String,
|
||||
groupingMode: String,
|
||||
@@ -381,6 +406,20 @@ class AstraLibraryDataModule : Module() {
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("getArtistPageBefore") Coroutine {
|
||||
sort: String,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
->
|
||||
try {
|
||||
repository().getArtistPageBefore(sort, groupingMode, includeCollaborations, cursor, limit)
|
||||
} catch (_: StaleRevisionException) {
|
||||
mapOf("error" to "STALE_REVISION")
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFunction("getAlbumDetail") Coroutine { albumKey: String, cursor: String?, limit: Int ->
|
||||
try {
|
||||
repository().getAlbumDetail(albumKey, cursor, limit)
|
||||
|
||||
+222
-72
@@ -596,39 +596,7 @@ class AstraLibraryRepository private constructor(
|
||||
limit = limit,
|
||||
)
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row ->
|
||||
when (sort) {
|
||||
"artist" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.artistSortKey,
|
||||
text2 = row.albumSortKey,
|
||||
text3 = "${row.titleSortKey}\u0000${row.path}",
|
||||
number1 = row.discSort.toLong(),
|
||||
number2 = row.trackSort.toLong(),
|
||||
// Artist cursor needs one extra string. Encode the path alongside the
|
||||
// title key with a NUL delimiter; SAF paths cannot contain NUL.
|
||||
)
|
||||
"recently_added" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.path,
|
||||
number1 = row.addedAt,
|
||||
)
|
||||
"duration" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.path,
|
||||
decimal1 = row.duration,
|
||||
)
|
||||
else -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.titleSortKey,
|
||||
text2 = row.path,
|
||||
)
|
||||
}.encode()
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row -> trackCursor(revision, sort, row).encode() }
|
||||
mapOf(
|
||||
"items" to rows.map(ActiveTrackView::toBridgeMap),
|
||||
"nextCursor" to next,
|
||||
@@ -638,6 +606,93 @@ class AstraLibraryRepository private constructor(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The page immediately *above* [cursorRaw] — the backward twin of [getTrackPage].
|
||||
* An A-Z rail jump drops the list into the middle of the catalog; this is what the
|
||||
* rows above it are fetched with when the user scrolls back up. Items come back
|
||||
* ascending, like every other page.
|
||||
*
|
||||
* `previousCursor` is non-null only on a full page: a short one means we walked off
|
||||
* the head of the catalog, so the caller knows to stop asking.
|
||||
*
|
||||
* Only the sorts the rail is offered for (`title`, `artist`) can be walked backwards;
|
||||
* anything else returns an empty page.
|
||||
*/
|
||||
suspend fun getTrackPageBefore(
|
||||
sort: String,
|
||||
cursorRaw: String?,
|
||||
requestedLimit: Int,
|
||||
): Map<String, Any?> = withCatalogRecovery { database ->
|
||||
initialize()
|
||||
val dao = database.catalogDao()
|
||||
val revision = dao.getRevision()
|
||||
val cursor = validateCursor(cursorRaw, revision, "tracks:$sort")
|
||||
val limit = requestedLimit.coerceIn(1, MAX_PAGE_SIZE)
|
||||
val descending = when {
|
||||
cursor == null -> emptyList()
|
||||
sort == "artist" -> dao.getArtistOrderPageBefore(
|
||||
beforeArtistKey = cursor.text1.orEmpty(),
|
||||
beforeAlbumKey = cursor.text2.orEmpty(),
|
||||
beforeDisc = cursor.number1?.toInt() ?: 0,
|
||||
beforeTrack = cursor.number2?.toInt() ?: 0,
|
||||
beforeTitleKey = cursorTitleKey(cursor),
|
||||
beforePath = cursorPath(cursor),
|
||||
limit = limit,
|
||||
)
|
||||
sort == "title" -> dao.getTitlePageBefore(
|
||||
beforeTitleKey = cursor.text1.orEmpty(),
|
||||
beforePath = cursor.text2.orEmpty(),
|
||||
limit = limit,
|
||||
)
|
||||
else -> emptyList()
|
||||
}
|
||||
// The DESC result's last row is the topmost one — the cursor for the page above this one.
|
||||
val previous = descending.takeIf { it.size == limit }
|
||||
?.lastOrNull()
|
||||
?.let { row -> trackCursor(revision, sort, row).encode() }
|
||||
mapOf(
|
||||
"items" to descending.reversed().map(ActiveTrackView::toBridgeMap),
|
||||
"nextCursor" to null,
|
||||
"previousCursor" to previous,
|
||||
"totalCount" to dao.countActiveTracks().toDouble(),
|
||||
"catalogRevision" to revision.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
/** Shared by the forward and backward track pages so the two can never disagree. */
|
||||
private fun trackCursor(revision: Long, sort: String, row: ActiveTrackView): TrackPageCursor =
|
||||
when (sort) {
|
||||
"artist" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.artistSortKey,
|
||||
text2 = row.albumSortKey,
|
||||
text3 = "${row.titleSortKey}\u0000${row.path}",
|
||||
number1 = row.discSort.toLong(),
|
||||
number2 = row.trackSort.toLong(),
|
||||
// Artist cursor needs one extra string. Encode the path alongside the
|
||||
// title key with a NUL delimiter; SAF paths cannot contain NUL.
|
||||
)
|
||||
"recently_added" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.path,
|
||||
number1 = row.addedAt,
|
||||
)
|
||||
"duration" -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.path,
|
||||
decimal1 = row.duration,
|
||||
)
|
||||
else -> TrackPageCursor(
|
||||
revision = revision,
|
||||
kind = "tracks:$sort",
|
||||
text1 = row.titleSortKey,
|
||||
text2 = row.path,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getTrack(path: String): Map<String, Any?>? =
|
||||
withCatalogRecovery { database -> database.catalogDao().getActiveTrack(path)?.toBridgeMap() }
|
||||
|
||||
@@ -1674,36 +1729,7 @@ class AstraLibraryRepository private constructor(
|
||||
limit,
|
||||
)
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row ->
|
||||
when (sort) {
|
||||
"artist" -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.artistSortKey,
|
||||
text2 = row.nameSortKey,
|
||||
text3 = row.identityKey,
|
||||
)
|
||||
"recently_added" -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.identityKey,
|
||||
number1 = row.latestAddedAt,
|
||||
)
|
||||
"year" -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.identityKey,
|
||||
number1 = (row.year ?: 0).toLong(),
|
||||
)
|
||||
else -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.identityKey,
|
||||
)
|
||||
}.encode()
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row -> albumCursor(revision, kind, sort, row).encode() }
|
||||
mapOf(
|
||||
"items" to rows.map(AlbumSummaryEntity::toBridgeMap),
|
||||
"nextCursor" to next,
|
||||
@@ -1713,6 +1739,85 @@ class AstraLibraryRepository private constructor(
|
||||
)
|
||||
}
|
||||
|
||||
/** Backward twin of [getAlbumPage]; see [getTrackPageBefore] for the contract. */
|
||||
suspend fun getAlbumPageBefore(
|
||||
sort: String,
|
||||
includeSingles: Boolean,
|
||||
cursorRaw: String?,
|
||||
requestedLimit: Int,
|
||||
): Map<String, Any?> = withCatalogRecovery { database ->
|
||||
val dao = database.catalogDao()
|
||||
val revision = dao.getRevision()
|
||||
val kind = "albums:$sort:${if (includeSingles) 1 else 0}"
|
||||
val cursor = validateCursor(cursorRaw, revision, kind)
|
||||
val limit = requestedLimit.coerceIn(1, MAX_PAGE_SIZE)
|
||||
val descending = when {
|
||||
cursor == null -> emptyList()
|
||||
sort == "artist" -> dao.getAlbumArtistPageBefore(
|
||||
revision,
|
||||
includeSingles,
|
||||
cursor.text1.orEmpty(),
|
||||
cursor.text2.orEmpty(),
|
||||
cursor.text3.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
sort == "name" -> dao.getAlbumNamePageBefore(
|
||||
revision,
|
||||
includeSingles,
|
||||
cursor.text1.orEmpty(),
|
||||
cursor.text2.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
else -> emptyList()
|
||||
}
|
||||
val previous = descending.takeIf { it.size == limit }
|
||||
?.lastOrNull()
|
||||
?.let { row -> albumCursor(revision, kind, sort, row).encode() }
|
||||
mapOf(
|
||||
"items" to descending.reversed().map(AlbumSummaryEntity::toBridgeMap),
|
||||
"nextCursor" to null,
|
||||
"previousCursor" to previous,
|
||||
"totalCount" to dao.countAlbums(revision, includeSingles).toDouble(),
|
||||
"catalogRevision" to revision.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
/** Shared by the forward and backward album pages so the two can never disagree. */
|
||||
private fun albumCursor(
|
||||
revision: Long,
|
||||
kind: String,
|
||||
sort: String,
|
||||
row: AlbumSummaryEntity,
|
||||
): TrackPageCursor =
|
||||
when (sort) {
|
||||
"artist" -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.artistSortKey,
|
||||
text2 = row.nameSortKey,
|
||||
text3 = row.identityKey,
|
||||
)
|
||||
"recently_added" -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.identityKey,
|
||||
number1 = row.latestAddedAt,
|
||||
)
|
||||
"year" -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.identityKey,
|
||||
number1 = (row.year ?: 0).toLong(),
|
||||
)
|
||||
else -> TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.identityKey,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getArtistPage(
|
||||
sort: String,
|
||||
groupingMode: String,
|
||||
@@ -1746,15 +1851,7 @@ class AstraLibraryRepository private constructor(
|
||||
limit,
|
||||
)
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row ->
|
||||
TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.artistKey,
|
||||
number1 = if (sort == "track_count") row.trackCount else null,
|
||||
).encode()
|
||||
}
|
||||
val next = rows.lastOrNull()?.let { row -> artistCursor(revision, kind, sort, row).encode() }
|
||||
mapOf(
|
||||
"items" to rows.map(ArtistSummaryEntity::toBridgeMap),
|
||||
"nextCursor" to next,
|
||||
@@ -1764,6 +1861,59 @@ class AstraLibraryRepository private constructor(
|
||||
)
|
||||
}
|
||||
|
||||
/** Backward twin of [getArtistPage]; see [getTrackPageBefore] for the contract. */
|
||||
suspend fun getArtistPageBefore(
|
||||
sort: String,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
cursorRaw: String?,
|
||||
requestedLimit: Int,
|
||||
): Map<String, Any?> = withCatalogRecovery { database ->
|
||||
val dao = database.catalogDao()
|
||||
val revision = dao.getRevision()
|
||||
val mode = if (groupingMode == "fileTags") "fileTags" else "astra"
|
||||
val kind = "artists:$sort:$mode:${if (includeCollaborations) 1 else 0}"
|
||||
val cursor = validateCursor(cursorRaw, revision, kind)
|
||||
val limit = requestedLimit.coerceIn(1, MAX_PAGE_SIZE)
|
||||
val descending = if (cursor == null || sort == "track_count") {
|
||||
emptyList()
|
||||
} else {
|
||||
dao.getArtistNamePageBefore(
|
||||
revision,
|
||||
mode,
|
||||
includeCollaborations,
|
||||
cursor.text1.orEmpty(),
|
||||
cursor.text2.orEmpty(),
|
||||
limit,
|
||||
)
|
||||
}
|
||||
val previous = descending.takeIf { it.size == limit }
|
||||
?.lastOrNull()
|
||||
?.let { row -> artistCursor(revision, kind, sort, row).encode() }
|
||||
mapOf(
|
||||
"items" to descending.reversed().map(ArtistSummaryEntity::toBridgeMap),
|
||||
"nextCursor" to null,
|
||||
"previousCursor" to previous,
|
||||
"totalCount" to dao.countArtists(revision, mode, includeCollaborations).toDouble(),
|
||||
"catalogRevision" to revision.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
/** Shared by the forward and backward artist pages so the two can never disagree. */
|
||||
private fun artistCursor(
|
||||
revision: Long,
|
||||
kind: String,
|
||||
sort: String,
|
||||
row: ArtistSummaryEntity,
|
||||
): TrackPageCursor =
|
||||
TrackPageCursor(
|
||||
revision,
|
||||
kind,
|
||||
text1 = row.nameSortKey,
|
||||
text2 = row.artistKey,
|
||||
number1 = if (sort == "track_count") row.trackCount else null,
|
||||
)
|
||||
|
||||
suspend fun getAlbumDetail(
|
||||
albumKey: String,
|
||||
cursorRaw: String?,
|
||||
|
||||
+113
@@ -360,6 +360,26 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
/**
|
||||
* Mirror of [getTitlePage] walking backwards. Rows come out DESC — the caller
|
||||
* reverses them so `items` is ascending like every other page. Backward paging
|
||||
* always has a cursor (there is nothing before the head), so no NULL branch.
|
||||
*/
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
WHERE title_sort_key < :beforeTitleKey
|
||||
OR (title_sort_key = :beforeTitleKey AND path < :beforePath)
|
||||
ORDER BY title_sort_key DESC, path DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getTitlePageBefore(
|
||||
beforeTitleKey: String,
|
||||
beforePath: String,
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
@@ -387,6 +407,34 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
/** Mirror of [getArtistOrderPage] walking backwards; rows come out DESC. */
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
WHERE artist_sort_key < :beforeArtistKey
|
||||
OR (artist_sort_key = :beforeArtistKey AND album_sort_key < :beforeAlbumKey)
|
||||
OR (artist_sort_key = :beforeArtistKey AND album_sort_key = :beforeAlbumKey AND disc_sort < :beforeDisc)
|
||||
OR (artist_sort_key = :beforeArtistKey AND album_sort_key = :beforeAlbumKey AND disc_sort = :beforeDisc
|
||||
AND track_sort < :beforeTrack)
|
||||
OR (artist_sort_key = :beforeArtistKey AND album_sort_key = :beforeAlbumKey AND disc_sort = :beforeDisc
|
||||
AND track_sort = :beforeTrack AND title_sort_key < :beforeTitleKey)
|
||||
OR (artist_sort_key = :beforeArtistKey AND album_sort_key = :beforeAlbumKey AND disc_sort = :beforeDisc
|
||||
AND track_sort = :beforeTrack AND title_sort_key = :beforeTitleKey AND path < :beforePath)
|
||||
ORDER BY artist_sort_key DESC, album_sort_key DESC, disc_sort DESC, track_sort DESC,
|
||||
title_sort_key DESC, path DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getArtistOrderPageBefore(
|
||||
beforeArtistKey: String,
|
||||
beforeAlbumKey: String,
|
||||
beforeDisc: Int,
|
||||
beforeTrack: Int,
|
||||
beforeTitleKey: String,
|
||||
beforePath: String,
|
||||
limit: Int,
|
||||
): List<ActiveTrackView>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM active_tracks
|
||||
@@ -543,6 +591,26 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
/** Mirror of [getAlbumNamePage] walking backwards; rows come out DESC. */
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
WHERE revision = :revision
|
||||
AND (:includeSingles OR is_single = 0)
|
||||
AND (name_sort_key < :beforeKey
|
||||
OR (name_sort_key = :beforeKey AND identity_key < :beforeId))
|
||||
ORDER BY name_sort_key DESC, identity_key DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getAlbumNamePageBefore(
|
||||
revision: Long,
|
||||
includeSingles: Boolean,
|
||||
beforeKey: String,
|
||||
beforeId: String,
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
@@ -566,6 +634,29 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
/** Mirror of [getAlbumArtistPage] walking backwards; rows come out DESC. */
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
WHERE revision = :revision
|
||||
AND (:includeSingles OR is_single = 0)
|
||||
AND (artist_sort_key < :beforeArtistKey
|
||||
OR (artist_sort_key = :beforeArtistKey AND name_sort_key < :beforeNameKey)
|
||||
OR (artist_sort_key = :beforeArtistKey AND name_sort_key = :beforeNameKey
|
||||
AND identity_key < :beforeId))
|
||||
ORDER BY artist_sort_key DESC, name_sort_key DESC, identity_key DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getAlbumArtistPageBefore(
|
||||
revision: Long,
|
||||
includeSingles: Boolean,
|
||||
beforeArtistKey: String,
|
||||
beforeNameKey: String,
|
||||
beforeId: String,
|
||||
limit: Int,
|
||||
): List<AlbumSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM album_summaries
|
||||
@@ -652,6 +743,28 @@ interface CatalogDao {
|
||||
limit: Int,
|
||||
): List<ArtistSummaryEntity>
|
||||
|
||||
/** Mirror of [getArtistNamePage] walking backwards; rows come out DESC. */
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM artist_summaries
|
||||
WHERE revision = :revision
|
||||
AND grouping_mode = :groupingMode
|
||||
AND (:includeCollaborations OR is_collaboration = 0)
|
||||
AND (name_sort_key < :beforeKey
|
||||
OR (name_sort_key = :beforeKey AND artist_key < :beforeId))
|
||||
ORDER BY name_sort_key DESC, artist_key DESC
|
||||
LIMIT :limit
|
||||
""",
|
||||
)
|
||||
suspend fun getArtistNamePageBefore(
|
||||
revision: Long,
|
||||
groupingMode: String,
|
||||
includeCollaborations: Boolean,
|
||||
beforeKey: String,
|
||||
beforeId: String,
|
||||
limit: Int,
|
||||
): List<ArtistSummaryEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM artist_summaries
|
||||
|
||||
Reference in New Issue
Block a user