Skip to content

Commit

Permalink
Feature/68 whole book progress (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
GrakovNe authored Nov 26, 2024
1 parent a322884 commit e0d1681
Show file tree
Hide file tree
Showing 23 changed files with 188 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,24 @@ abstract class AudiobookshelfChannel(
.fetchLibraries()
.map { libraryResponseConverter.apply(it) }

override suspend fun fetchRecentListenedBooks(libraryId: String): ApiResult<List<RecentBook>> =
dataRepository
override suspend fun fetchRecentListenedBooks(libraryId: String): ApiResult<List<RecentBook>> {
val progress: Map<String, Double> = dataRepository
.fetchUserInfoResponse()
.fold(
onSuccess = {
it
.user
.mediaProgress
?.associate { item -> item.libraryItemId to item.progress }
?: emptyMap()
},
onFailure = { emptyMap() },
)

return dataRepository
.fetchPersonalizedFeed(libraryId)
.map { recentBookResponseConverter.apply(it) }
.map { recentBookResponseConverter.apply(it, progress) }
}

override suspend fun fetchConnectionInfo(): ApiResult<ConnectionInfo> = dataRepository
.fetchConnectionInfo()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import javax.inject.Singleton
@Singleton
class RecentListeningResponseConverter @Inject constructor() {

fun apply(response: List<PersonalizedFeedResponse>): List<RecentBook> = response
fun apply(
response: List<PersonalizedFeedResponse>,
progress: Map<String, Double>,
): List<RecentBook> = response
.find { it.labelStringKey == LABEL_CONTINUE_LISTENING }
?.entities
?.distinctBy { it.id }
Expand All @@ -17,10 +20,12 @@ class RecentListeningResponseConverter @Inject constructor() {
id = it.id,
title = it.media.metadata.title,
author = it.media.metadata.authorName,
listenedPercentage = progress[it.id]?.let { it * 100 }?.toInt(),
)
} ?: emptyList()

companion object {

private const val LABEL_CONTINUE_LISTENING = "LabelContinueListening"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ data class MediaProgressResponse(
val currentTime: Double,
val isFinished: Boolean,
val lastUpdate: Long,
val progress: Double,
)
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class BookResponseConverter @Inject constructor() {
}
?: emptyList(),
chapters = maybeChapters ?: filesAsChapters(),
libraryId = item.libraryId,
progress = progressResponse
?.let {
MediaProgress(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.grakovne.lissen.channel.audiobookshelf.library.model
data class BookResponse(
val id: String,
val ino: String,
val libraryId: String,
val media: BookMedia,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class PodcastResponseConverter @Inject constructor() {
return DetailedItem(
id = item.id,
title = item.media.metadata.title,
libraryId = item.libraryId,
author = item.media.metadata.author,
files = orderedEpisodes
?.map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.grakovne.lissen.channel.audiobookshelf.podcast.model
data class PodcastResponse(
val id: String,
val ino: String,
val libraryId: String,
val media: PodcastMedia,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ object LocalCacheModule {
return database
.addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3)
.addMigrations(MIGRATION_3_4)
.build()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import org.grakovne.lissen.content.cache.entity.MediaProgressEntity
MediaProgressEntity::class,
CachedLibraryEntity::class,
],
version = 3,
version = 4,
exportSchema = true,
)
abstract class LocalCacheStorage : RoomDatabase() {
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/org/grakovne/lissen/content/cache/Migrations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,16 @@ val MIGRATION_2_3 = object : Migration(2, 3) {
db.execSQL("ALTER TABLE libraries_new RENAME TO libraries")
}
}

val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE detailed_books ADD COLUMN libraryId TEXT")

db.execSQL(
"""
UPDATE detailed_books
SET libraryId = NULL
""".trimIndent(),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.grakovne.lissen.domain.Book
import org.grakovne.lissen.domain.DetailedItem
import org.grakovne.lissen.domain.PlaybackProgress
import org.grakovne.lissen.domain.RecentBook
import org.grakovne.lissen.persistence.preferences.LissenSharedPreferences
import java.io.File
import java.time.Instant
import javax.inject.Inject
Expand All @@ -24,6 +25,7 @@ class CachedBookRepository @Inject constructor(
private val cachedBookEntityConverter: CachedBookEntityConverter,
private val cachedBookEntityDetailedConverter: CachedBookEntityDetailedConverter,
private val cachedBookEntityRecentConverter: CachedBookEntityRecentConverter,
private val preferences: LissenSharedPreferences,
) {

fun provideFileUri(bookId: String, fileId: String): Uri = properties
Expand All @@ -42,25 +44,43 @@ class CachedBookRepository @Inject constructor(
bookDao.upsertCachedBook(book)
}

suspend fun fetchCachedBooksIds() = bookDao.fetchBookIds()
suspend fun fetchCachedBooksIds() = bookDao.fetchBookIds(
libraryId = preferences.getPreferredLibrary()?.id,
)

suspend fun fetchBooks(
pageNumber: Int,
pageSize: Int,
): List<Book> = bookDao
.fetchCachedBooks(pageNumber = pageNumber, pageSize = pageSize)
.fetchCachedBooks(
pageNumber = pageNumber,
pageSize = pageSize,
libraryId = preferences.getPreferredLibrary()?.id,
)
.map { cachedBookEntityConverter.apply(it) }

suspend fun searchBooks(
query: String,
): List<Book> = bookDao
.searchCachedBooks(searchQuery = query)
.searchCachedBooks(
searchQuery = query,
libraryId = preferences.getPreferredLibrary()?.id,
)
.map { cachedBookEntityConverter.apply(it) }

suspend fun fetchRecentBooks(): List<RecentBook> =
bookDao
.fetchRecentlyListenedCachedBooks()
.map { cachedBookEntityRecentConverter.apply(it) }
suspend fun fetchRecentBooks(): List<RecentBook> {
val recentBooks = bookDao.fetchRecentlyListenedCachedBooks(
libraryId = preferences.getPreferredLibrary()?.id,
)

val progress = recentBooks
.map { it.id }
.mapNotNull { bookDao.fetchMediaProgress(it) }
.associate { it.bookId to it.currentTime }

return recentBooks
.map { cachedBookEntityRecentConverter.apply(it, progress[it.id]) }
}

suspend fun fetchBook(
bookId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class CachedBookEntityDetailedConverter @Inject constructor() {
id = entity.detailedBook.id,
title = entity.detailedBook.title,
author = entity.detailedBook.author,
libraryId = entity.detailedBook.libraryId,
files = entity.files.map { fileEntity ->
BookFile(
id = fileEntity.bookFileId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ import javax.inject.Singleton
@Singleton
class CachedBookEntityRecentConverter @Inject constructor() {

fun apply(entity: BookEntity): RecentBook = RecentBook(
fun apply(entity: BookEntity, currentTime: Double?): RecentBook = RecentBook(
id = entity.id,
title = entity.title,
author = entity.author,
listenedPercentage = currentTime
?.let { it / entity.duration }
?.let { it * 100 }
?.toInt(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface CachedBookDao {
title = book.title,
author = book.author,
duration = book.chapters.sumOf { it.duration }.toInt(),
libraryId = book.libraryId,
)

val bookFiles = book
Expand Down Expand Up @@ -70,29 +71,45 @@ interface CachedBookDao {
}

@Transaction
@Query("SELECT * FROM detailed_books ORDER BY title LIMIT :pageSize OFFSET :pageNumber * :pageSize")
@Query(
"""
SELECT * FROM detailed_books
WHERE (libraryId IS NULL OR libraryId = :libraryId)
ORDER BY title
LIMIT :pageSize OFFSET :pageNumber * :pageSize
""",
)
suspend fun fetchCachedBooks(
libraryId: String?,
pageNumber: Int,
pageSize: Int,
): List<BookEntity>

@Transaction
@Query("SELECT * FROM detailed_books WHERE title LIKE '%' || :searchQuery || '%' OR author LIKE '%' || :searchQuery || '%' ORDER BY title")
@Query(
"""
SELECT * FROM detailed_books
WHERE (libraryId IS NULL OR libraryId = :libraryId)
AND (title LIKE '%' || :searchQuery || '%' OR author LIKE '%' || :searchQuery || '%')
ORDER BY title
""",
)
suspend fun searchCachedBooks(
libraryId: String?,
searchQuery: String,
): List<BookEntity>

@Transaction
@RewriteQueriesToDropUnusedColumns
@Query(
"""
SELECT * FROM detailed_books
INNER JOIN media_progress ON detailed_books.id = media_progress.bookId
SELECT * FROM detailed_books
INNER JOIN media_progress ON detailed_books.id = media_progress.bookId WHERE (libraryId IS NULL OR libraryId = :libraryId)
ORDER BY media_progress.lastUpdate DESC
LIMIT 10
""",
)
suspend fun fetchRecentlyListenedCachedBooks(): List<BookEntity>
suspend fun fetchRecentlyListenedCachedBooks(libraryId: String?): List<BookEntity>

@Transaction
@Query("SELECT * FROM detailed_books WHERE id = :bookId")
Expand All @@ -103,8 +120,8 @@ interface CachedBookDao {
suspend fun fetchBook(bookId: String): BookEntity?

@Transaction
@Query("SELECT id FROM detailed_books")
suspend fun fetchBookIds(): List<String>
@Query("SELECT id FROM detailed_books WHERE (libraryId IS NULL OR libraryId = :libraryId) ")
suspend fun fetchBookIds(libraryId: String?): List<String>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertBook(book: BookEntity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ data class BookEntity(
val title: String,
val author: String?,
val duration: Int,
val libraryId: String?,
) : Serializable

@Entity(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ data class DetailedItem(
val files: List<BookFile>,
val chapters: List<BookChapter>,
val progress: MediaProgress?,
val libraryId: String?,
) : Serializable

data class BookFile(
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/org/grakovne/lissen/domain/RecentBook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ data class RecentBook(
val id: String,
val title: String,
val author: String?,
val listenedPercentage: Int?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ fun AsyncShimmeringImage(
contentScale: ContentScale,
modifier: Modifier = Modifier,
error: Painter,
onLoadingStateChanged: (Boolean) -> Unit = {},
) {
var isLoading by remember { mutableStateOf(true) }

Expand All @@ -48,8 +49,14 @@ fun AsyncShimmeringImage(
contentDescription = contentDescription,
contentScale = contentScale,
modifier = Modifier.fillMaxSize(),
onSuccess = { isLoading = false },
onError = { isLoading = false },
onSuccess = {
isLoading = false
onLoadingStateChanged(false)
},
onError = {
isLoading = false
onLoadingStateChanged(false)
},
error = error,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ fun LibraryScreen(
navController = navController,
recentBooks = showingRecentBooks,
imageLoader = imageLoader,
libraryViewModel = libraryViewModel,
)

Spacer(modifier = Modifier.height(20.dp))
Expand Down Expand Up @@ -323,6 +324,7 @@ fun LibraryScreen(
searchRequested = searchRequested,
cachingModelView = cachingModelView,
networkQualityService = networkQualityService,
libraryViewModel = libraryViewModel,
)
}
}
Expand Down
Loading

0 comments on commit e0d1681

Please sign in to comment.