mirror of
https://github.com/apognu/otter
synced 2025-02-17 11:20:34 +01:00
Added dependency injection.
This commit is contained in:
parent
567a7476f9
commit
945f227ace
@ -127,6 +127,11 @@ dependencies {
|
||||
implementation("androidx.appcompat:appcompat:1.2.0")
|
||||
implementation("androidx.core:core-ktx:1.5.0-alpha02")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha07")
|
||||
implementation("org.koin:koin-core:2.1.6")
|
||||
implementation("org.koin:koin-android:2.1.6")
|
||||
implementation("org.koin:koin-androidx-scope:2.1.6")
|
||||
implementation("org.koin:koin-androidx-viewmodel:2.1.6")
|
||||
implementation("org.koin:koin-androidx-fragment:2.1.6")
|
||||
implementation("androidx.fragment:fragment-ktx:1.2.5")
|
||||
implementation("androidx.room:room-runtime:2.2.5")
|
||||
implementation("androidx.room:room-ktx:2.2.5")
|
||||
@ -148,7 +153,6 @@ dependencies {
|
||||
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-kotlinx-serialization:2.2.3")
|
||||
implementation("com.google.code.gson:gson:2.8.6")
|
||||
implementation("com.squareup.picasso:picasso:2.71828")
|
||||
implementation("jp.wasabeef:picasso-transformations:2.2.1")
|
||||
|
||||
|
@ -1,15 +1,22 @@
|
||||
package com.github.apognu.otter
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.room.Room
|
||||
import com.github.apognu.otter.activities.MainActivity
|
||||
import com.github.apognu.otter.activities.SearchActivity
|
||||
import com.github.apognu.otter.adapters.*
|
||||
import com.github.apognu.otter.fragments.*
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.playback.MediaSession
|
||||
import com.github.apognu.otter.playback.QueueManager
|
||||
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.viewmodels.*
|
||||
import com.google.android.exoplayer2.database.ExoDatabaseProvider
|
||||
import com.google.android.exoplayer2.offline.DefaultDownloadIndex
|
||||
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory
|
||||
@ -20,7 +27,15 @@ 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
|
||||
import org.koin.android.ext.koin.androidLogger
|
||||
import org.koin.androidx.fragment.dsl.fragment
|
||||
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.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@ -36,12 +51,6 @@ class Otter : Application() {
|
||||
val eventBus: BroadcastChannel<Event> = BroadcastChannel(10)
|
||||
val commandBus: BroadcastChannel<Command> = BroadcastChannel(10)
|
||||
|
||||
val database: OtterDatabase by lazy {
|
||||
Room
|
||||
.databaseBuilder(this, OtterDatabase::class.java, "otter")
|
||||
.build()
|
||||
}
|
||||
|
||||
private val exoDatabase: ExoDatabaseProvider by lazy { ExoDatabaseProvider(this) }
|
||||
|
||||
val exoCache: SimpleCache by lazy {
|
||||
@ -65,7 +74,7 @@ class Otter : Application() {
|
||||
}
|
||||
|
||||
val exoDownloadManager: DownloadManager by lazy {
|
||||
DownloaderConstructorHelper(exoDownloadCache, QueueManager.factory(this)).run {
|
||||
DownloaderConstructorHelper(exoDownloadCache, factory(this)).run {
|
||||
DownloadManager(this@Otter, DefaultDownloadIndex(exoDatabase), DefaultDownloaderFactory(this))
|
||||
}
|
||||
}
|
||||
@ -83,6 +92,62 @@ class Otter : Application() {
|
||||
|
||||
instance = this
|
||||
|
||||
startKoin {
|
||||
androidLogger()
|
||||
androidContext(this@Otter)
|
||||
|
||||
modules(module {
|
||||
single {
|
||||
synchronized(this) {
|
||||
Room
|
||||
.databaseBuilder(get(), OtterDatabase::class.java, "otter")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
factory { MainActivity() }
|
||||
factory { SearchActivity(get(), get()) }
|
||||
|
||||
fragment { BrowseFragment() }
|
||||
fragment { LandscapeQueueFragment() }
|
||||
|
||||
single { PlayerStateViewModel(get()) }
|
||||
|
||||
single { ArtistsRepository(get(), get()) }
|
||||
factory { (id: Int) -> ArtistTracksRepository(get(), get(), id) }
|
||||
viewModel { ArtistsViewModel(get()) }
|
||||
factory { (context: Context?, listener: ArtistsFragment.OnArtistClickListener) -> ArtistsAdapter(context, listener) }
|
||||
|
||||
factory { (id: Int?) -> AlbumsRepository(get(), get(), id) }
|
||||
viewModel { (id: Int?) -> AlbumsViewModel(get { parametersOf(id) }, get { parametersOf(id) }, id) }
|
||||
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) }
|
||||
viewModel { (id: Int) -> TracksViewModel(get { parametersOf(id) }, get(), id) }
|
||||
factory { (context: Context?, favoriteListener: TracksAdapter.OnFavoriteListener?) -> TracksAdapter(context, favoriteListener) }
|
||||
|
||||
single { PlaylistsRepository(get(), get()) }
|
||||
factory { (id: Int) -> PlaylistTracksRepository(get(), get(), id) }
|
||||
viewModel { PlaylistsViewModel(get()) }
|
||||
viewModel { (id: Int) -> PlaylistViewModel(get { parametersOf(id) }, get { parametersOf(null) }, get(), id) }
|
||||
factory { (context: Context?, listener: PlaylistsAdapter.OnPlaylistClickListener) -> PlaylistsAdapter(context, listener) }
|
||||
factory { (context: Context?, listener: PlaylistTracksAdapter.OnFavoriteListener) -> PlaylistTracksAdapter(context, listener) }
|
||||
|
||||
single { FavoritesRepository(get(), get()) }
|
||||
single { FavoritedRepository(get(), get()) }
|
||||
factory { (context: Context?, listener: FavoritesAdapter.OnFavoriteListener) -> FavoritesAdapter(context, listener) }
|
||||
viewModel { FavoritesViewModel(get(), get { parametersOf(null) }) }
|
||||
|
||||
single { RadiosRepository(get(), get()) }
|
||||
factory { (context: Context?, scope: CoroutineScope, listener: RadiosAdapter.OnRadioClickListener) -> RadiosAdapter(context, scope, listener) }
|
||||
viewModel { RadiosViewModel(get()) }
|
||||
|
||||
single { (scope: CoroutineScope) -> QueueRepository(get(), scope) }
|
||||
viewModel { QueueViewModel(get(), get()) }
|
||||
})
|
||||
}
|
||||
|
||||
when (PowerPreference.getDefaultFile().getString("night_mode")) {
|
||||
"on" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
"off" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||
|
@ -14,11 +14,9 @@ import com.github.apognu.otter.fragments.LoginDialog
|
||||
import com.github.apognu.otter.models.api.Credentials
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import com.github.apognu.otter.utils.Userinfo
|
||||
import com.github.apognu.otter.utils.log
|
||||
import com.github.kittinunf.fuel.Fuel
|
||||
import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult
|
||||
import com.github.kittinunf.result.Result
|
||||
import com.google.gson.Gson
|
||||
import com.preference.PowerPreference
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
@ -131,12 +129,12 @@ class LoginActivity : AppCompatActivity() {
|
||||
is Result.Failure -> {
|
||||
dialog.dismiss()
|
||||
|
||||
val error = Gson().fromJson(String(response.data), Credentials::class.java)
|
||||
val error = AppContext.json.parse(Credentials.serializer(), String(response.data))
|
||||
|
||||
hostname_field.error = null
|
||||
username_field.error = null
|
||||
|
||||
if (error != null && error.non_field_errors?.isNotEmpty() == true) {
|
||||
if (error.non_field_errors?.isNotEmpty() == true) {
|
||||
username_field.error = error.non_field_errors[0]
|
||||
} else {
|
||||
hostname_field.error = result.error.localizedMessage
|
||||
|
@ -18,11 +18,10 @@ import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.github.apognu.otter.Otter
|
||||
import androidx.lifecycle.observe
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.fragments.*
|
||||
import com.github.apognu.otter.models.dao.RealmArtist
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.github.apognu.otter.playback.MediaControlsManager
|
||||
import com.github.apognu.otter.playback.PinService
|
||||
@ -31,16 +30,12 @@ import com.github.apognu.otter.repositories.FavoritedRepository
|
||||
import com.github.apognu.otter.repositories.FavoritesRepository
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.github.apognu.otter.viewmodels.PlayerStateViewModel
|
||||
import com.github.apognu.otter.viewmodels.QueueViewModel
|
||||
import com.github.apognu.otter.views.DisableableFrameLayout
|
||||
import com.github.kittinunf.fuel.Fuel
|
||||
import com.github.kittinunf.fuel.coroutines.awaitStringResponse
|
||||
import com.google.android.exoplayer2.Player
|
||||
import com.google.android.exoplayer2.offline.DownloadService
|
||||
import com.google.gson.Gson
|
||||
import com.preference.PowerPreference
|
||||
import com.squareup.picasso.Picasso
|
||||
import io.realm.Realm
|
||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.partial_now_playing.*
|
||||
@ -50,17 +45,20 @@ import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.stringify
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private val playerViewModel by inject<PlayerStateViewModel>()
|
||||
private val favoritesRepository by inject<FavoritesRepository>()
|
||||
private val favoritedRepository by inject<FavoritedRepository>()
|
||||
private val browseFragment by inject<BrowseFragment>()
|
||||
private var menu: Menu? = null
|
||||
|
||||
enum class ResultCode(val code: Int) {
|
||||
LOGOUT(1001)
|
||||
}
|
||||
|
||||
private val queueViewModel = QueueViewModel.get()
|
||||
private val favoritesRepository = FavoritesRepository(this)
|
||||
private val favoritedRepository = FavoritedRepository(this)
|
||||
private var menu: Menu? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@ -70,17 +68,17 @@ class MainActivity : AppCompatActivity() {
|
||||
setSupportActionBar(appbar)
|
||||
|
||||
when (intent.action) {
|
||||
MediaControlsManager.NOTIFICATION_ACTION_OPEN_QUEUE.toString() -> launchDialog(QueueFragment())
|
||||
MediaControlsManager.NOTIFICATION_ACTION_OPEN_QUEUE.toString() -> launchDialog(inject<QueueFragment>().value)
|
||||
}
|
||||
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.container, BrowseFragment())
|
||||
.replace(R.id.container, browseFragment)
|
||||
.commit()
|
||||
|
||||
watchEventBus()
|
||||
|
||||
PlayerStateViewModel.get().isPlaying.observe(this) { isPlaying ->
|
||||
playerViewModel.isPlaying.observe(this) { isPlaying ->
|
||||
when (isPlaying) {
|
||||
true -> {
|
||||
now_playing_toggle.icon = getDrawable(R.drawable.pause)
|
||||
@ -94,18 +92,18 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
PlayerStateViewModel.get().isBuffering.observe(this) { isBuffering ->
|
||||
playerViewModel.isBuffering.observe(this) { isBuffering ->
|
||||
when (isBuffering) {
|
||||
true -> now_playing_buffering.visibility = View.VISIBLE
|
||||
false -> now_playing_buffering.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
PlayerStateViewModel.get().track.observe(this) { track ->
|
||||
playerViewModel.track.observe(this) { track ->
|
||||
refreshCurrentTrack(track)
|
||||
}
|
||||
|
||||
PlayerStateViewModel.get().position.observe(this) { (current, duration, percent) ->
|
||||
playerViewModel.position.observe(this) { (current, duration, percent) ->
|
||||
now_playing_progress.progress = percent
|
||||
now_playing_details_progress.progress = percent
|
||||
|
||||
@ -187,7 +185,7 @@ class MainActivity : AppCompatActivity() {
|
||||
})
|
||||
|
||||
landscape_queue?.let {
|
||||
supportFragmentManager.beginTransaction().replace(R.id.landscape_queue, LandscapeQueueFragment()).commit()
|
||||
supportFragmentManager.beginTransaction().replace(R.id.landscape_queue, inject<LandscapeQueueFragment>().value).commit()
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,7 +229,7 @@ class MainActivity : AppCompatActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
launchFragment(BrowseFragment())
|
||||
launchFragment(browseFragment)
|
||||
}
|
||||
|
||||
R.id.nav_queue -> launchDialog(QueueFragment())
|
||||
@ -573,7 +571,7 @@ class MainActivity : AppCompatActivity() {
|
||||
.post(mustNormalizeUrl("/api/v1/history/listenings/"))
|
||||
.authorize()
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Gson().toJson(mapOf("track" to track.id)))
|
||||
.body(AppContext.json.stringify(mapOf("track" to track.id)))
|
||||
.awaitStringResponse()
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
|
@ -5,36 +5,39 @@ import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.adapters.SearchAdapter
|
||||
import com.github.apognu.otter.fragments.AlbumsFragment
|
||||
import com.github.apognu.otter.fragments.ArtistsFragment
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.models.dao.toDao
|
||||
import com.github.apognu.otter.repositories.*
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.github.apognu.otter.models.domain.Album
|
||||
import com.github.apognu.otter.models.domain.Artist
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.github.apognu.otter.repositories.AlbumsSearchRepository
|
||||
import com.github.apognu.otter.repositories.ArtistsSearchRepository
|
||||
import com.github.apognu.otter.repositories.FavoritesRepository
|
||||
import com.github.apognu.otter.repositories.TracksSearchRepository
|
||||
import com.github.apognu.otter.utils.Event
|
||||
import com.github.apognu.otter.utils.EventBus
|
||||
import com.github.apognu.otter.utils.untilNetwork
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import kotlinx.android.synthetic.main.activity_search.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.net.URLEncoder
|
||||
import java.util.*
|
||||
|
||||
class SearchActivity : AppCompatActivity() {
|
||||
class SearchActivity(private val database: OtterDatabase, private val favoritesRepository: FavoritesRepository) : AppCompatActivity() {
|
||||
private lateinit var adapter: SearchAdapter
|
||||
|
||||
lateinit var artistsRepository: ArtistsSearchRepository
|
||||
lateinit var albumsRepository: AlbumsSearchRepository
|
||||
lateinit var tracksRepository: TracksSearchRepository
|
||||
|
||||
lateinit var favoritesRepository: FavoritesRepository
|
||||
|
||||
var done = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -64,7 +67,6 @@ class SearchActivity : AppCompatActivity() {
|
||||
artistsRepository = ArtistsSearchRepository(this@SearchActivity, "")
|
||||
albumsRepository = AlbumsSearchRepository(this@SearchActivity, "")
|
||||
tracksRepository = TracksSearchRepository(this@SearchActivity, "")
|
||||
favoritesRepository = FavoritesRepository(this@SearchActivity)
|
||||
|
||||
search.setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(rawQuery: String?): Boolean {
|
||||
@ -92,7 +94,7 @@ class SearchActivity : AppCompatActivity() {
|
||||
done++
|
||||
|
||||
artists.forEach {
|
||||
Otter.get().database.artists().run {
|
||||
database.artists().run {
|
||||
insert(it.toDao())
|
||||
|
||||
adapter.artists.add(Artist.fromDecoratedEntity(getDecoratedBlocking(it.id)))
|
||||
@ -108,7 +110,7 @@ class SearchActivity : AppCompatActivity() {
|
||||
done++
|
||||
|
||||
albums.forEach {
|
||||
Otter.get().database.albums().run {
|
||||
database.albums().run {
|
||||
insert(it.toDao())
|
||||
|
||||
adapter.albums.add(Album.fromDecoratedEntity(getDecoratedBlocking(it.id)))
|
||||
@ -124,8 +126,8 @@ class SearchActivity : AppCompatActivity() {
|
||||
done++
|
||||
|
||||
tracks.forEach {
|
||||
Otter.get().database.tracks().run {
|
||||
insertWithAssocs(it)
|
||||
database.tracks().run {
|
||||
insertWithAssocs(database.artists(), database.albums(), database.uploads(), it)
|
||||
|
||||
adapter.tracks.add(Track.fromDecoratedEntity(getDecoratedBlocking(it.id)))
|
||||
}
|
||||
|
@ -41,10 +41,7 @@ class ArtistsAdapter(val context: Context?, private val listener: OnArtistClickL
|
||||
.into(holder.art)
|
||||
|
||||
holder.name.text = artist.name
|
||||
|
||||
context?.let {
|
||||
holder.albums.text = context.resources.getQuantityString(R.plurals.album_count, artist.album_count, artist.album_count)
|
||||
}
|
||||
holder.albums.text = context?.resources?.getQuantityString(R.plurals.album_count, artist.album_count, artist.album_count) ?: ""
|
||||
}
|
||||
|
||||
inner class ViewHolder(view: View, private val listener: OnArtistClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
||||
|
@ -3,17 +3,17 @@ package com.github.apognu.otter.adapters
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Build
|
||||
import android.view.*
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.fragments.OtterAdapter
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.squareup.picasso.Picasso
|
||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||
import kotlinx.android.synthetic.main.row_track.view.*
|
||||
@ -26,8 +26,6 @@ class PlaylistTracksAdapter(private val context: Context?, private val favoriteL
|
||||
|
||||
private lateinit var touchHelper: ItemTouchHelper
|
||||
|
||||
var currentTrack: Track? = null
|
||||
|
||||
override fun getItemCount() = data.size
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
@ -68,15 +66,11 @@ class PlaylistTracksAdapter(private val context: Context?, private val favoriteL
|
||||
|
||||
context?.let {
|
||||
holder.itemView.background = context.getDrawable(R.drawable.ripple)
|
||||
}
|
||||
|
||||
if (track == currentTrack) {
|
||||
context?.let {
|
||||
if (track.current) {
|
||||
holder.itemView.background = context.getDrawable(R.drawable.current)
|
||||
}
|
||||
}
|
||||
|
||||
context?.let {
|
||||
when (track.favorite) {
|
||||
true -> holder.favorite.setColorFilter(context.getColor(R.color.colorFavorite))
|
||||
false -> holder.favorite.setColorFilter(context.getColor(R.color.colorSelected))
|
||||
@ -85,6 +79,23 @@ class PlaylistTracksAdapter(private val context: Context?, private val favoriteL
|
||||
holder.favorite.setOnClickListener {
|
||||
favoriteListener?.onToggleFavorite(track.id, !track.favorite)
|
||||
}
|
||||
|
||||
when (track.cached || track.downloaded) {
|
||||
true -> holder.title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.downloaded, 0, 0, 0)
|
||||
false -> holder.title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
if (track.cached && !track.downloaded) {
|
||||
holder.title.compoundDrawables.forEach {
|
||||
it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.cached), PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
}
|
||||
|
||||
if (track.downloaded) {
|
||||
holder.title.compoundDrawables.forEach {
|
||||
it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.downloaded), PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
holder.actions.setOnClickListener {
|
||||
@ -96,7 +107,7 @@ class PlaylistTracksAdapter(private val context: Context?, private val favoriteL
|
||||
when (it.itemId) {
|
||||
R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(track)))
|
||||
R.id.track_play_next -> CommandBus.send(Command.PlayNext(track))
|
||||
R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(track))
|
||||
R.id.track_pin -> CommandBus.send(Command.PinTrack(track))
|
||||
}
|
||||
|
||||
true
|
||||
|
@ -6,8 +6,8 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.models.dao.RadioEntity
|
||||
import com.github.apognu.otter.fragments.OtterAdapter
|
||||
import com.github.apognu.otter.models.dao.RadioEntity
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import com.github.apognu.otter.utils.Event
|
||||
import com.github.apognu.otter.utils.EventBus
|
||||
|
@ -57,6 +57,7 @@ class TracksAdapter(private val context: Context?, private val favoriteListener:
|
||||
.maybeLoad(maybeNormalizeUrl(track.album?.cover()))
|
||||
.fit()
|
||||
.transform(RoundedCornersTransformation(8, 0))
|
||||
.placeholder(R.drawable.cover)
|
||||
.into(holder.cover)
|
||||
|
||||
holder.title.text = track.title
|
||||
|
@ -8,7 +8,6 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
@ -18,27 +17,34 @@ import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.activities.MainActivity
|
||||
import com.github.apognu.otter.adapters.AlbumsAdapter
|
||||
import com.github.apognu.otter.models.api.FunkwhaleAlbum
|
||||
import com.github.apognu.otter.models.domain.Album
|
||||
import com.github.apognu.otter.models.domain.Artist
|
||||
import com.github.apognu.otter.repositories.AlbumsRepository
|
||||
import com.github.apognu.otter.repositories.ArtistTracksRepository
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.github.apognu.otter.models.domain.Album
|
||||
import com.github.apognu.otter.viewmodels.AlbumsViewModel
|
||||
import com.github.apognu.otter.models.domain.Artist
|
||||
import com.squareup.picasso.Picasso
|
||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||
import kotlinx.android.synthetic.main.fragment_albums.*
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class AlbumsFragment : LiveOtterFragment<FunkwhaleAlbum, Album, AlbumsAdapter>() {
|
||||
override lateinit var liveData: LiveData<List<Album>>
|
||||
override val repository by inject<AlbumsRepository> { parametersOf(null) }
|
||||
override val adapter by inject<AlbumsAdapter> { parametersOf(context, OnAlbumClickListener()) }
|
||||
override val viewModel by viewModel<AlbumsViewModel> { parametersOf(artistId) }
|
||||
override val liveData by lazy { viewModel.albums }
|
||||
|
||||
override val viewRes = R.layout.fragment_albums
|
||||
override val recycler: RecyclerView get() = albums
|
||||
override val alwaysRefresh = false
|
||||
|
||||
private lateinit var artistTracksRepository: ArtistTracksRepository
|
||||
private val artistTracksRepository by inject<ArtistTracksRepository> { parametersOf(artistId) }
|
||||
|
||||
var artistId = 0
|
||||
private var artistId = 0
|
||||
var artistName = ""
|
||||
var artistArt = ""
|
||||
|
||||
@ -93,19 +99,13 @@ class AlbumsFragment : LiveOtterFragment<FunkwhaleAlbum, Album, AlbumsAdapter>()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
arguments?.apply {
|
||||
artistId = getInt("artistId")
|
||||
artistName = getString("artistName") ?: ""
|
||||
artistArt = getString("artistArt") ?: ""
|
||||
}
|
||||
|
||||
liveData = AlbumsViewModel(artistId).albums
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
adapter = AlbumsAdapter(context, OnAlbumClickListener())
|
||||
repository = AlbumsRepository(context, artistId)
|
||||
artistTracksRepository = ArtistTracksRepository(context, artistId)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@ -142,7 +142,7 @@ class AlbumsFragment : LiveOtterFragment<FunkwhaleAlbum, Album, AlbumsAdapter>()
|
||||
play.isClickable = true
|
||||
|
||||
lifecycleScope.launch(IO) {
|
||||
AlbumsViewModel(artistId).tracks().also {
|
||||
viewModel.tracks().also {
|
||||
CommandBus.send(Command.ReplaceQueue(it.shuffled()))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.github.apognu.otter.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
@ -11,26 +10,26 @@ import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.activities.MainActivity
|
||||
import com.github.apognu.otter.adapters.AlbumsGridAdapter
|
||||
import com.github.apognu.otter.models.api.FunkwhaleAlbum
|
||||
import com.github.apognu.otter.models.domain.Album
|
||||
import com.github.apognu.otter.repositories.AlbumsRepository
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import com.github.apognu.otter.models.domain.Album
|
||||
import com.github.apognu.otter.viewmodels.AlbumsViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_albums_grid.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class AlbumsGridFragment : LiveOtterFragment<FunkwhaleAlbum, Album, AlbumsGridAdapter>() {
|
||||
override val liveData = AlbumsViewModel().albums
|
||||
override val repository by inject<AlbumsRepository> { parametersOf(null) }
|
||||
override val adapter by inject<AlbumsGridAdapter> { parametersOf(context, OnAlbumClickListener()) }
|
||||
override val viewModel by viewModel<AlbumsViewModel> { parametersOf(null) }
|
||||
override val liveData by lazy { viewModel.albums }
|
||||
|
||||
override val viewRes = R.layout.fragment_albums_grid
|
||||
override val recycler: RecyclerView get() = albums
|
||||
override val layoutManager get() = GridLayoutManager(context, 3)
|
||||
override val alwaysRefresh = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
adapter = AlbumsGridAdapter(context, OnAlbumClickListener())
|
||||
repository = AlbumsRepository(context)
|
||||
}
|
||||
|
||||
inner class OnAlbumClickListener : AlbumsGridAdapter.OnAlbumClickListener {
|
||||
override fun onClick(view: View?, album: Album) {
|
||||
(context as? MainActivity)?.let { activity ->
|
||||
|
@ -18,15 +18,22 @@ import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.activities.MainActivity
|
||||
import com.github.apognu.otter.adapters.ArtistsAdapter
|
||||
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.onViewPager
|
||||
import com.github.apognu.otter.models.domain.Artist
|
||||
import com.github.apognu.otter.viewmodels.ArtistsViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_artists.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class ArtistsFragment : LiveOtterFragment<FunkwhaleArtist, Artist, ArtistsAdapter>() {
|
||||
override val liveData = ArtistsViewModel.get().artists
|
||||
override val repository by inject<ArtistsRepository>()
|
||||
override val adapter by inject<ArtistsAdapter> { parametersOf(context, OnArtistClickListener()) }
|
||||
override val viewModel by viewModel<ArtistsViewModel>()
|
||||
|
||||
override val liveData by lazy { viewModel.artists }
|
||||
override val viewRes = R.layout.fragment_artists
|
||||
override val recycler: RecyclerView get() = artists
|
||||
|
||||
@ -66,13 +73,6 @@ class ArtistsFragment : LiveOtterFragment<FunkwhaleArtist, Artist, ArtistsAdapte
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
adapter = ArtistsAdapter(context, OnArtistClickListener())
|
||||
repository = ArtistsRepository(context)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_artists, container, false)
|
||||
}
|
||||
|
@ -13,26 +13,32 @@ import com.github.apognu.otter.repositories.TracksRepository
|
||||
import com.github.apognu.otter.utils.Command
|
||||
import com.github.apognu.otter.utils.CommandBus
|
||||
import com.github.apognu.otter.utils.EventBus
|
||||
import com.github.apognu.otter.viewmodels.FavoritesViewModel
|
||||
import com.github.apognu.otter.viewmodels.PlayerStateViewModel
|
||||
import com.github.apognu.otter.viewmodels.TracksViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_favorites.*
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class FavoritesFragment : LiveOtterFragment<FunkwhaleTrack, Track, FavoritesAdapter>() {
|
||||
override val liveData = TracksViewModel(0).favorites
|
||||
override val repository by inject<FavoritesRepository>()
|
||||
override val adapter by inject<FavoritesAdapter> { parametersOf(context, FavoriteListener()) }
|
||||
override val viewModel by viewModel<FavoritesViewModel>()
|
||||
override val liveData by lazy { viewModel.favorites }
|
||||
|
||||
override val viewRes = R.layout.fragment_favorites
|
||||
override val recycler: RecyclerView get() = favorites
|
||||
override val alwaysRefresh = false
|
||||
|
||||
private val playerViewModel by inject<PlayerStateViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
adapter = FavoritesAdapter(context, FavoriteListener())
|
||||
repository = FavoritesRepository(context)
|
||||
|
||||
PlayerStateViewModel.get().track.observe(this) { refreshCurrentTrack(it) }
|
||||
playerViewModel.track.observe(this) { refreshCurrentTrack(it) }
|
||||
|
||||
watchEventBus()
|
||||
}
|
||||
|
@ -14,19 +14,15 @@ import com.github.apognu.otter.repositories.FavoritesRepository
|
||||
import com.github.apognu.otter.viewmodels.QueueViewModel
|
||||
import kotlinx.android.synthetic.main.partial_queue.*
|
||||
import kotlinx.android.synthetic.main.partial_queue.view.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class LandscapeQueueFragment : Fragment() {
|
||||
private val viewModel by viewModel<QueueViewModel>()
|
||||
private val favoritesRepository by inject<FavoritesRepository>()
|
||||
|
||||
private var adapter: TracksAdapter? = null
|
||||
|
||||
private val viewModel = QueueViewModel.get()
|
||||
lateinit var favoritesRepository: FavoritesRepository
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
favoritesRepository = FavoritesRepository(context)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
viewModel.queue.observe(viewLifecycleOwner) {
|
||||
refresh(it)
|
||||
|
@ -6,6 +6,7 @@ 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.recyclerview.widget.LinearLayoutManager
|
||||
@ -34,20 +35,22 @@ abstract class OtterAdapter<D, VH : RecyclerView.ViewHolder> : RecyclerView.Adap
|
||||
abstract override fun getItemId(position: Int): Long
|
||||
}
|
||||
|
||||
abstract class LiveOtterFragment<D : Any, DAO : Any, A : OtterAdapter<DAO, *>> : Fragment() {
|
||||
abstract class LiveOtterFragment<DAO : Any, D : Any, A : OtterAdapter<D, *>> : Fragment() {
|
||||
companion object {
|
||||
const val OFFSCREEN_PAGES = 20
|
||||
}
|
||||
|
||||
abstract val liveData: LiveData<List<DAO>>
|
||||
abstract val repository: Repository<DAO>
|
||||
abstract val adapter: A
|
||||
open val viewModel: ViewModel? = null
|
||||
|
||||
abstract val liveData: LiveData<List<D>>
|
||||
abstract val viewRes: Int
|
||||
abstract val recycler: RecyclerView
|
||||
|
||||
open val layoutManager: RecyclerView.LayoutManager get() = LinearLayoutManager(context)
|
||||
open val alwaysRefresh = true
|
||||
|
||||
lateinit var repository: Repository<D>
|
||||
lateinit var adapter: A
|
||||
|
||||
private var moreLoading = false
|
||||
private var listener: Job? = null
|
||||
|
||||
@ -58,6 +61,13 @@ abstract class LiveOtterFragment<D : Any, DAO : Any, A : OtterAdapter<DAO, *>> :
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
liveData.observe(viewLifecycleOwner) {
|
||||
onDataUpdated(it)
|
||||
|
||||
adapter.data = it.toMutableList()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
recycler.layoutManager = layoutManager
|
||||
(recycler.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
|
||||
recycler.adapter = adapter
|
||||
@ -88,17 +98,6 @@ abstract class LiveOtterFragment<D : Any, DAO : Any, A : OtterAdapter<DAO, *>> :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetch()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
liveData.observe(this) {
|
||||
adapter.data = it.toMutableList()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@ -109,7 +108,8 @@ abstract class LiveOtterFragment<D : Any, DAO : Any, A : OtterAdapter<DAO, *>> :
|
||||
}
|
||||
}
|
||||
|
||||
open fun onDataFetched(data: List<D>) {}
|
||||
open fun onDataFetched(data: List<DAO>) {}
|
||||
open fun onDataUpdated(data: List<D>?) {}
|
||||
|
||||
private fun fetch(size: Int = 0) {
|
||||
moreLoading = true
|
||||
|
@ -5,38 +5,41 @@ import android.view.Gravity
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.observe
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.adapters.PlaylistTracksAdapter
|
||||
import com.github.apognu.otter.models.api.FunkwhalePlaylistTrack
|
||||
import com.github.apognu.otter.models.dao.PlaylistEntity
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.github.apognu.otter.repositories.FavoritesRepository
|
||||
import com.github.apognu.otter.repositories.PlaylistTracksRepository
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.github.apognu.otter.viewmodels.PlayerStateViewModel
|
||||
import com.github.apognu.otter.viewmodels.PlaylistViewModel
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.squareup.picasso.Picasso
|
||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||
import kotlinx.android.synthetic.main.fragment_tracks.*
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class PlaylistTracksFragment : LiveOtterFragment<FunkwhalePlaylistTrack, Track, PlaylistTracksAdapter>() {
|
||||
override lateinit var liveData: LiveData<List<Track>>
|
||||
private val favoritesRepository by inject<FavoritesRepository>()
|
||||
override val repository by inject<PlaylistTracksRepository> { parametersOf(playlistId) }
|
||||
override val adapter by inject<PlaylistTracksAdapter> { parametersOf(context, FavoriteListener()) }
|
||||
override val viewModel by viewModel<PlaylistViewModel> { parametersOf(playlistId) }
|
||||
override val liveData by lazy { viewModel.tracks }
|
||||
|
||||
override val viewRes = R.layout.fragment_tracks
|
||||
override val recycler: RecyclerView get() = tracks
|
||||
|
||||
lateinit var favoritesRepository: FavoritesRepository
|
||||
|
||||
var playlistId = 0
|
||||
var playlistName = ""
|
||||
|
||||
companion object {
|
||||
fun new(playlist: PlaylistEntity): PlaylistTracksFragment {
|
||||
fun new(playlist: PlaylistEntity, favoritesRepository: FavoritesRepository): PlaylistTracksFragment {
|
||||
return PlaylistTracksFragment().apply {
|
||||
arguments = bundleOf(
|
||||
"playlistId" to playlist.id,
|
||||
@ -47,23 +50,12 @@ class PlaylistTracksFragment : LiveOtterFragment<FunkwhalePlaylistTrack, Track,
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
arguments?.apply {
|
||||
playlistId = getInt("playlistId")
|
||||
playlistName = getString("playlistName") ?: "N/A"
|
||||
}
|
||||
|
||||
liveData = PlaylistViewModel(playlistId).tracks
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
adapter = PlaylistTracksAdapter(context, FavoriteListener())
|
||||
repository = PlaylistTracksRepository(context, playlistId)
|
||||
favoritesRepository = FavoritesRepository(context)
|
||||
|
||||
PlayerStateViewModel.get().track.observe(this) { track ->
|
||||
adapter.currentTrack = track
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.github.apognu.otter.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@ -11,23 +10,26 @@ import com.github.apognu.otter.activities.MainActivity
|
||||
import com.github.apognu.otter.adapters.PlaylistsAdapter
|
||||
import com.github.apognu.otter.models.api.FunkwhalePlaylist
|
||||
import com.github.apognu.otter.models.dao.PlaylistEntity
|
||||
import com.github.apognu.otter.repositories.FavoritesRepository
|
||||
import com.github.apognu.otter.repositories.PlaylistsRepository
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import com.github.apognu.otter.viewmodels.PlaylistsViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_playlists.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class PlaylistsFragment : LiveOtterFragment<FunkwhalePlaylist, PlaylistEntity, PlaylistsAdapter>() {
|
||||
override val liveData = PlaylistsViewModel().playlists
|
||||
override val repository by inject<PlaylistsRepository>()
|
||||
override val adapter by inject<PlaylistsAdapter> { parametersOf(context, OnPlaylistClickListener()) }
|
||||
override val viewModel by viewModel<PlaylistsViewModel>()
|
||||
override val liveData by lazy { viewModel.playlists }
|
||||
|
||||
override val viewRes = R.layout.fragment_playlists
|
||||
override val recycler: RecyclerView get() = playlists
|
||||
override val alwaysRefresh = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
adapter = PlaylistsAdapter(context, OnPlaylistClickListener())
|
||||
repository = PlaylistsRepository(context)
|
||||
}
|
||||
private val favoritesRepository by inject<FavoritesRepository>()
|
||||
|
||||
inner class OnPlaylistClickListener : PlaylistsAdapter.OnPlaylistClickListener {
|
||||
override fun onClick(holder: View?, playlist: PlaylistEntity) {
|
||||
@ -41,7 +43,7 @@ class PlaylistsFragment : LiveOtterFragment<FunkwhalePlaylist, PlaylistEntity, P
|
||||
}
|
||||
}
|
||||
|
||||
val fragment = PlaylistTracksFragment.new(playlist).apply {
|
||||
val fragment = PlaylistTracksFragment.new(playlist, favoritesRepository).apply {
|
||||
enterTransition = Slide().apply {
|
||||
duration = AppContext.TRANSITION_DURATION
|
||||
interpolator = AccelerateDecelerateInterpolator()
|
||||
|
@ -21,18 +21,18 @@ import kotlinx.android.synthetic.main.fragment_queue.*
|
||||
import kotlinx.android.synthetic.main.fragment_queue.view.*
|
||||
import kotlinx.android.synthetic.main.partial_queue.*
|
||||
import kotlinx.android.synthetic.main.partial_queue.view.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class QueueFragment : BottomSheetDialogFragment() {
|
||||
private var adapter: TracksAdapter? = null
|
||||
private val viewModel by viewModel<QueueViewModel>()
|
||||
private val favoritesRepository by inject<FavoritesRepository>()
|
||||
|
||||
private val viewModel = QueueViewModel.get()
|
||||
lateinit var favoritesRepository: FavoritesRepository
|
||||
private var adapter: TracksAdapter? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
favoritesRepository = FavoritesRepository(context)
|
||||
|
||||
setStyle(DialogFragment.STYLE_NORMAL, R.style.AppTheme_FloatingBottomSheet)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.github.apognu.otter.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.core.view.forEach
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@ -15,20 +14,19 @@ import kotlinx.android.synthetic.main.fragment_radios.*
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class RadiosFragment : LiveOtterFragment<FunkwhaleRadio, RadioEntity, RadiosAdapter>() {
|
||||
override val liveData = RadiosViewModel().radios
|
||||
override val repository by inject<RadiosRepository>()
|
||||
override val adapter by inject<RadiosAdapter> { parametersOf(context, lifecycleScope, RadioClickListener()) }
|
||||
override val viewModel by inject<RadiosViewModel>()
|
||||
override val liveData by lazy { viewModel.radios }
|
||||
|
||||
override val viewRes = R.layout.fragment_radios
|
||||
override val recycler: RecyclerView get() = radios
|
||||
override val alwaysRefresh = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
adapter = RadiosAdapter(context, lifecycleScope, RadioClickListener())
|
||||
repository = RadiosRepository(context)
|
||||
}
|
||||
|
||||
inner class RadioClickListener : RadiosAdapter.OnRadioClickListener {
|
||||
override fun onClick(holder: RadiosAdapter.ViewHolder, radio: RadioEntity) {
|
||||
holder.spin()
|
||||
@ -37,7 +35,6 @@ class RadiosFragment : LiveOtterFragment<FunkwhaleRadio, RadioEntity, RadiosAdap
|
||||
it.isClickable = false
|
||||
}
|
||||
|
||||
// TOBEREDONE
|
||||
CommandBus.send(Command.PlayRadio(radio))
|
||||
|
||||
lifecycleScope.launch(Main) {
|
||||
|
@ -6,9 +6,7 @@ import android.view.View
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.observe
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.adapters.TracksAdapter
|
||||
@ -19,7 +17,7 @@ import com.github.apognu.otter.repositories.FavoritedRepository
|
||||
import com.github.apognu.otter.repositories.FavoritesRepository
|
||||
import com.github.apognu.otter.repositories.TracksRepository
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.github.apognu.otter.viewmodels.*
|
||||
import com.github.apognu.otter.viewmodels.TracksViewModel
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import com.preference.PowerPreference
|
||||
import com.squareup.picasso.Picasso
|
||||
@ -30,19 +28,22 @@ import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class TracksFragment : LiveOtterFragment<FunkwhaleTrack, Track, TracksAdapter>() {
|
||||
override lateinit var liveData: LiveData<List<Track>>
|
||||
override val repository by inject<TracksRepository> { parametersOf(albumId) }
|
||||
override val adapter by inject<TracksAdapter> { parametersOf(context, FavoriteListener()) }
|
||||
override val viewModel by viewModel<TracksViewModel> { parametersOf(albumId) }
|
||||
override val liveData by lazy { viewModel.tracks }
|
||||
|
||||
override val viewRes = R.layout.fragment_tracks
|
||||
override val recycler: RecyclerView get() = tracks
|
||||
|
||||
lateinit var favoritesRepository: FavoritesRepository
|
||||
lateinit var favoritedRepository: FavoritedRepository
|
||||
private val favoritesRepository by inject<FavoritesRepository>()
|
||||
|
||||
private var albumId = 0
|
||||
private var albumArtist = ""
|
||||
private var albumTitle = ""
|
||||
private var albumCover = ""
|
||||
|
||||
companion object {
|
||||
fun new(album: Album): TracksFragment {
|
||||
@ -53,35 +54,12 @@ class TracksFragment : LiveOtterFragment<FunkwhaleTrack, Track, TracksAdapter>()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
arguments?.apply {
|
||||
albumId = getInt("albumId")
|
||||
}
|
||||
|
||||
liveData = TracksViewModel(albumId).tracks
|
||||
|
||||
AlbumViewModel(albumId).album.observe(this) {
|
||||
title.text = it.title
|
||||
|
||||
Picasso.get()
|
||||
.maybeLoad(maybeNormalizeUrl(it.cover))
|
||||
.noFade()
|
||||
.fit()
|
||||
.centerCrop()
|
||||
.transform(RoundedCornersTransformation(16, 0))
|
||||
.into(cover)
|
||||
|
||||
ArtistViewModel(it.artist_id).artist.observe(this) {
|
||||
artist.text = it.name
|
||||
}
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
adapter = TracksAdapter(context, FavoriteListener())
|
||||
repository = TracksRepository(context, albumId)
|
||||
favoritesRepository = FavoritesRepository(context)
|
||||
favoritedRepository = FavoritedRepository(context)
|
||||
|
||||
watchEventBus()
|
||||
}
|
||||
|
||||
@ -156,6 +134,21 @@ class TracksFragment : LiveOtterFragment<FunkwhaleTrack, Track, TracksAdapter>()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDataUpdated(data: List<Track>?) {
|
||||
data?.let {
|
||||
title.text = data.getOrNull(0)?.album?.title
|
||||
artist.text = data.getOrNull(0)?.artist?.name
|
||||
|
||||
Picasso.get()
|
||||
.maybeLoad(data.getOrNull(0)?.album?.cover)
|
||||
.noFade()
|
||||
.fit()
|
||||
.centerCrop()
|
||||
.transform(RoundedCornersTransformation(16, 0))
|
||||
.into(cover)
|
||||
}
|
||||
}
|
||||
|
||||
private fun watchEventBus() {
|
||||
lifecycleScope.launch(IO) {
|
||||
EventBus.get().collect { message ->
|
||||
|
@ -21,7 +21,7 @@ class OtterResponseSerializer<T : Any>(private val dataSerializer: KSerializer<T
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Credentials(val token: String, val non_field_errors: List<String>? = null)
|
||||
data class Credentials(val token: String? = null, val non_field_errors: List<String>? = null)
|
||||
|
||||
@Serializable
|
||||
data class User(val full_username: String)
|
@ -2,6 +2,7 @@ package com.github.apognu.otter.models.api
|
||||
|
||||
import com.github.apognu.otter.models.domain.SearchResult
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import kotlinx.serialization.ContextualSerialization
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@ -55,10 +56,12 @@ data class FunkwhaleTrack(
|
||||
@Serializable
|
||||
data class Favorited(val track: Int)
|
||||
|
||||
@Serializable
|
||||
data class DownloadInfo(
|
||||
val id: Int,
|
||||
val contentId: String,
|
||||
val title: String,
|
||||
val artist: String,
|
||||
@ContextualSerialization
|
||||
var download: Download?
|
||||
)
|
@ -5,6 +5,7 @@ import androidx.room.*
|
||||
import androidx.room.ForeignKey.CASCADE
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.models.api.FunkwhaleTrack
|
||||
import org.koin.java.KoinJavaComponent.inject
|
||||
|
||||
@Entity(tableName = "tracks")
|
||||
data class TrackEntity(
|
||||
@ -59,17 +60,17 @@ data class TrackEntity(
|
||||
fun insert(track: TrackEntity)
|
||||
|
||||
@Transaction
|
||||
fun insertWithAssocs(track: FunkwhaleTrack) {
|
||||
Otter.get().database.artists().insert(track.artist.toDao())
|
||||
fun insertWithAssocs(artistsDao: ArtistEntity.Dao, albumsDao: AlbumEntity.Dao, uploadsDao: UploadEntity.Dao, track: FunkwhaleTrack) {
|
||||
artistsDao.insert(track.artist.toDao())
|
||||
|
||||
track.album?.let {
|
||||
Otter.get().database.albums().insert(it.toDao())
|
||||
albumsDao.insert(it.toDao())
|
||||
}
|
||||
|
||||
insert(track.toDao())
|
||||
|
||||
track.uploads.forEach {
|
||||
Otter.get().database.uploads().insert(it.toDao(track.id))
|
||||
uploadsDao.insert(it.toDao(track.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,6 +104,7 @@ fun FunkwhaleTrack.toDao() = run {
|
||||
ON ar.id = al.artist_id
|
||||
LEFT JOIN favorites
|
||||
ON favorites.track_id = tracks.id
|
||||
ORDER BY tracks.position
|
||||
""")
|
||||
data class DecoratedTrackEntity(
|
||||
val id: Int,
|
||||
|
@ -7,29 +7,27 @@ import android.net.Uri
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.models.api.DownloadInfo
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.github.apognu.otter.viewmodels.DownloadsViewModel
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import com.github.apognu.otter.utils.Event
|
||||
import com.github.apognu.otter.utils.EventBus
|
||||
import com.github.apognu.otter.utils.mustNormalizeUrl
|
||||
import com.github.apognu.otter.viewmodels.DownloadsViewModel
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import com.google.android.exoplayer2.offline.DownloadManager
|
||||
import com.google.android.exoplayer2.offline.DownloadRequest
|
||||
import com.google.android.exoplayer2.offline.DownloadService
|
||||
import com.google.android.exoplayer2.scheduler.Scheduler
|
||||
import com.google.android.exoplayer2.ui.DownloadNotificationHelper
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.serialization.stringify
|
||||
import java.util.*
|
||||
|
||||
class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
|
||||
private val scope: CoroutineScope = CoroutineScope(Job() + Main)
|
||||
|
||||
companion object {
|
||||
fun download(context: Context, track: Track) {
|
||||
track.bestUpload()?.let { upload ->
|
||||
val url = mustNormalizeUrl(upload.listen_url)
|
||||
val data = Gson().toJson(
|
||||
val data = AppContext.json.stringify(
|
||||
DownloadInfo(
|
||||
track.id,
|
||||
url,
|
||||
|
@ -17,7 +17,6 @@ import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.media.session.MediaButtonReceiver
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.models.dao.RadioEntity
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.github.apognu.otter.viewmodels.PlayerStateViewModel
|
||||
@ -32,8 +31,11 @@ import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class PlayerService : Service() {
|
||||
private val playerViewModel by inject<PlayerStateViewModel>()
|
||||
|
||||
companion object {
|
||||
const val INITIAL_COMMAND_KEY = "start_command"
|
||||
}
|
||||
@ -136,7 +138,7 @@ class PlayerService : Service() {
|
||||
|
||||
val (current, duration, percent) = getProgress(true)
|
||||
|
||||
PlayerStateViewModel.get().position.postValue(Triple(current, duration, percent))
|
||||
playerViewModel.position.postValue(Triple(current, duration, percent))
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,8 +151,8 @@ class PlayerService : Service() {
|
||||
when (command) {
|
||||
is Command.RefreshService -> {
|
||||
if (queue.metadata.isNotEmpty()) {
|
||||
PlayerStateViewModel.get()._track.postValue(queue.current())
|
||||
PlayerStateViewModel.get().isPlaying.postValue(player.playWhenReady)
|
||||
playerViewModel._track.postValue(queue.current())
|
||||
playerViewModel.isPlaying.postValue(player.playWhenReady)
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,7 +213,7 @@ class PlayerService : Service() {
|
||||
delay(1000)
|
||||
|
||||
if (player.playWhenReady) {
|
||||
PlayerStateViewModel.get().position.postValue(getProgress())
|
||||
playerViewModel.position.postValue(getProgress())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -271,7 +273,7 @@ class PlayerService : Service() {
|
||||
if (hasAudioFocus(state)) {
|
||||
player.playWhenReady = state
|
||||
|
||||
PlayerStateViewModel.get().isPlaying.postValue(state)
|
||||
playerViewModel.isPlaying.postValue(state)
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,7 +293,7 @@ class PlayerService : Service() {
|
||||
player.next()
|
||||
|
||||
Cache.set(this@PlayerService, "progress", "0".toByteArray())
|
||||
PlayerStateViewModel.get().position.postValue(Triple(0, 0, 0))
|
||||
playerViewModel.position.postValue(Triple(0, 0, 0))
|
||||
}
|
||||
|
||||
private fun getProgress(force: Boolean = false): Triple<Int, Int, Int> {
|
||||
@ -371,17 +373,17 @@ class PlayerService : Service() {
|
||||
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
|
||||
super.onPlayerStateChanged(playWhenReady, playbackState)
|
||||
|
||||
PlayerStateViewModel.get().isPlaying.postValue(playWhenReady)
|
||||
playerViewModel.isPlaying.postValue(playWhenReady)
|
||||
|
||||
if (queue.current == -1) {
|
||||
PlayerStateViewModel.get()._track.postValue(queue.current())
|
||||
playerViewModel._track.postValue(queue.current())
|
||||
}
|
||||
|
||||
when (playWhenReady) {
|
||||
true -> {
|
||||
when (playbackState) {
|
||||
Player.STATE_READY -> mediaControlsManager.updateNotification(queue.current(), true)
|
||||
Player.STATE_BUFFERING -> PlayerStateViewModel.get().isBuffering.postValue(true)
|
||||
Player.STATE_BUFFERING -> playerViewModel.isBuffering.postValue(true)
|
||||
Player.STATE_ENDED -> {
|
||||
setPlaybackState(false)
|
||||
|
||||
@ -396,11 +398,11 @@ class PlayerService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
if (playbackState != Player.STATE_BUFFERING) PlayerStateViewModel.get().isBuffering.postValue(false)
|
||||
if (playbackState != Player.STATE_BUFFERING) playerViewModel.isBuffering.postValue(false)
|
||||
}
|
||||
|
||||
false -> {
|
||||
PlayerStateViewModel.get().isBuffering.postValue(false)
|
||||
playerViewModel.isBuffering.postValue(false)
|
||||
|
||||
Build.VERSION_CODES.N.onApi(
|
||||
{ stopForeground(STOP_FOREGROUND_DETACH) },
|
||||
@ -434,7 +436,7 @@ class PlayerService : Service() {
|
||||
|
||||
Cache.set(this@PlayerService, "current", queue.current.toString().toByteArray())
|
||||
|
||||
PlayerStateViewModel.get()._track.postValue(queue.current())
|
||||
playerViewModel._track.postValue(queue.current())
|
||||
}
|
||||
|
||||
override fun onPositionDiscontinuity(reason: Int) {
|
||||
|
@ -18,9 +18,13 @@ import com.google.android.exoplayer2.util.Util
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class QueueManager(val context: Context) {
|
||||
private val queueRepository = QueueRepository(GlobalScope)
|
||||
class QueueManager(val context: Context) : KoinComponent {
|
||||
private val playerViewModel by inject<PlayerStateViewModel>()
|
||||
private val queueRepository by inject<QueueRepository> { parametersOf(GlobalScope) }
|
||||
|
||||
var metadata: MutableList<Track> = mutableListOf()
|
||||
val datasources = ConcatenatingMediaSource()
|
||||
@ -59,7 +63,7 @@ class QueueManager(val context: Context) {
|
||||
Cache.get(context, "current")?.let { string ->
|
||||
current = string.readLine().toInt()
|
||||
|
||||
PlayerStateViewModel.get()._track.postValue(current())
|
||||
playerViewModel._track.postValue(current())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,15 @@
|
||||
package com.github.apognu.otter.playback
|
||||
|
||||
import android.content.Context
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.models.api.FunkwhaleTrack
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.models.dao.RadioEntity
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.github.apognu.otter.repositories.FavoritedRepository
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.github.kittinunf.fuel.Fuel
|
||||
import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult
|
||||
import com.github.kittinunf.fuel.coroutines.awaitObjectResult
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
@ -19,6 +17,9 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.stringify
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
|
||||
@Serializable
|
||||
data class RadioSessionBody(val radio_type: String?, var custom_radio: Int? = null, var related_object_id: String? = null)
|
||||
@ -35,15 +36,15 @@ data class RadioTrack(val position: Int, val track: RadioTrackID)
|
||||
@Serializable
|
||||
data class RadioTrackID(val id: Int)
|
||||
|
||||
class RadioPlayer(val context: Context, val scope: CoroutineScope) {
|
||||
class RadioPlayer(val context: Context, val scope: CoroutineScope) : KoinComponent {
|
||||
private val database by inject<OtterDatabase>()
|
||||
|
||||
val lock = Semaphore(1)
|
||||
|
||||
private var currentRadio: RadioEntity? = null
|
||||
private var session: Int? = null
|
||||
private var cookie: String? = null
|
||||
|
||||
private val favoritedRepository = FavoritedRepository(context)
|
||||
|
||||
init {
|
||||
Cache.get(context, "radio_type")?.readLine()?.let { radio_type ->
|
||||
Cache.get(context, "radio_id")?.readLine()?.toInt()?.let { radio_id ->
|
||||
@ -80,8 +81,6 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) {
|
||||
fun isActive() = currentRadio != null && session != null
|
||||
|
||||
private suspend fun createSession() {
|
||||
"createSession".log()
|
||||
|
||||
currentRadio?.let { radio ->
|
||||
try {
|
||||
val request = RadioSessionBody(radio.radio_type, related_object_id = radio.related_object_id).apply {
|
||||
@ -90,7 +89,7 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) {
|
||||
}
|
||||
}
|
||||
|
||||
val body = Gson().toJson(request)
|
||||
val body = AppContext.json.stringify(request)
|
||||
val (_, response, result) = Fuel.post(mustNormalizeUrl("/api/v1/radios/sessions/"))
|
||||
.authorize()
|
||||
.header("Content-Type", "application/json")
|
||||
@ -107,8 +106,6 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) {
|
||||
|
||||
prepareNextTrack(true)
|
||||
} catch (e: Exception) {
|
||||
e.log()
|
||||
|
||||
withContext(Main) {
|
||||
context.toast(context.getString(R.string.radio_playback_error))
|
||||
}
|
||||
@ -117,11 +114,9 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) {
|
||||
}
|
||||
|
||||
suspend fun prepareNextTrack(first: Boolean = false) {
|
||||
"prepareTrack".log()
|
||||
|
||||
session?.let { session ->
|
||||
try {
|
||||
val body = Gson().toJson(RadioTrackBody(session))
|
||||
val body = AppContext.json.stringify(RadioTrackBody(session))
|
||||
val result = Fuel.post(mustNormalizeUrl("/api/v1/radios/tracks/"))
|
||||
.authorize()
|
||||
.header("Content-Type", "application/json")
|
||||
@ -138,8 +133,8 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) {
|
||||
.awaitObjectResult<FunkwhaleTrack>(AppContext.deserializer())
|
||||
.get()
|
||||
|
||||
Otter.get().database.tracks().run {
|
||||
insertWithAssocs(track)
|
||||
database.tracks().run {
|
||||
insertWithAssocs(database.artists(), database.albums(), database.uploads(), track)
|
||||
|
||||
Track.fromDecoratedEntity(find(track.id)).let { track ->
|
||||
if (first) {
|
||||
|
@ -1,11 +1,16 @@
|
||||
package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import com.github.apognu.otter.Otter
|
||||
import androidx.lifecycle.LiveData
|
||||
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
|
||||
|
||||
class AlbumsRepository(override val context: Context?, artistId: Int? = null) : Repository<FunkwhaleAlbum>() {
|
||||
class AlbumsRepository(override val context: Context, private val database: OtterDatabase, artistId: Int?) : Repository<FunkwhaleAlbum>() {
|
||||
override val upstream: Upstream<FunkwhaleAlbum> by lazy {
|
||||
val url =
|
||||
if (artistId == null) "/api/v1/albums/?playable=true&ordering=title"
|
||||
@ -20,9 +25,19 @@ class AlbumsRepository(override val context: Context?, artistId: Int? = null) :
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleAlbum>): List<FunkwhaleAlbum> {
|
||||
data.forEach {
|
||||
Otter.get().database.albums().insert(it.toDao())
|
||||
database.albums().insert(it.toDao())
|
||||
}
|
||||
|
||||
return super.onDataFetched(data)
|
||||
}
|
||||
|
||||
fun all() = database.albums().allDecorated()
|
||||
|
||||
fun ofArtist(id: Int): LiveData<List<DecoratedAlbumEntity>> {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
fetch().collect()
|
||||
}
|
||||
|
||||
return database.albums().forArtistDecorated(id)
|
||||
}
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.models.api.FunkwhaleTrack
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class ArtistTracksRepository(override val context: Context?, private val artistId: Int) : Repository<FunkwhaleTrack>() {
|
||||
class ArtistTracksRepository(override val context: Context, private val database: OtterDatabase, artistId: Int) : Repository<FunkwhaleTrack>() {
|
||||
override val upstream = HttpUpstream(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks/?playable=true&artist=$artistId", FunkwhaleTrack.serializer())
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleTrack>) = runBlocking(IO) {
|
||||
data.forEach {
|
||||
Otter.get().database.tracks().insertWithAssocs(it)
|
||||
database.tracks().insertWithAssocs(database.artists(), database.albums(), database.uploads(), it)
|
||||
}
|
||||
|
||||
super.onDataFetched(data)
|
||||
|
@ -1,33 +1,45 @@
|
||||
package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import com.github.apognu.otter.Otter
|
||||
import androidx.lifecycle.LiveData
|
||||
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
|
||||
|
||||
class ArtistsRepository(override val context: Context?) : Repository<FunkwhaleArtist>() {
|
||||
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=name", FunkwhaleArtist.serializer())
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleArtist>): List<FunkwhaleArtist> {
|
||||
scope.launch(IO) {
|
||||
data.forEach { artist ->
|
||||
Otter.get().database.artists().insert(artist.toDao())
|
||||
database.artists().insert(artist.toDao())
|
||||
|
||||
Realm.getDefaultInstance().executeTransaction { realm ->
|
||||
realm.insertOrUpdate(artist.toRealmDao())
|
||||
}
|
||||
|
||||
artist.albums?.forEach { album ->
|
||||
Otter.get().database.albums().insert(album.toDao(artist.id))
|
||||
database.albums().insert(album.toDao(artist.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.onDataFetched(data)
|
||||
}
|
||||
|
||||
fun all(): LiveData<List<DecoratedArtistEntity>> {
|
||||
scope.launch(IO) {
|
||||
fetch().collect()
|
||||
}
|
||||
|
||||
return database.artists().allDecorated()
|
||||
}
|
||||
fun get(id: Int) = database.artists().getDecorated(id)
|
||||
}
|
@ -1,30 +1,32 @@
|
||||
package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import com.github.apognu.otter.Otter
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.github.apognu.otter.models.api.Favorited
|
||||
import com.github.apognu.otter.models.api.FunkwhaleTrack
|
||||
import com.github.apognu.otter.models.dao.FavoriteEntity
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import com.github.apognu.otter.utils.Settings
|
||||
import com.github.apognu.otter.utils.mustNormalizeUrl
|
||||
import com.github.kittinunf.fuel.Fuel
|
||||
import com.github.kittinunf.fuel.coroutines.awaitByteArrayResponseResult
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.stringify
|
||||
|
||||
class FavoritesRepository(override val context: Context?) : Repository<FunkwhaleTrack>() {
|
||||
class FavoritesRepository(override val context: Context, private val database: OtterDatabase) : Repository<FunkwhaleTrack>() {
|
||||
override val upstream =
|
||||
HttpUpstream(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks/?favorites=true&playable=true&ordering=title", FunkwhaleTrack.serializer())
|
||||
|
||||
val favoritedRepository = FavoritedRepository(context)
|
||||
val favoritedRepository = FavoritedRepository(context, database)
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleTrack>): List<FunkwhaleTrack> = runBlocking {
|
||||
data.forEach {
|
||||
Otter.get().database.tracks().insertWithAssocs(it)
|
||||
Otter.get().database.favorites().insert(FavoriteEntity(it.id))
|
||||
database.tracks().insertWithAssocs(database.artists(), database.albums(), database.uploads(), it)
|
||||
database.favorites().insert(FavoriteEntity(it.id))
|
||||
}
|
||||
|
||||
/* val downloaded = TracksRepository.getDownloadedIds() ?: listOf()
|
||||
@ -45,8 +47,18 @@ class FavoritesRepository(override val context: Context?) : Repository<Funkwhale
|
||||
data
|
||||
}
|
||||
|
||||
fun all(): LiveData<List<FavoriteEntity>> {
|
||||
scope.launch(IO) {
|
||||
fetch().collect()
|
||||
}
|
||||
|
||||
return database.favorites().all()
|
||||
}
|
||||
|
||||
fun find(ids: List<Int>) = database.albums().findAllDecorated(ids)
|
||||
|
||||
fun addFavorite(id: Int) = scope.launch(IO) {
|
||||
Otter.get().database.favorites().add(id)
|
||||
database.favorites().add(id)
|
||||
|
||||
val body = mapOf("track" to id)
|
||||
|
||||
@ -59,7 +71,7 @@ class FavoritesRepository(override val context: Context?) : Repository<Funkwhale
|
||||
scope.launch(IO) {
|
||||
request
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Gson().toJson(body))
|
||||
.body(AppContext.json.stringify(body))
|
||||
.awaitByteArrayResponseResult()
|
||||
|
||||
favoritedRepository.update()
|
||||
@ -67,7 +79,7 @@ class FavoritesRepository(override val context: Context?) : Repository<Funkwhale
|
||||
}
|
||||
|
||||
fun deleteFavorite(id: Int) = scope.launch(IO) {
|
||||
Otter.get().database.favorites().remove(id)
|
||||
database.favorites().remove(id)
|
||||
|
||||
val body = mapOf("track" to id)
|
||||
|
||||
@ -80,7 +92,7 @@ class FavoritesRepository(override val context: Context?) : Repository<Funkwhale
|
||||
scope.launch(IO) {
|
||||
request
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Gson().toJson(body))
|
||||
.body(AppContext.json.stringify(body))
|
||||
.awaitByteArrayResponseResult()
|
||||
|
||||
favoritedRepository.update()
|
||||
@ -88,14 +100,14 @@ class FavoritesRepository(override val context: Context?) : Repository<Funkwhale
|
||||
}
|
||||
}
|
||||
|
||||
class FavoritedRepository(override val context: Context?) : Repository<Favorited>() {
|
||||
class FavoritedRepository(override val context: Context, private val database: OtterDatabase) : Repository<Favorited>() {
|
||||
override val upstream =
|
||||
HttpUpstream(HttpUpstream.Behavior.Single, "/api/v1/favorites/tracks/all/?playable=true", Favorited.serializer())
|
||||
|
||||
override fun onDataFetched(data: List<Favorited>): List<Favorited> {
|
||||
scope.launch(IO) {
|
||||
data.forEach {
|
||||
Otter.get().database.favorites().insert(FavoriteEntity(it.track))
|
||||
database.favorites().insert(FavoriteEntity(it.track))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,9 +52,6 @@ class HttpUpstream<D : Any>(val behavior: Behavior, private val url: String, pri
|
||||
}
|
||||
},
|
||||
{ error ->
|
||||
"GET $url".log()
|
||||
error.log()
|
||||
|
||||
when (error.exception) {
|
||||
is RefreshError -> EventBus.send(Event.LogOut)
|
||||
else -> send(Repository.Response(listOf(), page, false))
|
||||
|
@ -1,24 +1,35 @@
|
||||
package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import com.github.apognu.otter.Otter
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.github.apognu.otter.models.api.FunkwhalePlaylistTrack
|
||||
import com.github.apognu.otter.models.dao.DecoratedTrackEntity
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.models.dao.PlaylistTrack
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class PlaylistTracksRepository(override val context: Context?, private val playlistId: Int) : Repository<FunkwhalePlaylistTrack>() {
|
||||
class PlaylistTracksRepository(override val context: Context?, private val database: OtterDatabase, private val playlistId: Int) : Repository<FunkwhalePlaylistTrack>() {
|
||||
override val upstream =
|
||||
HttpUpstream(HttpUpstream.Behavior.Single, "/api/v1/playlists/$playlistId/tracks/?playable=true", FunkwhalePlaylistTrack.serializer())
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhalePlaylistTrack>): List<FunkwhalePlaylistTrack> = runBlocking {
|
||||
Otter.get().database.playlists().replaceTracks(playlistId, data.map {
|
||||
Otter.get().database.tracks().insertWithAssocs(it.track)
|
||||
database.playlists().replaceTracks(playlistId, data.map {
|
||||
database.tracks().insertWithAssocs(database.artists(), database.albums(), database.uploads(), it.track)
|
||||
|
||||
PlaylistTrack(playlistId, it.track.id)
|
||||
})
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
fun tracks(id: Int): LiveData<List<DecoratedTrackEntity>> {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
fetch().collect()
|
||||
}
|
||||
|
||||
return database.playlists().tracksFor(id)
|
||||
}
|
||||
}
|
@ -1,19 +1,41 @@
|
||||
package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import com.github.apognu.otter.Otter
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.github.apognu.otter.models.api.FunkwhalePlaylist
|
||||
import com.github.apognu.otter.models.dao.DecoratedTrackEntity
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.models.dao.PlaylistEntity
|
||||
import com.github.apognu.otter.models.dao.toDao
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PlaylistsRepository(override val context: Context?) : Repository<FunkwhalePlaylist>() {
|
||||
class PlaylistsRepository(override val context: Context, private val database: OtterDatabase) : Repository<FunkwhalePlaylist>() {
|
||||
override val upstream =
|
||||
HttpUpstream(HttpUpstream.Behavior.Progressive, "/api/v1/playlists/?playable=true&ordering=name", FunkwhalePlaylist.serializer())
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhalePlaylist>): List<FunkwhalePlaylist> {
|
||||
data.forEach {
|
||||
Otter.get().database.playlists().insert(it.toDao())
|
||||
database.playlists().insert(it.toDao())
|
||||
}
|
||||
|
||||
return super.onDataFetched(data)
|
||||
}
|
||||
|
||||
fun all(): LiveData<List<PlaylistEntity>> {
|
||||
scope.launch(IO) {
|
||||
fetch().collect()
|
||||
}
|
||||
|
||||
return database.playlists().all()
|
||||
}
|
||||
|
||||
fun tracks(id: Int): LiveData<List<DecoratedTrackEntity>> {
|
||||
scope.launch(IO) {
|
||||
fetch().collect()
|
||||
}
|
||||
|
||||
return database.playlists().tracksFor(id)
|
||||
}
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
package com.github.apognu.otter.repositories
|
||||
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class QueueRepository(val scope: CoroutineScope) {
|
||||
fun all() = Otter.get().database.queue().allDecorated()
|
||||
class QueueRepository(private val database: OtterDatabase, private val scope: CoroutineScope) {
|
||||
fun all() = database.queue().allDecorated()
|
||||
|
||||
fun allBlocking() = Otter.get().database.queue().allDecoratedBlocking()
|
||||
fun allBlocking() = database.queue().allDecoratedBlocking()
|
||||
|
||||
fun replace(tracks: List<Track>) = scope.launch {
|
||||
Otter.get().database.queue().replace(tracks)
|
||||
database.queue().replace(tracks)
|
||||
}
|
||||
}
|
@ -1,21 +1,32 @@
|
||||
package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import com.github.apognu.otter.Otter
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.github.apognu.otter.models.api.FunkwhaleRadio
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.models.dao.RadioEntity
|
||||
import com.github.apognu.otter.models.dao.toDao
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class RadiosRepository(override val context: Context?) : Repository<FunkwhaleRadio>() {
|
||||
class RadiosRepository(override val context: Context, private val database: OtterDatabase) : Repository<FunkwhaleRadio>() {
|
||||
override val upstream =
|
||||
HttpUpstream(HttpUpstream.Behavior.Progressive, "/api/v1/radios/radios/?playable=true&ordering=name", FunkwhaleRadio.serializer())
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleRadio>): List<FunkwhaleRadio> {
|
||||
data.forEach {
|
||||
Otter.get().database.radios().insert(it.toDao())
|
||||
database.radios().insert(it.apply { radio_type = "custom" }.toDao())
|
||||
}
|
||||
|
||||
return data
|
||||
.map { radio -> radio.apply { radio_type = "custom" } }
|
||||
.toMutableList()
|
||||
}
|
||||
|
||||
fun all(): LiveData<List<RadioEntity>> {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
fetch().collect()
|
||||
}
|
||||
|
||||
return database.radios().all()
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@ import android.content.Context
|
||||
import com.github.apognu.otter.models.api.FunkwhaleAlbum
|
||||
import com.github.apognu.otter.models.api.FunkwhaleArtist
|
||||
import com.github.apognu.otter.models.api.FunkwhaleTrack
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class TracksSearchRepository(override val context: Context?, var query: String) : Repository<FunkwhaleTrack>() {
|
||||
@ -13,11 +11,6 @@ class TracksSearchRepository(override val context: Context?, var query: String)
|
||||
get() = HttpUpstream(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks/?playable=true&q=$query", FunkwhaleTrack.serializer())
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleTrack>): List<FunkwhaleTrack> = runBlocking {
|
||||
val favorites = FavoritedRepository(context).fetch()
|
||||
.map { it.data }
|
||||
.toList()
|
||||
.flatten()
|
||||
|
||||
/* val downloaded = TracksRepository.getDownloadedIds() ?: listOf()
|
||||
|
||||
data.map { track ->
|
||||
|
@ -1,13 +1,21 @@
|
||||
package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
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.getMetadata
|
||||
import com.github.apognu.otter.utils.maybeNormalizeUrl
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class TracksRepository(override val context: Context?, albumId: Int) : Repository<FunkwhaleTrack>() {
|
||||
class TracksRepository(override val context: Context, private val database: OtterDatabase, albumId: Int?) : Repository<FunkwhaleTrack>() {
|
||||
override val upstream =
|
||||
HttpUpstream(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks/?playable=true&album=$albumId&ordering=disc_number,position", FunkwhaleTrack.serializer())
|
||||
|
||||
@ -32,9 +40,42 @@ class TracksRepository(override val context: Context?, albumId: Int) : Repositor
|
||||
|
||||
override fun onDataFetched(data: List<FunkwhaleTrack>): List<FunkwhaleTrack> = runBlocking {
|
||||
data.forEach { track ->
|
||||
Otter.get().database.tracks().insertWithAssocs(track)
|
||||
database.tracks().insertWithAssocs(database.artists(), database.albums(), database.uploads(), track)
|
||||
}
|
||||
|
||||
data.sortedWith(compareBy({ it.disc_number }, { it.position }))
|
||||
}
|
||||
|
||||
fun find(ids: List<Int>) = database.tracks().findAllDecorated(ids)
|
||||
|
||||
suspend fun ofArtistBlocking(id: Int) = database.tracks().ofArtistBlocking(id)
|
||||
|
||||
fun ofAlbums(albums: List<Int>): LiveData<List<DecoratedTrackEntity>> {
|
||||
scope.launch(IO) {
|
||||
fetch().collect()
|
||||
}
|
||||
|
||||
return database.tracks().ofAlbumsDecorated(albums)
|
||||
}
|
||||
|
||||
fun favorites() = database.tracks().favorites()
|
||||
|
||||
fun isCached(track: Track): Boolean = Otter.get().exoCache.isCached(maybeNormalizeUrl(track.bestUpload()?.listen_url), 0L, track.bestUpload()?.duration?.toLong() ?: 0)
|
||||
|
||||
fun downloaded(): List<Int> {
|
||||
val cursor = Otter.get().exoDownloadManager.downloadIndex.getDownloads()
|
||||
val ids: MutableList<Int> = mutableListOf()
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
val download = cursor.download
|
||||
|
||||
download.getMetadata()?.let {
|
||||
if (download.state == Download.STATE_COMPLETED) {
|
||||
ids.add(it.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
}
|
@ -29,11 +29,13 @@ object AppContext {
|
||||
const val PAGE_SIZE = 50
|
||||
const val TRANSITION_DURATION = 300L
|
||||
|
||||
val json = Json(JsonConfiguration(ignoreUnknownKeys = true))
|
||||
|
||||
inline fun <reified T : Any> deserializer(serializer: DeserializationStrategy<T>): ResponseDeserializable<T> =
|
||||
kotlinxDeserializerOf(loader = serializer, json = Json(JsonConfiguration(ignoreUnknownKeys = true)))
|
||||
kotlinxDeserializerOf(loader = serializer, json = json)
|
||||
|
||||
inline fun <reified T : Any> deserializer() =
|
||||
kotlinxDeserializerOf(T::class.serializer(), Json(JsonConfiguration(ignoreUnknownKeys = true)))
|
||||
kotlinxDeserializerOf(T::class.serializer(), json)
|
||||
|
||||
fun init(context: Activity) {
|
||||
setupNotificationChannels(context)
|
||||
|
@ -8,7 +8,6 @@ import com.github.apognu.otter.models.api.DownloadInfo
|
||||
import com.github.apognu.otter.repositories.Repository
|
||||
import com.github.kittinunf.fuel.core.Request
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import com.google.gson.Gson
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.squareup.picasso.RequestCreator
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -77,4 +76,4 @@ fun Request.authorize(): Request {
|
||||
}
|
||||
}
|
||||
|
||||
fun Download.getMetadata(): DownloadInfo? = Gson().fromJson(String(this.request.data), DownloadInfo::class.java)
|
||||
fun Download.getMetadata(): DownloadInfo? = AppContext.json.parse(DownloadInfo.serializer(), String(this.request.data))
|
||||
|
@ -3,19 +3,19 @@ package com.github.apognu.otter.viewmodels
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.models.domain.Album
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.github.apognu.otter.models.domain.Upload
|
||||
import com.github.apognu.otter.repositories.AlbumsRepository
|
||||
import com.github.apognu.otter.repositories.TracksRepository
|
||||
|
||||
class AlbumsViewModel(private val artistId: Int? = null) : ViewModel() {
|
||||
class AlbumsViewModel(private val repository: AlbumsRepository, private val tracksRepository: TracksRepository, private val artistId: Int? = null) : ViewModel() {
|
||||
val albums: LiveData<List<Album>> by lazy {
|
||||
if (artistId == null) {
|
||||
Transformations.map(Otter.get().database.albums().allDecorated()) {
|
||||
Transformations.map(repository.all()) {
|
||||
it.map { album -> Album.fromDecoratedEntity(album) }
|
||||
}
|
||||
} else {
|
||||
Transformations.map(Otter.get().database.albums().forArtistDecorated(artistId)) {
|
||||
Transformations.map(repository.ofArtist(artistId)) {
|
||||
it.map { album -> Album.fromDecoratedEntity(album) }
|
||||
}
|
||||
}
|
||||
@ -23,24 +23,13 @@ class AlbumsViewModel(private val artistId: Int? = null) : ViewModel() {
|
||||
|
||||
suspend fun tracks(): List<Track> {
|
||||
artistId?.let {
|
||||
val tracks = Otter.get().database.tracks().ofArtistBlocking(artistId)
|
||||
val uploads = Otter.get().database.uploads().findAllBlocking(tracks.map { it.id })
|
||||
val tracks = tracksRepository.ofArtistBlocking(artistId)
|
||||
|
||||
return tracks.map {
|
||||
Track.fromDecoratedEntity(it).apply {
|
||||
this.uploads = uploads.filter { it.track_id == id }.map { Upload.fromEntity(it) }
|
||||
}
|
||||
Track.fromDecoratedEntity(it)
|
||||
}
|
||||
}
|
||||
|
||||
return listOf()
|
||||
}
|
||||
}
|
||||
|
||||
class AlbumViewModel(private val id: Int) : ViewModel() {
|
||||
val album: LiveData<Album> by lazy {
|
||||
Transformations.map(Otter.get().database.albums().getDecorated(id)) { album ->
|
||||
Album.fromDecoratedEntity(album)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +1,13 @@
|
||||
package com.github.apognu.otter.viewmodels
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations.map
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.map
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.models.domain.Artist
|
||||
import com.github.apognu.otter.repositories.ArtistsRepository
|
||||
|
||||
class ArtistsViewModel : ViewModel() {
|
||||
companion object {
|
||||
private lateinit var instance: ArtistsViewModel
|
||||
|
||||
fun get(): ArtistsViewModel {
|
||||
instance = if (::instance.isInitialized) instance else ArtistsViewModel()
|
||||
return instance
|
||||
}
|
||||
}
|
||||
|
||||
val artists: LiveData<List<Artist>> = Otter.get().database.artists().allDecorated().map {
|
||||
it.map { Artist.fromDecoratedEntity(it) }
|
||||
class ArtistsViewModel(private val repository: ArtistsRepository) : ViewModel() {
|
||||
val artists: LiveData<List<Artist>> = repository.all().map { artists ->
|
||||
artists.map { Artist.fromDecoratedEntity(it) }
|
||||
}
|
||||
}
|
||||
|
||||
class ArtistViewModel(private val id: Int) : ViewModel() {
|
||||
val artist: LiveData<Artist> by lazy {
|
||||
map(Otter.get().database.artists().getDecorated(id)) { artist ->
|
||||
Artist.fromDecoratedEntity(artist)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +1,17 @@
|
||||
package com.github.apognu.otter.viewmodels
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.github.apognu.otter.Otter
|
||||
import androidx.lifecycle.*
|
||||
import com.github.apognu.otter.models.domain.Album
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.github.apognu.otter.models.domain.Upload
|
||||
import com.github.apognu.otter.repositories.FavoritesRepository
|
||||
import com.github.apognu.otter.repositories.TracksRepository
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class FavoritesViewModel : ViewModel() {
|
||||
companion object {
|
||||
private lateinit var instance: FavoritesViewModel
|
||||
|
||||
fun get(): FavoritesViewModel {
|
||||
instance = if (::instance.isInitialized) instance else FavoritesViewModel()
|
||||
|
||||
return instance
|
||||
class FavoritesViewModel(private val repository: FavoritesRepository, private val tracksRepository: TracksRepository) : ViewModel() {
|
||||
private val _downloaded = liveData {
|
||||
while (true) {
|
||||
emit(tracksRepository.downloaded())
|
||||
delay(5000)
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,49 +19,40 @@ class FavoritesViewModel : ViewModel() {
|
||||
Transformations.switchMap(_favorites) { tracks ->
|
||||
val ids = tracks.mapNotNull { it.album?.id }
|
||||
|
||||
Transformations.map(Otter.get().database.albums().findAllDecorated(ids)) { albums ->
|
||||
Transformations.map(repository.find(ids)) { albums ->
|
||||
albums.map { album -> Album.fromDecoratedEntity(album) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val _favorites: LiveData<List<Track>> by lazy {
|
||||
Transformations.switchMap(Otter.get().database.favorites().all()) {
|
||||
Transformations.switchMap(repository.all()) {
|
||||
val ids = it.map { favorite -> favorite.track_id }
|
||||
|
||||
Transformations.map(Otter.get().database.tracks().findAllDecorated(ids)) { tracks ->
|
||||
Transformations.map(tracksRepository.find(ids)) { tracks ->
|
||||
tracks.map { track -> Track.fromDecoratedEntity(track) }.sortedBy { it.title }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val _uploads: LiveData<List<Upload>> by lazy {
|
||||
Transformations.switchMap(_favorites) { tracks ->
|
||||
val ids = tracks.mapNotNull { it.album?.id }
|
||||
|
||||
Transformations.map(Otter.get().database.uploads().findAll(ids)) { uploads ->
|
||||
uploads.map { upload -> Upload.fromEntity(upload) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val favorites = MediatorLiveData<List<Track>>().apply {
|
||||
addSource(_favorites) { merge(_favorites, _albums, _uploads) }
|
||||
addSource(_albums) { merge(_favorites, _albums, _uploads) }
|
||||
addSource(_uploads) { merge(_favorites, _albums, _uploads) }
|
||||
addSource(_favorites) { merge(_favorites, _albums, _downloaded) }
|
||||
addSource(_albums) { merge(_favorites, _albums, _downloaded) }
|
||||
addSource(_downloaded) { merge(_favorites, _albums, _downloaded) }
|
||||
}
|
||||
|
||||
private fun merge(_tracks: LiveData<List<Track>>, _albums: LiveData<List<Album>>, _uploads: LiveData<List<Upload>>) {
|
||||
private fun merge(_tracks: LiveData<List<Track>>, _albums: LiveData<List<Album>>, _downloaded: LiveData<List<Int>>) {
|
||||
val _tracks = _tracks.value
|
||||
val _albums = _albums.value
|
||||
val _uploads = _uploads.value
|
||||
val _downloaded = _downloaded.value
|
||||
|
||||
if (_tracks == null || _albums == null || _uploads == null) {
|
||||
if (_tracks == null || _albums == null || _downloaded == null) {
|
||||
return
|
||||
}
|
||||
|
||||
favorites.value = _tracks.map { track ->
|
||||
track.uploads = _uploads.filter { upload -> upload.track_id == track.id }
|
||||
track.cached = tracksRepository.isCached(track)
|
||||
track.downloaded = _downloaded.contains(track.id)
|
||||
track
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,10 @@
|
||||
package com.github.apognu.otter.viewmodels
|
||||
|
||||
import androidx.lifecycle.*
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.models.dao.OtterDatabase
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
|
||||
class PlayerStateViewModel private constructor() : ViewModel() {
|
||||
companion object {
|
||||
private lateinit var instance: PlayerStateViewModel
|
||||
|
||||
fun get(): PlayerStateViewModel {
|
||||
instance = if (::instance.isInitialized) instance else PlayerStateViewModel()
|
||||
|
||||
return instance
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerStateViewModel(private val database: OtterDatabase) : ViewModel() {
|
||||
val isPlaying: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
|
||||
val isBuffering: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
|
||||
val position: MutableLiveData<Triple<Int, Int, Int>> by lazy { MutableLiveData<Triple<Int, Int, Int>>() }
|
||||
@ -27,7 +17,7 @@ class PlayerStateViewModel private constructor() : ViewModel() {
|
||||
return@switchMap null
|
||||
}
|
||||
|
||||
Otter.get().database.tracks().getDecorated(it.id).map { Track.fromDecoratedEntity(it) }
|
||||
database.tracks().getDecorated(it.id).map { Track.fromDecoratedEntity(it) }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +1,53 @@
|
||||
package com.github.apognu.otter.viewmodels
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.github.apognu.otter.Otter
|
||||
import androidx.lifecycle.*
|
||||
import com.github.apognu.otter.models.dao.PlaylistEntity
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.github.apognu.otter.repositories.PlaylistTracksRepository
|
||||
import com.github.apognu.otter.repositories.PlaylistsRepository
|
||||
import com.github.apognu.otter.repositories.TracksRepository
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class PlaylistsViewModel : ViewModel() {
|
||||
val playlists: LiveData<List<PlaylistEntity>> by lazy { Otter.get().database.playlists().all() }
|
||||
class PlaylistsViewModel(private val repository: PlaylistsRepository) : ViewModel() {
|
||||
val playlists: LiveData<List<PlaylistEntity>> by lazy { repository.all() }
|
||||
}
|
||||
|
||||
class PlaylistViewModel(playlistId: Int) : ViewModel() {
|
||||
val tracks: LiveData<List<Track>> by lazy {
|
||||
Transformations.map(Otter.get().database.playlists().tracksFor(playlistId)) {
|
||||
class PlaylistViewModel(private val repository: PlaylistTracksRepository, private val tracksRepository: TracksRepository, playerViewModel: PlayerStateViewModel, playlistId: Int) : ViewModel() {
|
||||
private val _downloaded = liveData {
|
||||
while (true) {
|
||||
emit(tracksRepository.downloaded())
|
||||
delay(5000)
|
||||
}
|
||||
}
|
||||
|
||||
private val _current = playerViewModel.track
|
||||
|
||||
private val _tracks: LiveData<List<Track>> by lazy {
|
||||
Transformations.map(repository.tracks(playlistId)) {
|
||||
it.map { track -> Track.fromDecoratedEntity(track) }
|
||||
}
|
||||
}
|
||||
|
||||
val tracks = MediatorLiveData<List<Track>>().apply {
|
||||
addSource(_tracks) { merge(_tracks, _current, _downloaded) }
|
||||
addSource(_current) { merge(_tracks, _current, _downloaded) }
|
||||
addSource(_downloaded) { merge(_tracks, _current, _downloaded) }
|
||||
}
|
||||
|
||||
private fun merge(_tracks: LiveData<List<Track>>, _current: LiveData<Track>, _downloaded: LiveData<List<Int>>) {
|
||||
val _tracks = _tracks.value
|
||||
val _current = _current.value
|
||||
val _downloaded = _downloaded.value
|
||||
|
||||
if (_tracks == null || _downloaded == null) {
|
||||
return
|
||||
}
|
||||
|
||||
tracks.value = _tracks.map { track ->
|
||||
track.current = _current?.id == track.id
|
||||
track.cached = tracksRepository.isCached(track)
|
||||
track.downloaded = _downloaded.contains(track.id)
|
||||
track
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,19 +7,7 @@ import com.github.apognu.otter.repositories.QueueRepository
|
||||
import com.github.apognu.otter.utils.maybeNormalizeUrl
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class QueueViewModel private constructor() : ViewModel() {
|
||||
companion object {
|
||||
private lateinit var instance: QueueViewModel
|
||||
|
||||
fun get(): QueueViewModel {
|
||||
instance = if (::instance.isInitialized) instance else QueueViewModel()
|
||||
|
||||
return instance
|
||||
}
|
||||
}
|
||||
|
||||
private val queueRepository = QueueRepository(viewModelScope)
|
||||
|
||||
class QueueViewModel(private val repository: QueueRepository, playerViewModel: PlayerStateViewModel) : ViewModel() {
|
||||
private val _cached = liveData {
|
||||
while (true) {
|
||||
emit(Otter.get().exoCache.keys)
|
||||
@ -27,10 +15,10 @@ class QueueViewModel private constructor() : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private val _current = PlayerStateViewModel.get().track
|
||||
private val _current = playerViewModel.track
|
||||
|
||||
private val _queue: LiveData<List<Track>> by lazy {
|
||||
Transformations.map(queueRepository.all()) { tracks ->
|
||||
Transformations.map(repository.all()) { tracks ->
|
||||
tracks.map { Track.fromDecoratedEntity(it) }
|
||||
}
|
||||
}
|
||||
|
@ -4,17 +4,8 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.models.dao.RadioEntity
|
||||
import com.github.apognu.otter.repositories.RadiosRepository
|
||||
|
||||
class RadiosViewModel : ViewModel() {
|
||||
companion object {
|
||||
private lateinit var instance: RadiosViewModel
|
||||
|
||||
fun get(): RadiosViewModel {
|
||||
instance = if (::instance.isInitialized) instance else RadiosViewModel()
|
||||
|
||||
return instance
|
||||
}
|
||||
}
|
||||
|
||||
val radios: LiveData<List<RadioEntity>> by lazy { Otter.get().database.radios().all() }
|
||||
class RadiosViewModel(private val repository: RadiosRepository) : ViewModel() {
|
||||
val radios: LiveData<List<RadioEntity>> by lazy { repository.all() }
|
||||
}
|
@ -1,65 +1,65 @@
|
||||
package com.github.apognu.otter.viewmodels
|
||||
|
||||
import androidx.lifecycle.*
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.models.domain.Track
|
||||
import com.github.apognu.otter.utils.maybeNormalizeUrl
|
||||
import com.github.apognu.otter.repositories.TracksRepository
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class TracksViewModel(private val albumId: Int) : ViewModel() {
|
||||
private val _cached = liveData {
|
||||
class TracksViewModel(private val repository: TracksRepository, playerViewModel: PlayerStateViewModel, private val albumId: Int) : ViewModel() {
|
||||
private val _downloaded = liveData {
|
||||
while (true) {
|
||||
emit(Otter.get().exoCache.keys)
|
||||
emit(repository.downloaded())
|
||||
delay(5000)
|
||||
}
|
||||
}
|
||||
|
||||
private val _current = PlayerStateViewModel.get().track
|
||||
private val _current = playerViewModel.track
|
||||
|
||||
private val _tracks: LiveData<List<Track>> by lazy {
|
||||
Transformations.map(Otter.get().database.tracks().ofAlbumsDecorated(listOf(albumId))) {
|
||||
Transformations.map(repository.ofAlbums(listOf(albumId))) {
|
||||
it.map { track -> Track.fromDecoratedEntity(track) }
|
||||
}
|
||||
}
|
||||
|
||||
private val _favorites: LiveData<List<Track>> by lazy {
|
||||
Transformations.map(Otter.get().database.tracks().favorites()) {
|
||||
Transformations.map(repository.favorites()) {
|
||||
it.map { track -> Track.fromDecoratedEntity(track) }
|
||||
}
|
||||
}
|
||||
|
||||
val tracks = MediatorLiveData<List<Track>>().apply {
|
||||
addSource(_tracks) { mergeTracks(_tracks, _current, _cached) }
|
||||
addSource(_current) { mergeTracks(_tracks, _current, _cached) }
|
||||
addSource(_cached) { mergeTracks(_tracks, _current, _cached) }
|
||||
addSource(_tracks) { mergeTracks(_tracks, _current, _downloaded) }
|
||||
addSource(_current) { mergeTracks(_tracks, _current, _downloaded) }
|
||||
addSource(_downloaded) { mergeTracks(_tracks, _current, _downloaded) }
|
||||
}
|
||||
|
||||
val favorites = MediatorLiveData<List<Track>>().apply {
|
||||
addSource(_favorites) { mergeFavorites(_favorites, _current, _cached) }
|
||||
addSource(_current) { mergeFavorites(_favorites, _current, _cached) }
|
||||
addSource(_cached) { mergeFavorites(_favorites, _current, _cached) }
|
||||
addSource(_favorites) { mergeFavorites(_favorites, _current, _downloaded) }
|
||||
addSource(_current) { mergeFavorites(_favorites, _current, _downloaded) }
|
||||
addSource(_downloaded) { mergeFavorites(_favorites, _current, _downloaded) }
|
||||
}
|
||||
|
||||
private fun mergeTracks(_tracks: LiveData<List<Track>>, _current: LiveData<Track>, _cached: LiveData<Set<String>>) {
|
||||
tracks.value = merge(_tracks, _current, _cached) ?: return
|
||||
private fun mergeTracks(_tracks: LiveData<List<Track>>, _current: LiveData<Track>, _downloaded: LiveData<List<Int>>) {
|
||||
tracks.value = merge(_tracks, _current, _downloaded) ?: return
|
||||
}
|
||||
|
||||
private fun mergeFavorites(_tracks: LiveData<List<Track>>, _current: LiveData<Track>, _cached: LiveData<Set<String>>) {
|
||||
favorites.value = merge(_tracks, _current, _cached) ?: return
|
||||
private fun mergeFavorites(_tracks: LiveData<List<Track>>, _current: LiveData<Track>, _downloaded: LiveData<List<Int>>) {
|
||||
favorites.value = merge(_tracks, _current, _downloaded) ?: return
|
||||
}
|
||||
|
||||
private fun merge(_tracks: LiveData<List<Track>>, _current: LiveData<Track>, _cached: LiveData<Set<String>>): List<Track>? {
|
||||
private fun merge(_tracks: LiveData<List<Track>>, _current: LiveData<Track>, _downloaded: LiveData<List<Int>>): List<Track>? {
|
||||
val _tracks = _tracks.value
|
||||
val _current = _current.value
|
||||
val _cached = _cached.value
|
||||
val _downloaded = _downloaded.value
|
||||
|
||||
if (_tracks == null || _cached == null) {
|
||||
if (_tracks == null || _downloaded == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return _tracks.map { track ->
|
||||
track.current = _current?.id == track.id
|
||||
track.cached = _cached.contains(maybeNormalizeUrl(track.bestUpload()?.listen_url))
|
||||
track.cached = repository.isCached(track)
|
||||
track.downloaded = _downloaded.contains(track.id)
|
||||
track
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user