mirror of
https://github.com/sudoxnym/audiobookshelf-atv.git
synced 2026-04-14 19:46:30 +00:00
Added better library browsing for Android Auto
Each library has 3 options: Library, Series and Collection. Library is grouped by authors
This commit is contained in:
parent
2b1e53b371
commit
a3a58a25ef
13 changed files with 785 additions and 43 deletions
|
|
@ -0,0 +1,36 @@
|
|||
package com.audiobookshelf.app.data
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.v4.media.MediaDescriptionCompat
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class CollapsedSeries(
|
||||
id:String,
|
||||
var libraryId:String?,
|
||||
var name:String,
|
||||
//var nameIgnorePrefix:String,
|
||||
var sequence:String?,
|
||||
var libraryItemIds:MutableList<String>
|
||||
) : LibraryItemWrapper(id) {
|
||||
@get:JsonIgnore
|
||||
val title get() = name
|
||||
@get:JsonIgnore
|
||||
val numBooks get() = libraryItemIds.size
|
||||
|
||||
@JsonIgnore
|
||||
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
|
||||
val extras = Bundle()
|
||||
|
||||
val mediaId = "__LIBRARY__${libraryId}__SERIE__${id}"
|
||||
return MediaDescriptionCompat.Builder()
|
||||
.setMediaId(mediaId)
|
||||
.setTitle(title)
|
||||
//.setIconUri(getCoverUri())
|
||||
.setSubtitle("${numBooks} books")
|
||||
.setExtras(extras)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.audiobookshelf.app.data
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.support.v4.media.MediaDescriptionCompat
|
||||
import com.audiobookshelf.app.BuildConfig
|
||||
import com.audiobookshelf.app.R
|
||||
import com.audiobookshelf.app.device.DeviceManager
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class LibraryAuthorItem(
|
||||
id:String,
|
||||
var libraryId:String,
|
||||
var name:String,
|
||||
var lastFirst:String,
|
||||
var description:String?,
|
||||
var imagePath:String?,
|
||||
var addedAt:Long,
|
||||
var updatedAt:Long,
|
||||
var numBooks:Int?,
|
||||
var libraryItems:MutableList<LibraryItem>?,
|
||||
var series:MutableList<LibrarySeriesItem>?
|
||||
) : LibraryItemWrapper(id) {
|
||||
@get:JsonIgnore
|
||||
val title get() = name
|
||||
|
||||
@get:JsonIgnore
|
||||
val bookCount get() = if (numBooks != null) numBooks else libraryItems!!.size
|
||||
|
||||
@JsonIgnore
|
||||
fun getPortraitUri(): Uri {
|
||||
if (imagePath == null) {
|
||||
return Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/" + R.drawable.md_account_outline)
|
||||
}
|
||||
|
||||
return Uri.parse("${DeviceManager.serverAddress}/api/authors/$id/image?token=${DeviceManager.token}")
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
|
||||
val extras = Bundle()
|
||||
|
||||
val mediaId = "__LIBRARY__${libraryId}__AUTHOR__${id}"
|
||||
return MediaDescriptionCompat.Builder()
|
||||
.setMediaId(mediaId)
|
||||
.setTitle(title)
|
||||
.setIconUri(getPortraitUri())
|
||||
.setSubtitle("${bookCount} books")
|
||||
.setExtras(extras)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.audiobookshelf.app.data
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.v4.media.MediaDescriptionCompat
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class LibraryCollection(
|
||||
id:String,
|
||||
var libraryId:String,
|
||||
var name:String,
|
||||
//var userId:String?,
|
||||
var description:String?,
|
||||
var books:MutableList<LibraryItem>?,
|
||||
) : LibraryItemWrapper(id) {
|
||||
@get:JsonIgnore
|
||||
val title get() = name
|
||||
|
||||
@get:JsonIgnore
|
||||
val bookCount get() = if (books != null) books!!.size else 0
|
||||
|
||||
@get:JsonIgnore
|
||||
val audiobookCount get() = books?.filter { book -> (book.media as Book).getAudioTracks().isNotEmpty() }?.size ?: 0
|
||||
|
||||
@JsonIgnore
|
||||
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
|
||||
val extras = Bundle()
|
||||
|
||||
val mediaId = "__LIBRARY__${libraryId}__COLLECTION__${id}"
|
||||
return MediaDescriptionCompat.Builder()
|
||||
.setMediaId(mediaId)
|
||||
.setTitle(title)
|
||||
//.setIconUri(getCoverUri())
|
||||
.setSubtitle("${bookCount} books")
|
||||
.setExtras(extras)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
@ -32,10 +32,17 @@ class LibraryItem(
|
|||
var media:MediaType,
|
||||
var libraryFiles:MutableList<LibraryFile>?,
|
||||
var userMediaProgress:MediaProgress?, // Only included when requesting library item with progress (for downloads)
|
||||
var collapsedSeries: CollapsedSeries?,
|
||||
var localLibraryItemId:String? // For Android Auto
|
||||
) : LibraryItemWrapper(id) {
|
||||
@get:JsonIgnore
|
||||
val title get() = media.metadata.title
|
||||
val title: String
|
||||
get() {
|
||||
if (collapsedSeries != null) {
|
||||
return collapsedSeries!!.title
|
||||
}
|
||||
return media.metadata.title
|
||||
}
|
||||
@get:JsonIgnore
|
||||
val authorName get() = media.metadata.getAuthorDisplayName()
|
||||
|
||||
|
|
@ -58,49 +65,76 @@ class LibraryItem(
|
|||
}
|
||||
|
||||
@JsonIgnore
|
||||
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
|
||||
fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context, authorId: String?): MediaDescriptionCompat {
|
||||
val extras = Bundle()
|
||||
|
||||
if (localLibraryItemId != null) {
|
||||
extras.putLong(
|
||||
MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
|
||||
MediaDescriptionCompat.STATUS_DOWNLOADED
|
||||
)
|
||||
}
|
||||
|
||||
if (progress != null) {
|
||||
if (progress.isFinished) {
|
||||
extras.putInt(
|
||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
||||
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
|
||||
)
|
||||
} else {
|
||||
extras.putInt(
|
||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
||||
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
|
||||
)
|
||||
extras.putDouble(
|
||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress.progress
|
||||
if (collapsedSeries == null) {
|
||||
if (localLibraryItemId != null) {
|
||||
extras.putLong(
|
||||
MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
|
||||
MediaDescriptionCompat.STATUS_DOWNLOADED
|
||||
)
|
||||
}
|
||||
|
||||
if (progress != null) {
|
||||
if (progress.isFinished) {
|
||||
extras.putInt(
|
||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
||||
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
|
||||
)
|
||||
} else {
|
||||
extras.putInt(
|
||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
||||
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
|
||||
)
|
||||
extras.putDouble(
|
||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress.progress
|
||||
)
|
||||
}
|
||||
} else if (mediaType != "podcast") {
|
||||
extras.putInt(
|
||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
||||
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
|
||||
)
|
||||
}
|
||||
|
||||
if (media.metadata.explicit) {
|
||||
extras.putLong(
|
||||
MediaConstants.METADATA_KEY_IS_EXPLICIT,
|
||||
MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
|
||||
)
|
||||
}
|
||||
} else if (mediaType != "podcast") {
|
||||
extras.putInt(
|
||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
||||
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
|
||||
)
|
||||
}
|
||||
|
||||
if (media.metadata.explicit) {
|
||||
extras.putLong(MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
|
||||
val mediaId = if (localLibraryItemId != null) {
|
||||
localLibraryItemId
|
||||
} else if (collapsedSeries != null) {
|
||||
if (authorId != null) {
|
||||
"__LIBRARY__${libraryId}__AUTHOR_SERIES__${authorId}__${collapsedSeries!!.id}"
|
||||
} else {
|
||||
"__LIBRARY__${libraryId}__SERIES__${collapsedSeries!!.id}"
|
||||
}
|
||||
} else {
|
||||
id
|
||||
}
|
||||
var subtitle = authorName
|
||||
if (collapsedSeries != null) {
|
||||
subtitle = "${collapsedSeries!!.numBooks} books"
|
||||
}
|
||||
|
||||
val mediaId = localLibraryItemId ?: id
|
||||
return MediaDescriptionCompat.Builder()
|
||||
.setMediaId(mediaId)
|
||||
.setTitle(title)
|
||||
.setIconUri(getCoverUri())
|
||||
.setSubtitle(authorName)
|
||||
.setSubtitle(subtitle)
|
||||
.setExtras(extras)
|
||||
.build()
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
|
||||
/*
|
||||
This is needed so Android auto library hierarchy for author series can be implemented
|
||||
*/
|
||||
return getMediaDescription(progress, ctx, null)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
package com.audiobookshelf.app.data
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.v4.media.MediaDescriptionCompat
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class LibrarySeriesItem(
|
||||
id:String,
|
||||
var libraryId:String,
|
||||
var name:String,
|
||||
var description:String?,
|
||||
var addedAt:Long,
|
||||
var updatedAt:Long,
|
||||
var books:MutableList<LibraryItem>?,
|
||||
var localLibraryItemId:String? // For Android Auto
|
||||
) : LibraryItemWrapper(id) {
|
||||
@get:JsonIgnore
|
||||
val title get() = name
|
||||
|
||||
@get:JsonIgnore
|
||||
val audiobookCount: Int
|
||||
get() {
|
||||
if (books == null) return 0
|
||||
val booksWithAudio = books?.filter { b -> (b.media as Book).numTracks != 0 }
|
||||
return booksWithAudio?.size ?: 0
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
|
||||
val extras = Bundle()
|
||||
|
||||
if (localLibraryItemId != null) {
|
||||
extras.putLong(
|
||||
MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
|
||||
MediaDescriptionCompat.STATUS_DOWNLOADED
|
||||
)
|
||||
}
|
||||
|
||||
val mediaId = "__LIBRARY__${libraryId}__SERIES__${id}"
|
||||
return MediaDescriptionCompat.Builder()
|
||||
.setMediaId(mediaId)
|
||||
.setTitle(title)
|
||||
//.setIconUri(getCoverUri())
|
||||
.setSubtitle("$audiobookCount books")
|
||||
.setExtras(extras)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,13 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
|||
private var selectedLibraryItems = mutableListOf<LibraryItem>()
|
||||
private var selectedLibraryId = ""
|
||||
|
||||
private var cachedLibraryAuthors : MutableMap<String, MutableMap<String, LibraryAuthorItem>> = hashMapOf()
|
||||
private var cachedLibraryAuthorItems : MutableMap<String, MutableMap<String, List<LibraryItem>>> = hashMapOf()
|
||||
private var cachedLibraryAuthorSeriesItems : MutableMap<String, MutableMap<String, List<LibraryItem>>> = hashMapOf()
|
||||
private var cachedLibrarySeries : MutableMap<String, List<LibrarySeriesItem>> = hashMapOf()
|
||||
private var cachedLibrarySeriesItem : MutableMap<String, MutableMap<String, List<LibraryItem>>> = hashMapOf()
|
||||
private var cachedLibraryCollections : MutableMap<String, MutableMap<String, LibraryCollection>> = hashMapOf()
|
||||
|
||||
private var selectedPodcast:Podcast? = null
|
||||
private var selectedLibraryItemId:String? = null
|
||||
private var podcastEpisodeLibraryItemMap = mutableMapOf<String, LibraryItemWithEpisode>()
|
||||
|
|
@ -142,6 +149,229 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns series with audio books from selected library.
|
||||
* If data is not found from local cache then it will be fetched from server
|
||||
*/
|
||||
fun loadLibrarySeriesWithAudio(libraryId:String, cb: (List<LibrarySeriesItem>) -> Unit) {
|
||||
// Check "cache" first
|
||||
if (cachedLibrarySeries.containsKey(libraryId)) {
|
||||
Log.d(tag, "Series with audio found from cache | Library $libraryId ")
|
||||
cb(cachedLibrarySeries[libraryId] as List<LibrarySeriesItem>)
|
||||
} else {
|
||||
apiHandler.getLibrarySeries(libraryId) { seriesItems ->
|
||||
Log.d(tag, "Series with audio loaded from server | Library $libraryId")
|
||||
val seriesItemsWithAudio = seriesItems.filter { si -> si.audiobookCount > 0 }
|
||||
|
||||
cachedLibrarySeries[libraryId] = seriesItemsWithAudio
|
||||
|
||||
cb(seriesItemsWithAudio)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns series with audiobooks from selected library using filter for paging.
|
||||
* If data is not found from local cache then it will be fetched from server
|
||||
*/
|
||||
fun loadLibrarySeriesWithAudio(libraryId:String, seriesFilter:String, cb: (List<LibrarySeriesItem>) -> Unit) {
|
||||
// Check "cache" first
|
||||
if (!cachedLibrarySeries.containsKey(libraryId)) {
|
||||
loadLibrarySeriesWithAudio(libraryId) {}
|
||||
} else {
|
||||
Log.d(tag, "Series with audio found from cache | Library $libraryId ")
|
||||
}
|
||||
val seriesWithBooks = cachedLibrarySeries[libraryId]!!.filter { ls -> ls.title.uppercase().startsWith(seriesFilter) }.toList()
|
||||
cb(seriesWithBooks)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns books for series from library.
|
||||
* If data is not found from local cache then it will be fetched from server
|
||||
*/
|
||||
fun loadLibrarySeriesItemsWithAudio(libraryId:String, seriesId:String, cb: (List<LibraryItem>) -> Unit) {
|
||||
// Check "cache" first
|
||||
if (!cachedLibrarySeriesItem.containsKey(libraryId)) {
|
||||
cachedLibrarySeriesItem[libraryId] = hashMapOf()
|
||||
}
|
||||
if (cachedLibrarySeriesItem[libraryId]!!.containsKey(seriesId)) {
|
||||
Log.d(tag, "Items for series $seriesId found from cache | Library $libraryId")
|
||||
cachedLibrarySeriesItem[libraryId]!![seriesId]?.let { cb(it) }
|
||||
} else {
|
||||
apiHandler.getLibrarySeriesItems(libraryId, seriesId) { libraryItems ->
|
||||
Log.d(tag, "Items for series $seriesId loaded from server | Library $libraryId")
|
||||
val libraryItemsWithAudio = libraryItems.filter { li -> li.checkHasTracks() }
|
||||
|
||||
cachedLibrarySeriesItem[libraryId]!![seriesId] = libraryItemsWithAudio
|
||||
|
||||
libraryItemsWithAudio.forEach { libraryItem ->
|
||||
if (serverLibraryItems.find { li -> li.id == libraryItem.id } == null) {
|
||||
serverLibraryItems.add(libraryItem)
|
||||
}
|
||||
}
|
||||
cb(libraryItemsWithAudio)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns authors with books from library.
|
||||
* If data is not found from local cache then it will be fetched from server
|
||||
*/
|
||||
fun loadAuthorsWithBooks(libraryId:String, cb: (List<LibraryAuthorItem>) -> Unit) {
|
||||
// Check "cache" first
|
||||
if (cachedLibraryAuthors.containsKey(libraryId)) {
|
||||
Log.d(tag, "Authors with books found from cache | Library $libraryId ")
|
||||
cb(cachedLibraryAuthors[libraryId]!!.values.toList())
|
||||
} else {
|
||||
// Fetch data from server and add it to local "cache"
|
||||
apiHandler.getLibraryAuthors(libraryId) { authorItems ->
|
||||
Log.d(tag, "Authors with books loaded from server | Library $libraryId ")
|
||||
// TO-DO: This check won't ensure that there is audiobooks. Current API won't offer ability to do so
|
||||
val authorItemsWithBooks = authorItems.filter { li -> li.bookCount != null && li.bookCount!! > 0 }
|
||||
|
||||
// Ensure that there is map for library
|
||||
cachedLibraryAuthors[libraryId] = mutableMapOf()
|
||||
// Cache authors
|
||||
authorItemsWithBooks.forEach {
|
||||
if (!cachedLibraryAuthors[libraryId]!!.containsKey(it.id)) {
|
||||
cachedLibraryAuthors[libraryId]!![it.id] = it
|
||||
}
|
||||
}
|
||||
cb(authorItemsWithBooks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns authors with books from selected library using filter for paging.
|
||||
* If data is not found from local cache then it will be fetched from server
|
||||
*/
|
||||
fun loadAuthorsWithBooks(libraryId:String, authorFilter: String, cb: (List<LibraryAuthorItem>) -> Unit) {
|
||||
// Check "cache" first
|
||||
if (cachedLibraryAuthors.containsKey(libraryId)) {
|
||||
Log.d(tag, "Authors with books found from cache | Library $libraryId ")
|
||||
} else {
|
||||
loadAuthorsWithBooks(libraryId) {}
|
||||
}
|
||||
val authorsWithBooks = cachedLibraryAuthors[libraryId]!!.values.filter { lai -> lai.name.uppercase().startsWith(authorFilter) }.toList()
|
||||
cb(authorsWithBooks)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns audiobooks for author from library
|
||||
* If data is not found from local cache then it will be fetched from server
|
||||
*/
|
||||
fun loadAuthorBooksWithAudio(libraryId:String, authorId:String, cb: (List<LibraryItem>) -> Unit) {
|
||||
// Ensure that there is map for library
|
||||
if (!cachedLibraryAuthorItems.containsKey(libraryId)) {
|
||||
cachedLibraryAuthorItems[libraryId] = mutableMapOf()
|
||||
}
|
||||
// Check "cache" first
|
||||
if (cachedLibraryAuthorItems[libraryId]!!.containsKey(authorId)) {
|
||||
Log.d(tag, "Items for author $authorId found from cache | Library $libraryId")
|
||||
cachedLibraryAuthorItems[libraryId]!![authorId]?.let { cb(it) }
|
||||
} else {
|
||||
apiHandler.getLibraryItemsFromAuthor(libraryId, authorId) { libraryItems ->
|
||||
Log.d(tag, "Items for author $authorId loaded from server | Library $libraryId")
|
||||
val libraryItemsWithAudio = libraryItems.filter { li -> li.checkHasTracks() }
|
||||
|
||||
cachedLibraryAuthorItems[libraryId]!![authorId] = libraryItemsWithAudio
|
||||
|
||||
libraryItemsWithAudio.forEach { libraryItem ->
|
||||
if (serverLibraryItems.find { li -> li.id == libraryItem.id } == null) {
|
||||
serverLibraryItems.add(libraryItem)
|
||||
}
|
||||
}
|
||||
|
||||
cb(libraryItemsWithAudio)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns audiobooks for author from specified series within library
|
||||
* If data is not found from local cache then it will be fetched from server
|
||||
*/
|
||||
fun loadAuthorSeriesBooksWithAudio(libraryId:String, authorId:String, seriesId: String, cb: (List<LibraryItem>) -> Unit) {
|
||||
val authorSeriesKey = "$authorId|$seriesId"
|
||||
// Ensure that there is map for library
|
||||
if (!cachedLibraryAuthorSeriesItems.containsKey(libraryId)) {
|
||||
cachedLibraryAuthorSeriesItems[libraryId] = mutableMapOf()
|
||||
}
|
||||
// Check "cache" first
|
||||
if (cachedLibraryAuthorSeriesItems[libraryId]!!.containsKey(authorSeriesKey)) {
|
||||
Log.d(tag, "Items for series $seriesId with author $authorId found from cache | Library $libraryId")
|
||||
cachedLibraryAuthorSeriesItems[libraryId]!![authorSeriesKey]?.let { cb(it) }
|
||||
} else {
|
||||
apiHandler.getLibrarySeriesItems(libraryId, seriesId) { libraryItems ->
|
||||
Log.d(tag, "Items for series $seriesId with author $authorId loaded from server | Library $libraryId")
|
||||
val libraryItemsWithAudio = libraryItems.filter { li -> li.checkHasTracks() }
|
||||
if (!cachedLibraryAuthors[libraryId]!!.containsKey(authorId)) {
|
||||
Log.d(tag, "Author data is missing")
|
||||
}
|
||||
val authorName = cachedLibraryAuthors[libraryId]!![authorId]?.name ?: ""
|
||||
Log.d(tag, "Using author name: $authorName")
|
||||
val libraryItemsFromAuthorWithAudio = libraryItemsWithAudio.filter { li -> li.authorName.indexOf(authorName, ignoreCase = true) >= 0 }
|
||||
|
||||
cachedLibraryAuthorSeriesItems[libraryId]!![authorId] = libraryItemsFromAuthorWithAudio
|
||||
|
||||
libraryItemsFromAuthorWithAudio.forEach { libraryItem ->
|
||||
if (serverLibraryItems.find { li -> li.id == libraryItem.id } == null) {
|
||||
serverLibraryItems.add(libraryItem)
|
||||
}
|
||||
}
|
||||
|
||||
cb(libraryItemsFromAuthorWithAudio)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns collections with audiobooks from library
|
||||
* If data is not found from local cache then it will be fetched from server
|
||||
*/
|
||||
fun loadLibraryCollectionsWithAudio(libraryId:String, cb: (List<LibraryCollection>) -> Unit) {
|
||||
if (cachedLibraryCollections.containsKey(libraryId)) {
|
||||
Log.d(tag, "Collections with books found from cache | Library $libraryId ")
|
||||
cb(cachedLibraryCollections[libraryId]!!.values.toList())
|
||||
} else {
|
||||
apiHandler.getLibraryCollections(libraryId) { libraryCollections ->
|
||||
Log.d(tag, "Collections with books loaded from server | Library $libraryId ")
|
||||
val libraryCollectionsWithAudio = libraryCollections.filter { lc -> lc.audiobookCount > 0 }
|
||||
|
||||
// Cache collections
|
||||
cachedLibraryCollections[libraryId] = hashMapOf()
|
||||
libraryCollectionsWithAudio.forEach {
|
||||
if (!cachedLibraryCollections[libraryId]!!.containsKey(it.id)) {
|
||||
cachedLibraryCollections[libraryId]!![it.id] = it
|
||||
}
|
||||
}
|
||||
cb(libraryCollectionsWithAudio)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns audiobooks for collection from library
|
||||
* If data is not found from local cache then it will be fetched from server
|
||||
*/
|
||||
fun loadLibraryCollectionBooksWithAudio(libraryId: String, collectionId: String, cb: (List<LibraryItem>) -> Unit) {
|
||||
if (!cachedLibraryCollections.containsKey(libraryId)) {
|
||||
loadLibraryCollectionsWithAudio(libraryId) {}
|
||||
}
|
||||
Log.d(tag, "Trying to find collection $collectionId items from from cache | Library $libraryId ")
|
||||
if ( cachedLibraryCollections[libraryId]!!.containsKey(collectionId)) {
|
||||
val libraryCollectionBookswithAudio = cachedLibraryCollections[libraryId]!![collectionId]?.books
|
||||
libraryCollectionBookswithAudio?.forEach { libraryItem ->
|
||||
if (serverLibraryItems.find { li -> li.id == libraryItem.id } == null) {
|
||||
serverLibraryItems.add(libraryItem)
|
||||
}
|
||||
}
|
||||
cb(libraryCollectionBookswithAudio as List<LibraryItem>)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadLibraryItem(libraryItemId:String, cb: (LibraryItemWrapper?) -> Unit) {
|
||||
if (libraryItemId.startsWith("local")) {
|
||||
cb(DeviceManager.dbManager.getLocalLibraryItem(libraryItemId))
|
||||
|
|
|
|||
|
|
@ -1029,29 +1029,35 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
|
||||
val localBooks = DeviceManager.dbManager.getLocalLibraryItems("book")
|
||||
val localPodcasts = DeviceManager.dbManager.getLocalLibraryItems("podcast")
|
||||
val localBrowseItems:MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
|
||||
val localBrowseItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
|
||||
|
||||
localBooks.forEach { localLibraryItem ->
|
||||
if (localLibraryItem.media.getAudioTracks().isNotEmpty()) {
|
||||
val progress = DeviceManager.dbManager.getLocalMediaProgress(localLibraryItem.id)
|
||||
val description = localLibraryItem.getMediaDescription(progress, ctx)
|
||||
|
||||
localBrowseItems += MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||
localBrowseItems += MediaBrowserCompat.MediaItem(
|
||||
description,
|
||||
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
localPodcasts.forEach { localLibraryItem ->
|
||||
val mediaDescription = localLibraryItem.getMediaDescription(null, ctx)
|
||||
localBrowseItems += MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||
localBrowseItems += MediaBrowserCompat.MediaItem(
|
||||
mediaDescription,
|
||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
)
|
||||
}
|
||||
|
||||
result.sendResult(localBrowseItems)
|
||||
|
||||
} else if (parentMediaId == CONTINUE_ROOT) {
|
||||
val localBrowseItems:MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
|
||||
val localBrowseItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
|
||||
mediaManager.serverItemsInProgress.forEach { itemInProgress ->
|
||||
val progress: MediaProgressWrapper?
|
||||
val mediaDescription:MediaDescriptionCompat
|
||||
val mediaDescription: MediaDescriptionCompat
|
||||
if (itemInProgress.episode != null) {
|
||||
if (itemInProgress.isLocal) {
|
||||
progress = DeviceManager.dbManager.getLocalMediaProgress("${itemInProgress.libraryItemWrapper.id}-${itemInProgress.episode.id}")
|
||||
|
|
@ -1093,20 +1099,224 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
}
|
||||
} else if (mediaManager.getIsLibrary(parentMediaId)) { // Load library items for library
|
||||
Log.d(tag, "Loading items for library $parentMediaId")
|
||||
mediaManager.loadLibraryItemsWithAudio(parentMediaId) { libraryItems ->
|
||||
val children = libraryItems.map { libraryItem ->
|
||||
if (libraryItem.mediaType == "podcast") { // Podcasts are browseable
|
||||
val mediaDescription = libraryItem.getMediaDescription(null, ctx)
|
||||
MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||
val children = mutableListOf(
|
||||
MediaBrowserCompat.MediaItem(
|
||||
MediaDescriptionCompat.Builder()
|
||||
.setTitle("Library")
|
||||
.setMediaId("__LIBRARY__${parentMediaId}__AUTHORS")
|
||||
.build(),
|
||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
),
|
||||
MediaBrowserCompat.MediaItem(
|
||||
MediaDescriptionCompat.Builder()
|
||||
.setTitle("Series")
|
||||
.setMediaId("__LIBRARY__${parentMediaId}__SERIES_LIST")
|
||||
.build(),
|
||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
),
|
||||
MediaBrowserCompat.MediaItem(
|
||||
MediaDescriptionCompat.Builder()
|
||||
.setTitle("Collections")
|
||||
.setMediaId("__LIBRARY__${parentMediaId}__COLLECTIONS")
|
||||
.build(),
|
||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
)
|
||||
)
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
} else if (parentMediaId.startsWith("__LIBRARY__")) {
|
||||
Log.d(tag, "Browsing library $parentMediaId")
|
||||
val mediaIdParts = parentMediaId.split("__")
|
||||
/*
|
||||
MediaIdParts for Library
|
||||
1: LIBRARY
|
||||
2: mediaId for library
|
||||
3: Browsing style (AUTHORS, AUTHOR, AUTHOR_SERIES, SERIES_LIST, SERIES, COLLECTION, COLLECTIONS)
|
||||
4:
|
||||
- Paging: SERIES_LIST, AUTHORS
|
||||
- SeriesId: SERIES
|
||||
- AuthorId: AUTHOR, AUTHOR_SERIES
|
||||
- CollectionId: COLLECTIONS
|
||||
5: SeriesId: AUTHOR_SERIES
|
||||
*/
|
||||
if (!mediaManager.getIsLibrary(mediaIdParts[2])) {
|
||||
Log.d(tag, "${mediaIdParts[2]} is not library")
|
||||
result.sendResult(null)
|
||||
return
|
||||
}
|
||||
Log.d(tag, "$mediaIdParts")
|
||||
if (mediaIdParts[3] == "SERIES_LIST" && mediaIdParts.size == 5) {
|
||||
Log.d(tag, "Loading series from library ${mediaIdParts[2]} with paging ${mediaIdParts[4]}")
|
||||
mediaManager.loadLibrarySeriesWithAudio(mediaIdParts[2], mediaIdParts[4]) { seriesItems ->
|
||||
Log.d(tag, "Received ${seriesItems.size} series")
|
||||
if (seriesItems.size > 500) {
|
||||
val seriesLetters = seriesItems.groupingBy { iwb -> iwb.title.substring(0, mediaIdParts[4].length + 1).uppercase() }.eachCount()
|
||||
val children = seriesLetters.map { (seriesLetter, seriesCount) ->
|
||||
MediaBrowserCompat.MediaItem(
|
||||
MediaDescriptionCompat.Builder()
|
||||
.setTitle(seriesLetter)
|
||||
.setMediaId("${parentMediaId}${seriesLetter.last()}")
|
||||
.setSubtitle("$seriesCount series")
|
||||
.build(),
|
||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
} else {
|
||||
val children = seriesItems.map { seriesItem ->
|
||||
val description = seriesItem.getMediaDescription(null, ctx)
|
||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
}
|
||||
}
|
||||
}else if (mediaIdParts[3] == "SERIES_LIST") {
|
||||
Log.d(tag, "Loading series from library ${mediaIdParts[2]}")
|
||||
mediaManager.loadLibrarySeriesWithAudio(mediaIdParts[2]) { seriesItems ->
|
||||
Log.d(tag, "Received ${seriesItems.size} series")
|
||||
if (seriesItems.size > 1000) {
|
||||
val seriesLetters = seriesItems.groupingBy { iwb -> iwb.title.first().uppercaseChar() }.eachCount()
|
||||
val children = seriesLetters.map { (seriesLetter, seriesCount) ->
|
||||
MediaBrowserCompat.MediaItem(
|
||||
MediaDescriptionCompat.Builder()
|
||||
.setTitle(seriesLetter.toString())
|
||||
.setSubtitle("$seriesCount series")
|
||||
.setMediaId("${parentMediaId}__${seriesLetter}")
|
||||
.build(),
|
||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
} else {
|
||||
val children = seriesItems.map { seriesItem ->
|
||||
val description = seriesItem.getMediaDescription(null, ctx)
|
||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
}
|
||||
}
|
||||
} else if (mediaIdParts[3] == "SERIES") {
|
||||
Log.d(tag, "Loading items for serie ${mediaIdParts[4]} from library ${mediaIdParts[2]}")
|
||||
mediaManager.loadLibrarySeriesItemsWithAudio(
|
||||
mediaIdParts[2],
|
||||
mediaIdParts[4]
|
||||
) { libraryItems ->
|
||||
Log.d(tag, "Received ${libraryItems.size} library items")
|
||||
val children = libraryItems.map { libraryItem ->
|
||||
val progress =
|
||||
mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItem.id }
|
||||
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(libraryItem.id)
|
||||
libraryItem.localLibraryItemId = localLibraryItem?.id
|
||||
val description = libraryItem.getMediaDescription(progress, ctx)
|
||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
}
|
||||
} else if (mediaIdParts[3] == "AUTHORS" && mediaIdParts.size == 5) {
|
||||
Log.d(tag, "Loading authors from library ${mediaIdParts[2]} with paging ${mediaIdParts[4]}")
|
||||
mediaManager.loadAuthorsWithBooks(mediaIdParts[2], mediaIdParts[4]) { authorItems ->
|
||||
Log.d(tag, "Received ${authorItems.size} authors")
|
||||
if (authorItems.size > 100) {
|
||||
val authorLetters = authorItems.groupingBy { iwb -> iwb.name.substring(0, mediaIdParts[4].length + 1).uppercase() }.eachCount()
|
||||
val children = authorLetters.map { (authorLetter, authorCount) ->
|
||||
MediaBrowserCompat.MediaItem(
|
||||
MediaDescriptionCompat.Builder()
|
||||
.setTitle(authorLetter)
|
||||
.setMediaId("${parentMediaId}${authorLetter.last()}")
|
||||
.setSubtitle("$authorCount authors")
|
||||
.build(),
|
||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
} else {
|
||||
val children = authorItems.map { authorItem ->
|
||||
val description = authorItem.getMediaDescription(null, ctx)
|
||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
}
|
||||
}
|
||||
} else if (mediaIdParts[3] == "AUTHORS") {
|
||||
Log.d(tag, "Loading authors from library ${mediaIdParts[2]}")
|
||||
mediaManager.loadAuthorsWithBooks(mediaIdParts[2]) { authorItems ->
|
||||
Log.d(tag, "Received ${authorItems.size} authors")
|
||||
if (authorItems.size > 1000) {
|
||||
val authorLetters = authorItems.groupingBy { iwb -> iwb.name.first().uppercaseChar() }.eachCount()
|
||||
val children = authorLetters.map { (authorLetter, authorCount) ->
|
||||
MediaBrowserCompat.MediaItem(
|
||||
MediaDescriptionCompat.Builder()
|
||||
.setTitle(authorLetter.toString())
|
||||
.setSubtitle("$authorCount authors")
|
||||
.setMediaId("${parentMediaId}__${authorLetter}")
|
||||
.build(),
|
||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
} else {
|
||||
val children = authorItems.map { authorItem ->
|
||||
val description = authorItem.getMediaDescription(null, ctx)
|
||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
}
|
||||
}
|
||||
} else if (mediaIdParts[3] == "AUTHOR") {
|
||||
mediaManager.loadAuthorBooksWithAudio(mediaIdParts[2], mediaIdParts[4]) { libraryItems ->
|
||||
val children = libraryItems.map { libraryItem ->
|
||||
val progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItem.id }
|
||||
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(libraryItem.id)
|
||||
libraryItem.localLibraryItemId = localLibraryItem?.id
|
||||
if (libraryItem.collapsedSeries != null) {
|
||||
val description = libraryItem.getMediaDescription(progress, ctx, mediaIdParts[4])
|
||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||
} else {
|
||||
val description = libraryItem.getMediaDescription(progress, ctx)
|
||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||
}
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
}
|
||||
} else if (mediaIdParts[3] == "AUTHOR_SERIES") {
|
||||
mediaManager.loadAuthorSeriesBooksWithAudio(mediaIdParts[2], mediaIdParts[4], mediaIdParts[5]) { libraryItems ->
|
||||
val children = libraryItems.map { libraryItem ->
|
||||
val progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItem.id }
|
||||
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(libraryItem.id)
|
||||
libraryItem.localLibraryItemId = localLibraryItem?.id
|
||||
val description = libraryItem.getMediaDescription(progress, ctx)
|
||||
if (libraryItem.collapsedSeries != null) {
|
||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||
} else {
|
||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||
}
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
}
|
||||
} else if (mediaIdParts[3] == "COLLECTIONS") {
|
||||
Log.d(tag, "Loading collections from library ${mediaIdParts[2]}")
|
||||
mediaManager.loadLibraryCollectionsWithAudio(mediaIdParts[2]) { collectionItems ->
|
||||
Log.d(tag, "Received ${collectionItems.size} collections")
|
||||
val children = collectionItems.map { collectionItem ->
|
||||
val description = collectionItem.getMediaDescription(null, ctx)
|
||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
}
|
||||
} else if (mediaIdParts[3] == "COLLECTION") {
|
||||
Log.d(tag, "Loading collection ${mediaIdParts[4]} books from library ${mediaIdParts[2]}")
|
||||
mediaManager.loadLibraryCollectionBooksWithAudio(mediaIdParts[2], mediaIdParts[4]) { libraryItems ->
|
||||
Log.d(tag, "Received ${libraryItems.size} collections")
|
||||
val children = libraryItems.map { libraryItem ->
|
||||
val progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItem.id }
|
||||
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(libraryItem.id)
|
||||
libraryItem.localLibraryItemId = localLibraryItem?.id
|
||||
val description = libraryItem.getMediaDescription(progress, ctx)
|
||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
} else {
|
||||
result.sendResult(null)
|
||||
}
|
||||
} else {
|
||||
Log.d(tag, "Loading podcast episodes for podcast $parentMediaId")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.audiobookshelf.app.data.*
|
||||
import com.audiobookshelf.app.device.DeviceManager
|
||||
|
|
@ -189,6 +190,90 @@ class ApiHandler(var ctx:Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun getLibrarySeries(libraryId:String, cb: (List<LibrarySeriesItem>) -> Unit) {
|
||||
Log.d(tag, "Getting series")
|
||||
getRequest("/api/libraries/$libraryId/series?minified=1&sort=name&limit=10000", null, null) {
|
||||
val items = mutableListOf<LibrarySeriesItem>()
|
||||
if (it.has("results")) {
|
||||
val array = it.getJSONArray("results")
|
||||
for (i in 0 until array.length()) {
|
||||
val item = jacksonMapper.readValue<LibrarySeriesItem>(array.get(i).toString())
|
||||
items.add(item)
|
||||
}
|
||||
}
|
||||
cb(items)
|
||||
}
|
||||
}
|
||||
|
||||
fun getLibrarySeriesItems(libraryId:String, seriesId:String, cb: (List<LibraryItem>) -> Unit) {
|
||||
Log.d(tag, "Getting items for series")
|
||||
val seriesIdBase64 = Base64.encodeToString(seriesId.toByteArray(), Base64.DEFAULT)
|
||||
getRequest("/api/libraries/$libraryId/items?minified=1&sort=media.metadata.title&filter=series.${seriesIdBase64}&limit=1000", null, null) {
|
||||
val items = mutableListOf<LibraryItem>()
|
||||
if (it.has("results")) {
|
||||
val array = it.getJSONArray("results")
|
||||
for (i in 0 until array.length()) {
|
||||
val item = jacksonMapper.readValue<LibraryItem>(array.get(i).toString())
|
||||
items.add(item)
|
||||
}
|
||||
}
|
||||
cb(items)
|
||||
}
|
||||
}
|
||||
|
||||
fun getLibraryAuthors(libraryId:String, cb: (List<LibraryAuthorItem>) -> Unit) {
|
||||
Log.d(tag, "Getting series")
|
||||
getRequest("/api/libraries/$libraryId/authors", null, null) {
|
||||
val items = mutableListOf<LibraryAuthorItem>()
|
||||
if (it.has("authors")) {
|
||||
val array = it.getJSONArray("authors")
|
||||
for (i in 0 until array.length()) {
|
||||
val item = jacksonMapper.readValue<LibraryAuthorItem>(array.get(i).toString())
|
||||
items.add(item)
|
||||
}
|
||||
}else{
|
||||
Log.e(tag, "No results")
|
||||
}
|
||||
cb(items)
|
||||
}
|
||||
}
|
||||
|
||||
fun getLibraryItemsFromAuthor(libraryId:String, authorId:String, cb: (List<LibraryItem>) -> Unit) {
|
||||
Log.d(tag, "Getting author items")
|
||||
val authorIdBase64 = Base64.encodeToString(authorId.toByteArray(), Base64.DEFAULT)
|
||||
getRequest("/api/libraries/$libraryId/items?limit=1000&minified=1&filter=authors.${authorIdBase64}&sort=media.metadata.title&collapseseries=1", null, null) {
|
||||
val items = mutableListOf<LibraryItem>()
|
||||
if (it.has("results")) {
|
||||
val array = it.getJSONArray("results")
|
||||
for (i in 0 until array.length()) {
|
||||
val item = jacksonMapper.readValue<LibraryItem>(array.get(i).toString())
|
||||
if (item.collapsedSeries != null) {
|
||||
item.collapsedSeries?.libraryId = libraryId
|
||||
}
|
||||
items.add(item)
|
||||
}
|
||||
}else{
|
||||
Log.e(tag, "No results")
|
||||
}
|
||||
cb(items)
|
||||
}
|
||||
}
|
||||
|
||||
fun getLibraryCollections(libraryId:String, cb: (List<LibraryCollection>) -> Unit) {
|
||||
Log.d(tag, "Getting collections")
|
||||
getRequest("/api/libraries/$libraryId/collections?minified=1&sort=name&limit=1000", null, null) {
|
||||
val items = mutableListOf<LibraryCollection>()
|
||||
if (it.has("results")) {
|
||||
val array = it.getJSONArray("results")
|
||||
for (i in 0 until array.length()) {
|
||||
val item = jacksonMapper.readValue<LibraryCollection>(array.get(i).toString())
|
||||
items.add(item)
|
||||
}
|
||||
}
|
||||
cb(items)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllItemsInProgress(cb: (List<ItemInProgress>) -> Unit) {
|
||||
getRequest("/api/me/items-in-progress", null, null) {
|
||||
val items = mutableListOf<ItemInProgress>()
|
||||
|
|
|
|||
BIN
android/app/src/main/res/drawable-hdpi/md_account_outline.png
Normal file
BIN
android/app/src/main/res/drawable-hdpi/md_account_outline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 560 B |
BIN
android/app/src/main/res/drawable-mdpi/md_account_outline.png
Normal file
BIN
android/app/src/main/res/drawable-mdpi/md_account_outline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 387 B |
BIN
android/app/src/main/res/drawable-xhdpi/md_account_outline.png
Normal file
BIN
android/app/src/main/res/drawable-xhdpi/md_account_outline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 707 B |
BIN
android/app/src/main/res/drawable-xxhdpi/md_account_outline.png
Normal file
BIN
android/app/src/main/res/drawable-xxhdpi/md_account_outline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1 KiB |
1
android/app/src/main/res/drawable/md_account_outline.xml
Normal file
1
android/app/src/main/res/drawable/md_account_outline.xml
Normal file
|
|
@ -0,0 +1 @@
|
|||
<!-- drawable/account_outline.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#FFFFFF" android:pathData="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6M12,13C14.67,13 20,14.33 20,17V20H4V17C4,14.33 9.33,13 12,13M12,14.9C9.03,14.9 5.9,16.36 5.9,17V18.1H18.1V17C18.1,16.36 14.97,14.9 12,14.9Z" /></vector>
|
||||
Loading…
Reference in a new issue