mirror of
https://github.com/apognu/otter
synced 2025-02-17 03:10:35 +01:00
Advancement with Couchbase Lite.
This commit is contained in:
parent
1126d47a1a
commit
4e3da9160e
@ -7,13 +7,12 @@ plugins {
|
||||
id("kotlin-android")
|
||||
id("kotlin-android-extensions")
|
||||
id("kotlin-kapt")
|
||||
id("realm-android")
|
||||
|
||||
id("org.jlleitschuh.gradle.ktlint") version "8.1.0"
|
||||
id("com.gladed.androidgitversion") version "0.4.10"
|
||||
id("com.github.triplet.play") version "2.4.2"
|
||||
|
||||
kotlin("plugin.serialization") version "1.3.70"
|
||||
kotlin("plugin.serialization") version "1.3.72"
|
||||
}
|
||||
|
||||
val props = Properties().apply {
|
||||
@ -149,13 +148,15 @@ dependencies {
|
||||
|
||||
implementation("com.aliassadi:power-preference-lib:1.4.1")
|
||||
implementation("com.github.kittinunf.fuel:fuel:2.2.3")
|
||||
implementation("com.github.kittinunf.fuel:fuel-coroutines:2.1.0")
|
||||
implementation("com.github.kittinunf.fuel:fuel-android:2.1.0")
|
||||
implementation("com.github.kittinunf.fuel:fuel-gson:2.1.0")
|
||||
implementation("com.github.kittinunf.fuel:fuel-coroutines:2.2.3")
|
||||
implementation("com.github.kittinunf.fuel:fuel-android:2.2.3")
|
||||
implementation("com.github.kittinunf.fuel:fuel-kotlinx-serialization:2.2.3")
|
||||
implementation("com.squareup.picasso:picasso:2.71828")
|
||||
implementation("jp.wasabeef:picasso-transformations:2.2.1")
|
||||
|
||||
implementation("com.couchbase.lite:couchbase-lite-android:2.7.1")
|
||||
implementation("com.github.MOLO17:couchbase-lite-kotlin:1.0.0")
|
||||
|
||||
debugImplementation("com.amitshekhar.android:debug-db:1.0.6")
|
||||
|
||||
kapt("androidx.room:room-compiler:2.2.5")
|
||||
|
@ -17,7 +17,8 @@
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true">
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:replace="android:label">
|
||||
|
||||
<activity
|
||||
android:name=".activities.SplashActivity"
|
||||
|
@ -5,6 +5,7 @@ import android.content.Context
|
||||
import android.widget.SearchView
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.room.Room
|
||||
import com.couchbase.lite.*
|
||||
import com.github.apognu.otter.activities.MainActivity
|
||||
import com.github.apognu.otter.activities.SearchActivity
|
||||
import com.github.apognu.otter.adapters.*
|
||||
@ -14,10 +15,7 @@ import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.playback.MediaSession
|
||||
import com.github.apognu.otter.playback.QueueManager.Companion.factory
|
||||
import com.github.apognu.otter.repositories.*
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import com.github.apognu.otter.utils.Cache
|
||||
import com.github.apognu.otter.utils.Command
|
||||
import com.github.apognu.otter.utils.Event
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.github.apognu.otter.viewmodels.*
|
||||
import com.google.android.exoplayer2.database.ExoDatabaseProvider
|
||||
import com.google.android.exoplayer2.offline.DefaultDownloadIndex
|
||||
@ -28,7 +26,6 @@ import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvicto
|
||||
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor
|
||||
import com.google.android.exoplayer2.upstream.cache.SimpleCache
|
||||
import com.preference.PowerPreference
|
||||
import io.realm.Realm
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
@ -38,6 +35,7 @@ import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import org.koin.dsl.module
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@ -86,7 +84,7 @@ class Otter : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
Realm.init(this)
|
||||
CouchbaseLite.init(this)
|
||||
|
||||
defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
|
||||
@ -107,6 +105,13 @@ class Otter : Application() {
|
||||
}
|
||||
}
|
||||
|
||||
single {
|
||||
Database("otter", DatabaseConfiguration()).apply {
|
||||
createIndex("type", IndexBuilder.valueIndex(ValueIndexItem.expression(Expression.property("type"))))
|
||||
createIndex("order", IndexBuilder.valueIndex(ValueIndexItem.expression(Expression.property("order"))))
|
||||
}
|
||||
}
|
||||
|
||||
fragment { BrowseFragment() }
|
||||
fragment { LandscapeQueueFragment() }
|
||||
|
||||
@ -114,7 +119,7 @@ class Otter : Application() {
|
||||
|
||||
single { ArtistsRepository(get(), get()) }
|
||||
factory { (id: Int) -> ArtistTracksRepository(get(), get(), id) }
|
||||
viewModel { ArtistsViewModel(get(), get()) }
|
||||
viewModel { ArtistsViewModel(get()) }
|
||||
factory { (context: Context?, listener: ArtistsFragment.OnArtistClickListener) -> ArtistsAdapter(context, listener) }
|
||||
|
||||
factory { (id: Int?) -> AlbumsRepository(get(), get(), id) }
|
||||
@ -122,7 +127,7 @@ class Otter : Application() {
|
||||
factory { (context: Context?, adapter: AlbumsAdapter.OnAlbumClickListener) -> AlbumsAdapter(context, adapter) }
|
||||
factory { (context: Context?, adapter: AlbumsGridAdapter.OnAlbumClickListener) -> AlbumsGridAdapter(context, adapter) }
|
||||
|
||||
factory { (id: Int?) -> TracksRepository(get(), get(), id) }
|
||||
factory { (id: Int?) -> TracksRepository(get(), get(), get(), id) }
|
||||
viewModel { (id: Int) -> TracksViewModel(get { parametersOf(id) }, get(), id) }
|
||||
factory { (context: Context?, favoriteListener: TracksAdapter.OnFavoriteListener?) -> TracksAdapter(context, favoriteListener) }
|
||||
|
||||
@ -149,8 +154,6 @@ class Otter : Application() {
|
||||
single { ArtistsSearchRepository(get(), get()) }
|
||||
single { AlbumsSearchRepository(get(), get { parametersOf(null) }) }
|
||||
single { TracksSearchRepository(get(), get { parametersOf(null) }) }
|
||||
|
||||
single { Mediator(get(), get(), get()) }
|
||||
})
|
||||
}
|
||||
|
||||
@ -164,6 +167,8 @@ class Otter : Application() {
|
||||
fun deleteAllData() {
|
||||
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).clear()
|
||||
|
||||
filesDir.deleteRecursively()
|
||||
|
||||
cacheDir.listFiles()?.forEach {
|
||||
it.delete()
|
||||
}
|
||||
|
@ -25,10 +25,6 @@ class ArtistsAdapter(val context: Context?, private val listener: OnArtistClickL
|
||||
fun onClick(holder: View?, artist: Artist)
|
||||
}
|
||||
|
||||
// override fun getItemCount() = data.size
|
||||
|
||||
// override fun getItemId(position: Int) = data[position].id.toLong()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.row_artist, parent, false)
|
||||
|
||||
|
@ -9,7 +9,7 @@ import com.github.apognu.otter.fragments.*
|
||||
class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : FragmentPagerAdapter(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
var tabs = mutableListOf<Fragment>()
|
||||
|
||||
override fun getCount() = 5
|
||||
override fun getCount() = 1
|
||||
|
||||
override fun getItem(position: Int): Fragment {
|
||||
tabs.getOrNull(position)?.let {
|
||||
|
@ -50,7 +50,7 @@ class AlbumsFragment : OtterFragment<FunkwhaleAlbum, Album, AlbumsAdapter>() {
|
||||
|
||||
companion object {
|
||||
fun new(artist: Artist, _art: String? = null): AlbumsFragment {
|
||||
val art = _art ?: if (artist.albums?.isNotEmpty() == true) artist.cover() else ""
|
||||
val art = _art ?: (artist.cover() ?: "")
|
||||
|
||||
return AlbumsFragment().apply {
|
||||
arguments = bundleOf(
|
||||
|
@ -8,10 +8,7 @@ import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.observe
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import androidx.transition.Fade
|
||||
import androidx.transition.Slide
|
||||
import com.github.apognu.otter.R
|
||||
@ -33,7 +30,7 @@ class ArtistsFragment : PagedOtterFragment<FunkwhaleArtist, Artist, ArtistsAdapt
|
||||
override val adapter by inject<ArtistsAdapter> { parametersOf(context, OnArtistClickListener()) }
|
||||
override val viewModel by viewModel<ArtistsViewModel>()
|
||||
|
||||
override val liveData by lazy { viewModel.artistsPaged }
|
||||
override val liveData by lazy { viewModel.artists }
|
||||
override val viewRes = R.layout.fragment_artists
|
||||
override val recycler: RecyclerView get() = artists
|
||||
|
||||
|
@ -5,12 +5,10 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.observe
|
||||
import androidx.lifecycle.*
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.paging.map
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
@ -21,11 +19,14 @@ import com.github.apognu.otter.utils.EventBus
|
||||
import com.github.apognu.otter.utils.log
|
||||
import com.github.apognu.otter.utils.untilNetwork
|
||||
import kotlinx.android.synthetic.main.fragment_artists.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import org.koin.ext.scope
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
abstract class OtterAdapter<D, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
|
||||
var data: MutableList<D> = mutableListOf()
|
||||
@ -154,8 +155,6 @@ abstract class OtterFragment<DAO : Any, D : Any, A : OtterAdapter<D, *>> : Fragm
|
||||
}
|
||||
|
||||
abstract class PagedOtterFragment<DAO : Any, D : Any, A : PagingDataAdapter<D, *>> : Fragment() {
|
||||
open val OFFSCREEN_PAGES = 10
|
||||
|
||||
abstract val repository: Repository<DAO>
|
||||
abstract val adapter: A
|
||||
open val viewModel: ViewModel? = null
|
||||
@ -219,15 +218,4 @@ abstract class PagedOtterFragment<DAO : Any, D : Any, A : PagingDataAdapter<D, *
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun needsMoreOffscreenPages(): Boolean {
|
||||
view?.let {
|
||||
val offset = recycler.computeVerticalScrollOffset()
|
||||
val left = recycler.computeVerticalScrollRange() - recycler.height - offset
|
||||
|
||||
return left < (recycler.height * OFFSCREEN_PAGES)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -5,24 +5,29 @@ import androidx.paging.ExperimentalPagingApi
|
||||
import androidx.paging.LoadType
|
||||
import androidx.paging.PagingState
|
||||
import androidx.paging.RemoteMediator
|
||||
import androidx.room.withTransaction
|
||||
import com.github.apognu.otter.models.dao.DecoratedArtistEntity
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.models.dao.toDao
|
||||
import com.couchbase.lite.Database
|
||||
import com.github.apognu.otter.models.api.FunkwhaleArtist
|
||||
import com.github.apognu.otter.models.domain.Artist
|
||||
import com.github.apognu.otter.repositories.ArtistsRepository
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import com.github.apognu.otter.utils.Cache
|
||||
import com.github.apognu.otter.utils.log
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import com.molo17.couchbase.lite.doInBatch
|
||||
import com.molo17.couchbase.lite.from
|
||||
import com.molo17.couchbase.lite.select
|
||||
import com.molo17.couchbase.lite.where
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.KoinComponent
|
||||
|
||||
|
||||
@OptIn(ExperimentalPagingApi::class)
|
||||
class Mediator(private val context: Context, private val database: OtterDatabase, private val repository: ArtistsRepository) : RemoteMediator<Int, DecoratedArtistEntity>(), KoinComponent {
|
||||
override suspend fun load(loadType: LoadType, state: PagingState<Int, DecoratedArtistEntity>): MediatorResult {
|
||||
class Mediator(private val context: Context, private val database: Database, private val repository: ArtistsRepository) : RemoteMediator<Int, Artist>(), KoinComponent {
|
||||
override suspend fun load(loadType: LoadType, state: PagingState<Int, Artist>): MediatorResult {
|
||||
loadType.log()
|
||||
|
||||
return try {
|
||||
val key = when (loadType) {
|
||||
LoadType.REFRESH -> 1
|
||||
@ -33,20 +38,28 @@ class Mediator(private val context: Context, private val database: OtterDatabase
|
||||
}
|
||||
}
|
||||
|
||||
key.log("fetching page")
|
||||
val response = withContext(IO) {
|
||||
repository.fetch((key - 1) * AppContext.PAGE_SIZE).take(1).first()
|
||||
}
|
||||
|
||||
val response = repository.fetch((key - 1) * AppContext.PAGE_SIZE).take(1).first()
|
||||
|
||||
database.withTransaction {
|
||||
database.doInBatch {
|
||||
if (loadType == LoadType.REFRESH) {
|
||||
Cache.delete(context, "key")
|
||||
database.artists().deleteAll()
|
||||
|
||||
select("_id")
|
||||
.from(database)
|
||||
.where { "type" equalTo "artist" }
|
||||
.execute()
|
||||
.forEach { delete(getDocument(it.getString(0))) }
|
||||
}
|
||||
|
||||
Cache.set(context, "key", (key + 1).toString().toByteArray())
|
||||
|
||||
response.data.forEach {
|
||||
database.artists().insert(it.toDao())
|
||||
FunkwhaleArtist.persist(database, response.data, (key + 1) * 100)
|
||||
|
||||
listeners.forEach {
|
||||
it()
|
||||
listeners.remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,4 +68,10 @@ class Mediator(private val context: Context, private val database: OtterDatabase
|
||||
MediatorResult.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
private var listeners: MutableList<() -> Unit> = mutableListOf()
|
||||
|
||||
fun addListener(listener: () -> Unit) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
package com.github.apognu.otter.models.api
|
||||
|
||||
import com.couchbase.lite.Database
|
||||
import com.couchbase.lite.MutableDocument
|
||||
import com.github.apognu.otter.utils.toCouchbaseArray
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@ -15,6 +18,34 @@ data class FunkwhaleAlbum(
|
||||
data class Artist(val id: Int, val name: String)
|
||||
|
||||
fun cover() = cover?.urls?.original
|
||||
|
||||
companion object {
|
||||
fun persist(database: Database, albums: List<FunkwhaleAlbum>, base: Int? = null) {
|
||||
albums.forEachIndexed { index, album ->
|
||||
val doc = database.getDocument("album:${album.id}")?.toMutable() ?: MutableDocument("album:${album.id}")
|
||||
|
||||
doc.run {
|
||||
setString("type", "album")
|
||||
|
||||
setInt("id", album.id)
|
||||
setInt("artist_id", album.artist.id)
|
||||
setString("artist_name", album.artist.name)
|
||||
setString("title", album.title)
|
||||
setString("release_date", album.release_date)
|
||||
|
||||
album.cover?.urls?.original?.let { cover ->
|
||||
setString("cover", cover)
|
||||
}
|
||||
|
||||
base?.let {
|
||||
setInt("order", base + index)
|
||||
}
|
||||
|
||||
database.save(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@ -22,4 +53,3 @@ data class Covers(val urls: CoverUrls?)
|
||||
|
||||
@Serializable
|
||||
data class CoverUrls(val original: String?)
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
package com.github.apognu.otter.models.api
|
||||
|
||||
import com.couchbase.lite.Database
|
||||
import com.couchbase.lite.MutableDocument
|
||||
import com.github.apognu.otter.utils.toCouchbaseArray
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@ -16,4 +19,52 @@ data class FunkwhaleArtist(
|
||||
val cover: Covers?,
|
||||
val release_date: String?
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun persist(database: Database, artists: List<FunkwhaleArtist>, base: Int? = null) {
|
||||
artists
|
||||
// .filter { it.albums?.isNotEmpty() ?: false }
|
||||
.forEachIndexed { index, artist ->
|
||||
artist.albums?.forEach { album ->
|
||||
val albumDoc = database.getDocument("album:${album.id}")?.toMutable() ?: MutableDocument("album:${album.id}")
|
||||
|
||||
albumDoc.run {
|
||||
setString("type", "album")
|
||||
|
||||
setInt("id", album.id)
|
||||
setInt("artist_id", artist.id)
|
||||
setString("artist_name", artist.name)
|
||||
setString("title", album.title)
|
||||
setString("release_date", album.release_date)
|
||||
|
||||
album.cover?.urls?.original?.let { cover ->
|
||||
setString("cover", cover)
|
||||
}
|
||||
|
||||
database.save(this)
|
||||
}
|
||||
}
|
||||
|
||||
val artistDoc = database.getDocument("artist:${artist.id}")?.toMutable() ?: MutableDocument("artist:${artist.id}")
|
||||
|
||||
artistDoc.run {
|
||||
setString("type", "artist")
|
||||
|
||||
setInt("id", artist.id)
|
||||
setString("name", artist.name)
|
||||
setArray("albums", artist.albums?.map { it.id }.toCouchbaseArray())
|
||||
|
||||
artist.albums?.getOrNull(0)?.cover?.urls?.original?.let { cover ->
|
||||
setString("cover", cover)
|
||||
}
|
||||
|
||||
base?.let {
|
||||
setInt("order", base + index)
|
||||
}
|
||||
|
||||
database.save(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package com.github.apognu.otter.models.api
|
||||
|
||||
import com.couchbase.lite.Database
|
||||
import com.couchbase.lite.MutableDocument
|
||||
import com.github.apognu.otter.models.domain.SearchResult
|
||||
import com.github.apognu.otter.utils.toCouchbaseArray
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import kotlinx.serialization.ContextualSerialization
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -30,6 +33,63 @@ data class FunkwhaleTrack(
|
||||
album = FunkwhaleAlbum(0, FunkwhaleAlbum.Artist(0, ""), "", Covers(CoverUrls("")), ""),
|
||||
uploads = listOf(FunkwhaleUpload(download.contentId, 0, 0))
|
||||
)
|
||||
|
||||
fun persist(database: Database, tracks: List<FunkwhaleTrack>, base: Int? = null) {
|
||||
tracks.forEachIndexed { index, track ->
|
||||
val artistDoc = database.getDocument("artist:${track.artist.id}")?.toMutable() ?: MutableDocument("artist:${track.artist.id}")
|
||||
|
||||
artistDoc.run {
|
||||
setString("type", "artist")
|
||||
|
||||
setInt("id", track.artist.id)
|
||||
setString("name", track.artist.name)
|
||||
|
||||
database.save(this)
|
||||
}
|
||||
|
||||
track.album?.let { album ->
|
||||
val albumDoc = database.getDocument("album:${album.id}")?.toMutable() ?: MutableDocument("album:${album.id}")
|
||||
|
||||
albumDoc.run {
|
||||
setString("type", "album")
|
||||
|
||||
setInt("id", album.id)
|
||||
setInt("artist_id", album.artist.id)
|
||||
setString("artist_name", album.artist.name)
|
||||
setString("title", album.title)
|
||||
setString("release_date", album.release_date)
|
||||
|
||||
album.cover?.urls?.original?.let { cover ->
|
||||
setString("cover", cover)
|
||||
}
|
||||
|
||||
database.save(this)
|
||||
}
|
||||
}
|
||||
|
||||
val doc = database.getDocument("track:${track.id}")?.toMutable() ?: MutableDocument("track:${track.id}")
|
||||
|
||||
doc.run {
|
||||
setString("type", "track")
|
||||
|
||||
setInt("id", track.id)
|
||||
|
||||
track.album?.let { album ->
|
||||
setInt("albumId", album.id)
|
||||
}
|
||||
|
||||
setInt("artistId", track.artist.id)
|
||||
setString("title", track.title)
|
||||
setInt("position", track.position)
|
||||
setInt("disc_number", track.disc_number ?: 0)
|
||||
setString("copyright", track.copyright)
|
||||
setString("license", track.license)
|
||||
setString("uploads", track.uploads.getOrNull(0)?.listen_url ?: "")
|
||||
|
||||
database.save(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
@ -4,9 +4,6 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.paging.DataSource
|
||||
import androidx.room.*
|
||||
import com.github.apognu.otter.models.api.FunkwhaleArtist
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.Required
|
||||
|
||||
@Entity(tableName = "artists")
|
||||
data class ArtistEntity(
|
||||
@PrimaryKey
|
||||
@ -59,14 +56,3 @@ data class DecoratedArtistEntity(
|
||||
val album_count: Int,
|
||||
val album_cover: String?
|
||||
)
|
||||
|
||||
open class RealmArtist(
|
||||
@io.realm.annotations.PrimaryKey
|
||||
var id: Int = 0,
|
||||
@Required
|
||||
var name: String = ""
|
||||
) : RealmObject()
|
||||
|
||||
fun FunkwhaleArtist.toRealmDao() = run {
|
||||
RealmArtist(id, name)
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package com.github.apognu.otter.models.domain
|
||||
|
||||
import com.couchbase.lite.Result
|
||||
import com.github.apognu.otter.models.dao.DecoratedAlbumEntity
|
||||
|
||||
data class Album(
|
||||
@ -12,6 +13,17 @@ data class Album(
|
||||
): SearchResult {
|
||||
|
||||
companion object {
|
||||
fun from(album: Result) = album.getDictionary(0).run {
|
||||
Album(
|
||||
getInt("id"),
|
||||
getString("title") ?: "N/A",
|
||||
0,
|
||||
getString("cover"),
|
||||
getString("release_date"),
|
||||
getString("artist_name") ?: "N/A"
|
||||
)
|
||||
}
|
||||
|
||||
fun fromDecoratedEntity(entity: DecoratedAlbumEntity): Album = entity.run {
|
||||
Album(
|
||||
id,
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.github.apognu.otter.models.domain
|
||||
|
||||
import com.couchbase.lite.Result
|
||||
import com.github.apognu.otter.models.dao.DecoratedArtistEntity
|
||||
|
||||
data class Artist(
|
||||
@ -11,6 +12,15 @@ data class Artist(
|
||||
) : SearchResult {
|
||||
|
||||
companion object {
|
||||
fun from(artist: Result) = artist.getDictionary(0).run {
|
||||
Artist(
|
||||
getInt("id"),
|
||||
getString("name") ?: "N/A",
|
||||
getArray("albums")?.count() ?: 0,
|
||||
getString("cover")
|
||||
)
|
||||
}
|
||||
|
||||
fun fromDecoratedEntity(entity: DecoratedArtistEntity): Artist = entity.run {
|
||||
Artist(
|
||||
id,
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.github.apognu.otter.models.domain
|
||||
|
||||
import com.couchbase.lite.Result
|
||||
import com.github.apognu.otter.models.dao.DecoratedTrackEntity
|
||||
import com.preference.PowerPreference
|
||||
|
||||
@ -23,6 +24,17 @@ data class Track(
|
||||
) : SearchResult {
|
||||
|
||||
companion object {
|
||||
fun from(track: Result) = track.getDictionary(0).run {
|
||||
Track(
|
||||
getInt("id"),
|
||||
getString("title") ?: "N/A",
|
||||
getInt("position"),
|
||||
getString("copyright"),
|
||||
getString("license"),
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
fun fromDecoratedEntity(entity: DecoratedTrackEntity) = entity.run {
|
||||
Track(
|
||||
id,
|
||||
|
@ -2,15 +2,16 @@ package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import com.couchbase.lite.*
|
||||
import com.github.apognu.otter.models.api.FunkwhaleAlbum
|
||||
import com.github.apognu.otter.models.dao.DecoratedAlbumEntity
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.models.dao.toDao
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import com.molo17.couchbase.lite.*
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
|
||||
class AlbumsRepository(override val context: Context, private val database: OtterDatabase, artistId: Int?) : Repository<FunkwhaleAlbum>() {
|
||||
class AlbumsRepository(override val context: Context, private val couch: Database, artistId: Int?) : Repository<FunkwhaleAlbum>() {
|
||||
override val upstream: Upstream<FunkwhaleAlbum> by lazy {
|
||||
val url =
|
||||
if (artistId == null) "/api/v1/albums/?playable=true&ordering=title"
|
||||
@ -24,22 +25,36 @@ class AlbumsRepository(override val context: Context, private val database: Otte
|
||||
}
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleAlbum>): List<FunkwhaleAlbum> {
|
||||
data.forEach {
|
||||
insert(it)
|
||||
}
|
||||
FunkwhaleAlbum.persist(couch, data)
|
||||
|
||||
return super.onDataFetched(data)
|
||||
}
|
||||
|
||||
fun insert(album: FunkwhaleAlbum) = database.albums().insert(album.toDao())
|
||||
fun all() = database.albums().allDecorated()
|
||||
fun find(ids: List<Int>) = database.albums().findAllDecorated(ids)
|
||||
fun insert(albums: List<FunkwhaleAlbum>) = FunkwhaleAlbum.persist(couch, albums)
|
||||
|
||||
fun ofArtist(id: Int): LiveData<List<DecoratedAlbumEntity>> {
|
||||
fun all() =
|
||||
select(SelectResult.all())
|
||||
.from(couch)
|
||||
.where { "type" equalTo "album"}
|
||||
.asFlow()
|
||||
.asLiveData()
|
||||
|
||||
fun find(ids: List<Int>) =
|
||||
select(SelectResult.all())
|
||||
.from(couch)
|
||||
.where { ("type" equalTo "album") and (Meta.id.`in`(*ids.map { Expression.string("album:$it") }.toTypedArray())) }
|
||||
.asFlow()
|
||||
.asLiveData(GlobalScope.coroutineContext)
|
||||
|
||||
fun ofArtist(id: Int): LiveData<ResultSet> {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
fetch().collect()
|
||||
}
|
||||
|
||||
return database.albums().forArtistDecorated(id)
|
||||
return select(SelectResult.all())
|
||||
.from(couch)
|
||||
.where { ("type" equalTo "album") and ("artist_id" equalTo id) }
|
||||
.asFlow()
|
||||
.asLiveData(GlobalScope.coroutineContext)
|
||||
}
|
||||
}
|
@ -1,51 +1,64 @@
|
||||
package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import com.couchbase.lite.Database
|
||||
import com.couchbase.lite.Expression
|
||||
import com.couchbase.lite.Meta
|
||||
import com.github.apognu.otter.models.Mediator
|
||||
import com.github.apognu.otter.models.api.FunkwhaleArtist
|
||||
import com.github.apognu.otter.models.dao.DecoratedArtistEntity
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.models.dao.toDao
|
||||
import com.github.apognu.otter.models.dao.toRealmDao
|
||||
import io.realm.Realm
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import com.github.apognu.otter.models.domain.Artist
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import com.github.apognu.otter.viewmodels.CouchbasePagingSource
|
||||
import com.molo17.couchbase.lite.*
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
|
||||
class ArtistsRepository(override val context: Context, private val database: Database) : Repository<FunkwhaleArtist>() {
|
||||
private val mediator = Mediator(context, database, this)
|
||||
|
||||
class ArtistsRepository(override val context: Context, private val database: OtterDatabase) : Repository<FunkwhaleArtist>() {
|
||||
override val upstream =
|
||||
HttpUpstream(HttpUpstream.Behavior.Progressive, "/api/v1/artists/?playable=true&ordering=id", FunkwhaleArtist.serializer())
|
||||
HttpUpstream(HttpUpstream.Behavior.Progressive, "/api/v1/artists/?playable=true&ordering=name", FunkwhaleArtist.serializer())
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleArtist>): List<FunkwhaleArtist> {
|
||||
scope.launch(IO) {
|
||||
data.forEach { artist ->
|
||||
database.artists().insert(artist.toDao())
|
||||
|
||||
Realm.getDefaultInstance().executeTransaction { realm ->
|
||||
realm.insertOrUpdate(artist.toRealmDao())
|
||||
}
|
||||
|
||||
artist.albums?.forEach { album ->
|
||||
database.albums().insert(album.toDao(artist.id))
|
||||
val pager = Pager(
|
||||
// config = PagingConfig(pageSize = AppContext.PAGE_SIZE, initialLoadSize = AppContext.PAGE_SIZE * 5, prefetchDistance = 10 * AppContext.PAGE_SIZE, maxSize = 25 * AppContext.PAGE_SIZE, enablePlaceholders = false),
|
||||
config = PagingConfig(pageSize = AppContext.PAGE_SIZE, initialLoadSize = 10, prefetchDistance = 2),
|
||||
pagingSourceFactory = {
|
||||
CouchbasePagingSource(this).apply {
|
||||
mediator.addListener {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
remoteMediator = mediator
|
||||
)
|
||||
|
||||
return super.onDataFetched(data)
|
||||
}
|
||||
fun insert(artists: List<FunkwhaleArtist>) = FunkwhaleArtist.persist(database, artists)
|
||||
|
||||
fun insert(artist: FunkwhaleArtist) = database.artists().insert(artist.toDao())
|
||||
fun all(page: Int) =
|
||||
select(all())
|
||||
.from(database)
|
||||
.where { "type" equalTo "artist" }
|
||||
.orderBy { "order".ascending() }
|
||||
.limit(AppContext.PAGE_SIZE, AppContext.PAGE_SIZE * page)
|
||||
.execute()
|
||||
|
||||
fun allPaged() = database.artists().allPaged()
|
||||
fun get(id: Int) =
|
||||
select(all())
|
||||
.from(database)
|
||||
.where {
|
||||
("type" equalTo "artist") and ("_id" equalTo "artist:$id")
|
||||
}
|
||||
.limit(1)
|
||||
.execute()
|
||||
.next()
|
||||
.run { Artist.from(this) }
|
||||
|
||||
fun all(): LiveData<List<DecoratedArtistEntity>> {
|
||||
scope.launch(IO) {
|
||||
fetch().collect()
|
||||
}
|
||||
|
||||
return database.artists().allDecorated()
|
||||
}
|
||||
|
||||
fun get(id: Int) = database.artists().getDecorated(id)
|
||||
fun find(ids: List<Int>) = database.artists().findDecorated(ids)
|
||||
fun find(ids: List<Int>) =
|
||||
select(all())
|
||||
.from(database)
|
||||
.where { ("type" equalTo "artist") and (Meta.id.`in`(*ids.map { Expression.string("artist:$it") }.toTypedArray())) }
|
||||
.asFlow()
|
||||
.asLiveData(GlobalScope.coroutineContext)
|
||||
}
|
@ -8,6 +8,7 @@ import com.github.kittinunf.fuel.Fuel
|
||||
import com.github.kittinunf.fuel.core.FuelError
|
||||
import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult
|
||||
import com.github.kittinunf.fuel.coroutines.awaitObjectResult
|
||||
import com.github.kittinunf.fuel.coroutines.awaitStringResponseResult
|
||||
import com.github.kittinunf.result.Result
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.*
|
||||
@ -49,8 +50,6 @@ class HttpUpstream<D : Any>(val behavior: Behavior, private val url: String, pri
|
||||
}
|
||||
},
|
||||
{ error ->
|
||||
error.log()
|
||||
|
||||
when (error.exception) {
|
||||
is RefreshError -> EventBus.send(Event.LogOut)
|
||||
else -> emit(Repository.Response(listOf(), page, false))
|
||||
@ -67,13 +66,18 @@ class HttpUpstream<D : Any>(val behavior: Behavior, private val url: String, pri
|
||||
}
|
||||
}
|
||||
|
||||
val (_, response, result) = request.awaitObjectResponseResult(AppContext.deserializer(OtterResponseSerializer(serializer)))
|
||||
val (_, response, result) = request.awaitStringResponseResult()
|
||||
val items = AppContext.deserializer(OtterResponseSerializer(serializer)).deserialize(result.get())
|
||||
|
||||
if (response.statusCode == 401) {
|
||||
return retryGet(url)
|
||||
}
|
||||
|
||||
result
|
||||
items?.let {
|
||||
return Result.success(items)
|
||||
}
|
||||
|
||||
Result.error(FuelError.wrap(Exception("")))
|
||||
} catch (e: Exception) {
|
||||
Result.error(FuelError.wrap(e))
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.lifecycle.map
|
||||
import com.couchbase.lite.Database
|
||||
import com.github.apognu.otter.models.api.FunkwhaleAlbum
|
||||
import com.github.apognu.otter.models.api.FunkwhaleArtist
|
||||
import com.github.apognu.otter.models.api.FunkwhaleTrack
|
||||
@ -24,13 +25,13 @@ class ArtistsSearchRepository(override val context: Context?, private val reposi
|
||||
private val _ids: MutableLiveData<List<Int>> = MutableLiveData()
|
||||
|
||||
val results: LiveData<List<Artist>> = Transformations.switchMap(_ids) {
|
||||
repository.find(it).map { artists -> artists.map { artist -> Artist.fromDecoratedEntity(artist) } }
|
||||
repository.find(it).map { result ->
|
||||
result.map { artist -> Artist.from(artist) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleArtist>): List<FunkwhaleArtist> {
|
||||
data.forEach {
|
||||
repository.insert(it)
|
||||
}
|
||||
repository.insert(data)
|
||||
|
||||
ids.addAll(data.map { it.id })
|
||||
_ids.postValue(ids)
|
||||
@ -57,13 +58,13 @@ class AlbumsSearchRepository(override val context: Context?, private val reposit
|
||||
private val _ids: MutableLiveData<List<Int>> = MutableLiveData()
|
||||
|
||||
val results: LiveData<List<Album>> = Transformations.switchMap(_ids) {
|
||||
repository.find(it).map { albums -> albums.map { album -> Album.fromDecoratedEntity(album) } }
|
||||
repository.find(it).map { result ->
|
||||
result.map { album -> Album.from(album) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleAlbum>): List<FunkwhaleAlbum> {
|
||||
data.forEach {
|
||||
repository.insert(it)
|
||||
}
|
||||
repository.insert(data)
|
||||
|
||||
ids.addAll(data.map { it.id })
|
||||
_ids.postValue(ids)
|
||||
@ -91,13 +92,13 @@ class TracksSearchRepository(override val context: Context?, private val reposit
|
||||
private val _ids: MutableLiveData<List<Int>> = MutableLiveData()
|
||||
|
||||
val results: LiveData<List<Track>> = Transformations.switchMap(_ids) {
|
||||
repository.find(it).map { tracks -> tracks.map { track -> Track.fromDecoratedEntity(track) } }
|
||||
repository.find(it).map { result ->
|
||||
result.map { track -> Track.from(track) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleTrack>): List<FunkwhaleTrack> {
|
||||
data.forEach {
|
||||
repository.insert(it)
|
||||
}
|
||||
repository.insert(data)
|
||||
|
||||
ids.addAll(data.map { it.id })
|
||||
_ids.postValue(ids)
|
||||
|
@ -2,11 +2,13 @@ package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.couchbase.lite.*
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.models.api.FunkwhaleTrack
|
||||
import com.github.apognu.otter.models.dao.DecoratedTrackEntity
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.github.apognu.otter.utils.asLiveData
|
||||
import com.github.apognu.otter.utils.getMetadata
|
||||
import com.github.apognu.otter.utils.maybeNormalizeUrl
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
@ -15,7 +17,7 @@ import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class TracksRepository(override val context: Context, private val database: OtterDatabase, albumId: Int?) : Repository<FunkwhaleTrack>() {
|
||||
class TracksRepository(override val context: Context, private val database: OtterDatabase, private val couch: Database, albumId: Int?) : Repository<FunkwhaleTrack>() {
|
||||
override val upstream =
|
||||
HttpUpstream(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks/?playable=true&album=$albumId&ordering=disc_number,position", FunkwhaleTrack.serializer())
|
||||
|
||||
@ -39,18 +41,21 @@ class TracksRepository(override val context: Context, private val database: Otte
|
||||
}
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleTrack>): List<FunkwhaleTrack> = runBlocking {
|
||||
data.forEach { track ->
|
||||
database.tracks().insertWithAssocs(database.artists(), database.albums(), database.uploads(), track)
|
||||
}
|
||||
FunkwhaleTrack.persist(couch, data)
|
||||
|
||||
data.sortedWith(compareBy({ it.disc_number }, { it.position }))
|
||||
}
|
||||
|
||||
fun insert(track: FunkwhaleTrack) {
|
||||
database.tracks().insertWithAssocs(database.artists(), database.albums(), database.uploads(), track)
|
||||
}
|
||||
fun insert(tracks: List<FunkwhaleTrack>) = FunkwhaleTrack.persist(couch, tracks)
|
||||
|
||||
fun find(ids: List<Int>) = database.tracks().findAllDecorated(ids)
|
||||
fun find(ids: List<Int>) = QueryBuilder
|
||||
.select(SelectResult.all())
|
||||
.from(DataSource.database(couch))
|
||||
.where(
|
||||
Expression.property("type").equalTo(Expression.string("track"))
|
||||
.and(Meta.id.`in`(*ids.map { Expression.string("track:$it") }.toTypedArray()))
|
||||
)
|
||||
.asLiveData()
|
||||
|
||||
suspend fun ofArtistBlocking(id: Int) = database.tracks().ofArtistBlocking(id)
|
||||
|
||||
|
@ -2,6 +2,9 @@ package com.github.apognu.otter.utils
|
||||
|
||||
import android.os.Build
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.couchbase.lite.*
|
||||
import com.couchbase.lite.Array
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.fragments.BrowseFragment
|
||||
import com.github.apognu.otter.models.api.DownloadInfo
|
||||
@ -77,3 +80,32 @@ fun Request.authorize(): Request {
|
||||
}
|
||||
|
||||
fun Download.getMetadata(): DownloadInfo? = AppContext.json.parse(DownloadInfo.serializer(), String(this.request.data))
|
||||
|
||||
class DocumentSetLiveData(val query: Query) : LiveData<List<Result>>() {
|
||||
private var token: ListenerToken? = null
|
||||
|
||||
override fun onActive() {
|
||||
token = query.addChangeListener {
|
||||
postValue(query.execute().allResults())
|
||||
}
|
||||
|
||||
query.execute()
|
||||
}
|
||||
|
||||
override fun onInactive() {
|
||||
token?.let {
|
||||
query.removeChangeListener(it)
|
||||
token = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Query.asLiveData() = DocumentSetLiveData(this)
|
||||
|
||||
fun <T : Any> List<T>?.toCouchbaseArray(): Array {
|
||||
this?.let {
|
||||
return MutableArray(this)
|
||||
}
|
||||
|
||||
return MutableArray(listOf())
|
||||
}
|
@ -12,11 +12,11 @@ class AlbumsViewModel(private val repository: AlbumsRepository, private val trac
|
||||
val albums: LiveData<List<Album>> by lazy {
|
||||
if (artistId == null) {
|
||||
Transformations.map(repository.all()) {
|
||||
it.map { album -> Album.fromDecoratedEntity(album) }
|
||||
it.map { result -> Album.from(result) }
|
||||
}
|
||||
} else {
|
||||
Transformations.map(repository.ofArtist(artistId)) {
|
||||
it.map { album -> Album.fromDecoratedEntity(album) }
|
||||
it.map { result -> Album.from(result) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,37 @@
|
||||
package com.github.apognu.otter.viewmodels
|
||||
|
||||
import androidx.lifecycle.*
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.paging.map
|
||||
import com.github.apognu.otter.models.Mediator
|
||||
import com.github.apognu.otter.models.domain.Artist
|
||||
import com.github.apognu.otter.repositories.ArtistsRepository
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class ArtistsViewModel(repository: ArtistsRepository, mediator: Mediator) : ViewModel() {
|
||||
private val pager = Pager(
|
||||
config = PagingConfig(pageSize = AppContext.PAGE_SIZE, initialLoadSize = AppContext.PAGE_SIZE * 5, prefetchDistance = 10 * AppContext.PAGE_SIZE, maxSize = 25 * AppContext.PAGE_SIZE, enablePlaceholders = false),
|
||||
pagingSourceFactory = repository.allPaged().asPagingSourceFactory(),
|
||||
remoteMediator = mediator
|
||||
)
|
||||
class CouchbasePagingSource(val repository: ArtistsRepository) : PagingSource<Int, Artist>() {
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Artist> {
|
||||
return try {
|
||||
val page = params.key ?: 0
|
||||
|
||||
val artistsPaged = pager
|
||||
.flow
|
||||
.map { artists -> artists.map { Artist.fromDecoratedEntity(it) } }
|
||||
.cachedIn(viewModelScope)
|
||||
.asLiveData()
|
||||
val artists = repository.all(page).map { Artist.from(it) }
|
||||
val prevKey = if (page > 0) page - 1 else null
|
||||
val nextKey = if (artists.isNotEmpty() && artists.size == AppContext.PAGE_SIZE) page + 1 else null
|
||||
|
||||
val artists: LiveData<List<Artist>> = repository.all().map { artists ->
|
||||
artists.map { Artist.fromDecoratedEntity(it) }
|
||||
LoadResult.Page(
|
||||
data = artists,
|
||||
prevKey = prevKey,
|
||||
nextKey = nextKey
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ArtistsViewModel(repository: ArtistsRepository) : ViewModel() {
|
||||
val artists = repository.pager
|
||||
.flow
|
||||
.cachedIn(viewModelScope)
|
||||
.asLiveData(viewModelScope.coroutineContext)
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ class FavoritesViewModel(private val repository: FavoritesRepository, private va
|
||||
val ids = it.map { favorite -> favorite.track_id }
|
||||
|
||||
Transformations.map(tracksRepository.find(ids)) { tracks ->
|
||||
tracks.map { track -> Track.fromDecoratedEntity(track) }.sortedBy { it.title }
|
||||
tracks.map { track -> Track.from(track) }.sortedBy { it.title }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,8 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:4.0.0")
|
||||
classpath("com.android.tools.build:gradle:4.0.1")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72")
|
||||
classpath("io.realm:realm-gradle-plugin:10.0.0-BETA.6")
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user