Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • bugfix/46-adjust-now-playing-top-margin
  • deploy-in-docker
  • develop
  • enhancement/speed-up-pipelines
  • fix-ci-for-tags
  • gradle-v8
  • housekeeping/mavenCentral
  • housekeeping/remove-warnings
  • renovate/io.mockk-mockk-1.x
  • technical/skip-metadata-file-for-branches
  • technical/update-compile-sdk-version-docker
  • technical/update-jvmTarget-version
  • technical/upgrade-appcompat-1.5.x
  • technical/upgrade-exoplayer
  • 0.0.1
  • 0.1
  • 0.1.1
  • 0.1.2
  • 0.1.3
  • 0.1.4
  • 0.1.5
  • 0.1.5-1
  • 0.2.0
  • 0.2.1
  • 0.2.1-1
  • 0.3.0
  • fdroid-dummy-1
  • fdroid-dummy-2
  • fdroid-dummy-3
  • fdroid-dummy-4
  • fdroid-dummy-5
31 results

Target

Select target project
  • funkwhale/funkwhale-android
  • creak/funkwhale-android
  • Keunes/funkwhale-android
  • Mouath/funkwhale-android
4 results
Select Git revision
  • bugfix/46-adjust-now-playing-top-margin
  • bugfix/90-error-playing-user-radio
  • deploy-in-docker
  • develop
  • enhancement/89-update-appstore-metadata
  • enhancement/speed-up-pipelines
  • housekeeping/remove-warnings
  • renovate/configure
  • 0.0.1
  • 0.1
  • 0.1.1
11 results
Show changes
Showing
with 387 additions and 240 deletions
package audio.funkwhale.ffa.model
import android.os.Parcelable
import audio.funkwhale.ffa.utils.containsIgnoringCase
import com.preference.PowerPreference
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@Parcelize
data class Track(
val id: Int = 0,
val title: String,
private val cover: Covers? ,
val artist: Artist,
val album: Album?,
val disc_number: Int = 0,
......@@ -12,10 +18,18 @@ data class Track(
val uploads: List<Upload> = listOf(),
val copyright: String? = null,
val license: String? = null
) : SearchResult {
) : SearchResult, Parcelable {
@IgnoredOnParcel
var current: Boolean = false
@IgnoredOnParcel
var favorite: Boolean = false
@IgnoredOnParcel
var cached: Boolean = false
@IgnoredOnParcel
var downloaded: Boolean = false
companion object {
......@@ -23,17 +37,21 @@ data class Track(
fun fromDownload(download: DownloadInfo): Track = Track(
id = download.id,
title = download.title,
cover = Covers(CoverUrls("")),
artist = Artist(0, download.artist, listOf()),
album = Album(0, Album.Artist(""), "", Covers(CoverUrls("")), ""),
uploads = listOf(Upload(download.contentId, 0, 0))
)
}
data class Upload(
val listen_url: String,
val duration: Int,
val bitrate: Int
)
@Parcelize
data class Upload(val listen_url: String, val duration: Int, val bitrate: Int) : Parcelable
fun matchesFilter(filter: String): Boolean {
return title.containsIgnoringCase(filter) ||
artist.name.containsIgnoringCase(filter) ||
album?.title.containsIgnoringCase(filter)
}
override fun equals(other: Any?): Boolean {
return when (other) {
......@@ -49,14 +67,30 @@ data class Track(
fun bestUpload(): Upload? {
if (uploads.isEmpty()) return null
return when (PowerPreference.getDefaultFile().getString("media_cache_quality")) {
var bestUpload = when (PowerPreference.getDefaultFile().getString("media_cache_quality")) {
"quality" -> uploads.maxByOrNull { it.bitrate } ?: uploads[0]
"size" -> uploads.minByOrNull { it.bitrate } ?: uploads[0]
else -> uploads.maxByOrNull { it.bitrate } ?: uploads[0]
}
return when (PowerPreference.getDefaultFile().getString("bandwidth_limitation")) {
"unlimited" -> bestUpload
"limited" -> {
var listenUrl = bestUpload.listen_url
Upload(listenUrl.plus("&to=mp3&max_bitrate=320"), uploads[0].duration, 320_000)
}
else -> bestUpload
}
}
override fun cover(): String? {
return if (cover?.urls?.original != null) {
cover.urls.original
} else {
album?.cover()
}
}
override fun cover() = album?.cover?.urls?.original
override fun title() = title
override fun subtitle() = artist.name
......
......@@ -5,7 +5,7 @@ import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.utils.OAuth
import audio.funkwhale.ffa.utils.Settings
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.google.android.exoplayer2.upstream.FileDataSource
import com.google.android.exoplayer2.upstream.cache.Cache
import com.google.android.exoplayer2.upstream.cache.CacheDataSource
......@@ -33,9 +33,9 @@ class CacheDataSourceFactoryProvider(
}
private fun createDatasourceFactory(context: Context, oAuth: OAuth): DataSource.Factory {
val http = DefaultHttpDataSourceFactory(
Util.getUserAgent(context, context.getString(R.string.app_name))
)
val http = DefaultHttpDataSource.Factory().apply {
setUserAgent(Util.getUserAgent(context, context.getString(R.string.app_name)))
}
return if (!Settings.isAnonymous()) {
OAuth2DatasourceFactory(context, http, oAuth)
} else {
......
......@@ -2,6 +2,7 @@ package audio.funkwhale.ffa.playback
import android.app.Notification
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.app.Service
import android.content.Intent
import android.support.v4.media.session.MediaSessionCompat
......@@ -12,16 +13,20 @@ import androidx.media.app.NotificationCompat.MediaStyle
import androidx.media.session.MediaButtonReceiver
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.activities.MainActivity
import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import com.squareup.picasso.Picasso
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.launch
import org.koin.java.KoinJavaComponent.inject
class MediaControlsManager(val context: Service, private val scope: CoroutineScope, private val mediaSession: MediaSessionCompat) {
class MediaControlsManager(
val context: Service,
private val scope: CoroutineScope,
private val mediaSession: MediaSessionCompat
) {
companion object {
const val NOTIFICATION_ACTION_OPEN_QUEUE = 0
......@@ -41,8 +46,10 @@ class MediaControlsManager(val context: Service, private val scope: CoroutineSco
}
scope.launch(Default) {
val openIntent = Intent(context, MainActivity::class.java).apply { action = NOTIFICATION_ACTION_OPEN_QUEUE.toString() }
val openPendingIntent = PendingIntent.getActivity(context, 0, openIntent, 0)
val openIntent = Intent(context, MainActivity::class.java).apply {
action = NOTIFICATION_ACTION_OPEN_QUEUE.toString()
}
val openPendingIntent = PendingIntent.getActivity(context, 0, openIntent, FLAG_IMMUTABLE)
val coverUrl = maybeNormalizeUrl(track.album?.cover())
......@@ -61,7 +68,7 @@ class MediaControlsManager(val context: Service, private val scope: CoroutineSco
.run {
coverUrl?.let {
try {
setLargeIcon(Picasso.get().load(coverUrl).get())
setLargeIcon(CoverArt.requestCreator(coverUrl).get())
} catch (_: Exception) {
}
......@@ -98,7 +105,8 @@ class MediaControlsManager(val context: Service, private val scope: CoroutineSco
if (playing) {
context.startForeground(AppContext.NOTIFICATION_MEDIA_CONTROL, it)
} else {
NotificationManagerCompat.from(context).notify(AppContext.NOTIFICATION_MEDIA_CONTROL, it)
NotificationManagerCompat.from(context)
.notify(AppContext.NOTIFICATION_MEDIA_CONTROL, it)
}
}
......
......@@ -2,13 +2,13 @@ package audio.funkwhale.ffa.playback
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.ResultReceiver
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import com.google.android.exoplayer2.ControlDispatcher
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
......@@ -30,7 +30,6 @@ class MediaSession(private val context: Context) {
val session: MediaSessionCompat by lazy {
MediaSessionCompat(context, context.packageName).apply {
setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
setPlaybackState(playbackStateBuilder.build())
isActive = true
......@@ -42,13 +41,19 @@ class MediaSession(private val context: Context) {
MediaSessionConnector(session).also {
it.setQueueNavigator(FFAQueueNavigator())
it.setMediaButtonEventHandler { _, _, intent ->
it.setMediaButtonEventHandler { _, intent ->
if (!active) {
context.startService(Intent(context, PlayerService::class.java).apply {
action = intent.action
Intent(context, PlayerService::class.java).let { player ->
player.action = intent.action
intent.extras?.let { extras -> putExtras(extras) }
})
intent.extras?.let { extras -> player.putExtras(extras) }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(player)
} else {
context.startService(player)
}
}
return@setMediaButtonEventHandler true
}
......@@ -60,13 +65,11 @@ class MediaSession(private val context: Context) {
}
class FFAQueueNavigator : MediaSessionConnector.QueueNavigator {
override fun onSkipToQueueItem(player: Player, controlDispatcher: ControlDispatcher, id: Long) {
override fun onSkipToQueueItem(player: Player, id: Long) {
CommandBus.send(Command.PlayTrack(id.toInt()))
}
override fun onCurrentWindowIndexChanged(player: Player) {}
override fun onCommand(player: Player, controlDispatcher: ControlDispatcher, command: String, extras: Bundle?, cb: ResultReceiver?) = true
override fun onCommand(player: Player, command: String, extras: Bundle?, cb: ResultReceiver?) = true
override fun getSupportedQueueNavigatorActions(player: Player): Long {
return PlaybackStateCompat.ACTION_PLAY_PAUSE or
......@@ -75,13 +78,13 @@ class FFAQueueNavigator : MediaSessionConnector.QueueNavigator {
PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM
}
override fun onSkipToNext(player: Player, controlDispatcher: ControlDispatcher) {
override fun onSkipToNext(player: Player) {
CommandBus.send(Command.NextTrack)
}
override fun getActiveQueueItemId(player: Player?) = player?.currentWindowIndex?.toLong() ?: 0
override fun getActiveQueueItemId(player: Player?) = player?.currentMediaItemIndex?.toLong() ?: 0
override fun onSkipToPrevious(player: Player, controlDispatcher: ControlDispatcher) {
override fun onSkipToPrevious(player: Player) {
CommandBus.send(Command.PreviousTrack)
}
......
......@@ -3,7 +3,11 @@ package audio.funkwhale.ffa.playback
import android.content.Context
import android.net.Uri
import audio.funkwhale.ffa.utils.OAuth
import com.google.android.exoplayer2.upstream.*
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DataSpec
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.google.android.exoplayer2.upstream.HttpDataSource
import com.google.android.exoplayer2.upstream.TransferListener
class OAuthDatasource(
private val context: Context,
......@@ -38,7 +42,7 @@ class OAuthDatasource(
class OAuth2DatasourceFactory(
private val context: Context,
private val http: DefaultHttpDataSourceFactory,
private val http: DefaultHttpDataSource.Factory,
private val oauth: OAuth
) : DataSource.Factory {
......
......@@ -7,7 +7,13 @@ import androidx.core.net.toUri
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.model.DownloadInfo
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.utils.*
import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.utils.Event
import audio.funkwhale.ffa.utils.EventBus
import audio.funkwhale.ffa.utils.Request
import audio.funkwhale.ffa.utils.RequestBus
import audio.funkwhale.ffa.utils.Response
import audio.funkwhale.ffa.utils.mustNormalizeUrl
import com.google.android.exoplayer2.offline.Download
import com.google.android.exoplayer2.offline.DownloadManager
import com.google.android.exoplayer2.offline.DownloadRequest
......@@ -18,10 +24,9 @@ import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.koin.java.KoinJavaComponent
import java.util.*
import java.util.Collections
class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
......@@ -29,6 +34,7 @@ class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
private val exoDownloadManager: DownloadManager by KoinJavaComponent.inject(DownloadManager::class.java)
companion object {
fun download(context: Context, track: Track) {
track.bestUpload()?.let { upload ->
val url = mustNormalizeUrl(upload.listen_url)
......@@ -42,7 +48,7 @@ class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
)
).toByteArray()
val request = DownloadRequest.Builder(track.id.toString(), url.toUri())
val request = DownloadRequest.Builder(url.toUri().toString(), url.toUri())
.setData(data)
.setStreamKeys(Collections.emptyList())
.build()
......@@ -57,8 +63,8 @@ class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
scope.launch(Main) {
RequestBus.get().collect { request ->
when (request) {
is Request.GetDownloads -> request.channel?.trySend(Response.Downloads(getDownloads()))?.isSuccess
if (request is Request.GetDownloads) {
request.channel?.trySend(Response.Downloads(getDownloads()))?.isSuccess
}
}
}
......@@ -66,20 +72,28 @@ class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
return super.onStartCommand(intent, flags, startId)
}
override fun getDownloadManager() = exoDownloadManager.apply {
override fun getDownloadManager(): DownloadManager {
return exoDownloadManager.apply {
addListener(DownloadListener())
}
}
override fun getScheduler(): Scheduler? = null
override fun getForegroundNotification(downloads: MutableList<Download>): Notification {
override fun getForegroundNotification(
downloads: MutableList<Download>,
notMetRequirements: Int
): Notification {
val description =
resources.getQuantityString(R.plurals.downloads_description, downloads.size, downloads.size)
return DownloadNotificationHelper(
this,
AppContext.NOTIFICATION_CHANNEL_DOWNLOADS
).buildProgressNotification(this, R.drawable.downloads, null, description, downloads)
).buildProgressNotification(
this, R.drawable.downloads, null, description,
downloads, notMetRequirements
)
}
private fun getDownloads() = downloadManager.downloadIndex.getDownloads()
......
......@@ -4,8 +4,15 @@ import android.content.Context
import android.net.Uri
import audio.funkwhale.ffa.model.QueueCache
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.utils.*
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.Event
import audio.funkwhale.ffa.utils.EventBus
import audio.funkwhale.ffa.utils.FFACache
import audio.funkwhale.ffa.utils.log
import audio.funkwhale.ffa.utils.mustNormalizeUrl
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.source.ConcatenatingMediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.gson.Gson
......@@ -22,23 +29,25 @@ class QueueManager(val context: Context) {
var current = -1
init {
FFACache.get(context, "queue")?.let { json ->
gsonDeserializerOf(QueueCache::class.java).deserialize(json)?.let { cache ->
FFACache.getLine(context, "queue")?.let { json ->
gsonDeserializerOf(QueueCache::class.java).deserialize(json.reader())?.let { cache ->
metadata = cache.data.toMutableList()
val factory = cacheDataSourceFactoryProvider.create(context)
dataSources.addMediaSources(metadata.map { track ->
dataSources.addMediaSources(
metadata.map { track ->
val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
ProgressiveMediaSource.Factory(factory).setTag(track.title)
.createMediaSource(Uri.parse(url))
})
val mediaItem = MediaItem.fromUri(Uri.parse(url)).buildUpon().setTag(track.title).build()
ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem)
}
)
}
}
FFACache.get(context, "current")?.let { string ->
current = string.readLine().toInt()
FFACache.getLine(context, "current")?.let {
current = it.toInt()
}
}
......@@ -46,7 +55,7 @@ class QueueManager(val context: Context) {
FFACache.set(
context,
"queue",
Gson().toJson(QueueCache(metadata)).toByteArray()
Gson().toJson(QueueCache(metadata)).toString()
)
}
......@@ -55,8 +64,8 @@ class QueueManager(val context: Context) {
val factory = cacheDataSourceFactoryProvider.create(context)
val sources = tracks.map { track ->
val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
ProgressiveMediaSource.Factory(factory).setTag(track.title).createMediaSource(Uri.parse(url))
val mediaItem = MediaItem.fromUri(Uri.parse(url)).buildUpon().setTag(track.title).build()
ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem)
}
metadata = tracks.toMutableList()
......@@ -76,7 +85,8 @@ class QueueManager(val context: Context) {
val sources = missingTracks.map { track ->
val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(url))
val mediaItem = MediaItem.fromUri(Uri.parse(url)).buildUpon().setTag(track.title).build()
ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem)
}
metadata.addAll(tracks)
......@@ -93,7 +103,8 @@ class QueueManager(val context: Context) {
val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
if (metadata.indexOf(track) == -1) {
ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(url)).let {
val mediaItem = MediaItem.fromUri(Uri.parse(url)).buildUpon().setTag(track.title).build()
ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem).let {
dataSources.addMediaSource(current + 1, it)
metadata.add(current + 1, track)
}
......@@ -156,6 +167,8 @@ class QueueManager(val context: Context) {
return metadata.getOrNull(current)
}
fun currentIndex(): Int = (if (current == -1) 0 else current)
fun clear() {
metadata = mutableListOf()
dataSources.clear()
......
......@@ -6,7 +6,16 @@ import audio.funkwhale.ffa.model.Radio
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.repositories.FavoritedRepository
import audio.funkwhale.ffa.repositories.Repository
import audio.funkwhale.ffa.utils.*
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.Event
import audio.funkwhale.ffa.utils.EventBus
import audio.funkwhale.ffa.utils.FFACache
import audio.funkwhale.ffa.utils.OAuth
import audio.funkwhale.ffa.utils.authorize
import audio.funkwhale.ffa.utils.logError
import audio.funkwhale.ffa.utils.mustNormalizeUrl
import audio.funkwhale.ffa.utils.toast
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult
import com.github.kittinunf.fuel.coroutines.awaitObjectResult
......@@ -44,10 +53,10 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) {
private val favoritedRepository = FavoritedRepository(context)
init {
FFACache.get(context, "radio_type")?.readLine()?.let { radio_type ->
FFACache.get(context, "radio_id")?.readLine()?.toInt()?.let { radio_id ->
FFACache.get(context, "radio_session")?.readLine()?.toInt()?.let { radio_session ->
val cachedCookie = FFACache.get(context, "radio_cookie")?.readLine()
FFACache.getLine(context, "radio_type")?.let { radio_type ->
FFACache.getLine(context, "radio_id")?.toInt()?.let { radio_id ->
FFACache.getLine(context, "radio_session")?.toInt()?.let { radio_session ->
val cachedCookie = FFACache.getLine(context, "radio_cookie")
currentRadio = Radio(radio_id, radio_type, "", "")
session = radio_session
......@@ -98,13 +107,14 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) {
session = result.get().id
cookie = response.header("set-cookie").joinToString(";")
FFACache.set(context, "radio_type", radio.radio_type.toByteArray())
FFACache.set(context, "radio_id", radio.id.toString().toByteArray())
FFACache.set(context, "radio_session", session.toString().toByteArray())
FFACache.set(context, "radio_cookie", cookie.toString().toByteArray())
FFACache.set(context, "radio_type", radio.radio_type)
FFACache.set(context, "radio_id", radio.id.toString())
FFACache.set(context, "radio_session", session.toString())
FFACache.set(context, "radio_cookie", cookie.toString())
prepareNextTrack(true)
} catch (e: Exception) {
e.logError()
withContext(Main) {
context.toast(context.getString(R.string.radio_playback_error))
}
......
......@@ -8,7 +8,6 @@ import audio.funkwhale.ffa.utils.OAuth
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import com.google.gson.reflect.TypeToken
import org.koin.java.KoinJavaComponent.inject
import java.io.BufferedReader
class AlbumsRepository(override val context: Context?, artistId: Int? = null) :
Repository<Album, AlbumsCache>() {
......@@ -35,6 +34,6 @@ class AlbumsRepository(override val context: Context?, artistId: Int? = null) :
}
override fun cache(data: List<Album>) = AlbumsCache(data)
override fun uncache(reader: BufferedReader) =
gsonDeserializerOf(AlbumsCache::class.java).deserialize(reader)
override fun uncache(json: String) =
gsonDeserializerOf(AlbumsCache::class.java).deserialize(json.reader())
}
......@@ -9,7 +9,6 @@ import audio.funkwhale.ffa.utils.OAuth
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import com.google.gson.reflect.TypeToken
import org.koin.java.KoinJavaComponent.inject
import java.io.BufferedReader
class ArtistTracksRepository(override val context: Context?, private val artistId: Int) :
Repository<Track, TracksCache>() {
......@@ -27,6 +26,6 @@ class ArtistTracksRepository(override val context: Context?, private val artistI
)
override fun cache(data: List<Track>) = TracksCache(data)
override fun uncache(reader: BufferedReader) =
gsonDeserializerOf(TracksCache::class.java).deserialize(reader)
override fun uncache(json: String) =
gsonDeserializerOf(TracksCache::class.java).deserialize(json.reader())
}
......@@ -9,7 +9,6 @@ import audio.funkwhale.ffa.utils.OAuth
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import com.google.gson.reflect.TypeToken
import org.koin.java.KoinJavaComponent.inject
import java.io.BufferedReader
class ArtistsRepository(override val context: Context?) : Repository<Artist, ArtistsCache>() {
......@@ -26,6 +25,6 @@ class ArtistsRepository(override val context: Context?) : Repository<Artist, Art
)
override fun cache(data: List<Artist>) = ArtistsCache(data)
override fun uncache(reader: BufferedReader) =
gsonDeserializerOf(ArtistsCache::class.java).deserialize(reader)
override fun uncache(json: String) =
gsonDeserializerOf(ArtistsCache::class.java).deserialize(json.reader())
}
package audio.funkwhale.ffa.repositories
import android.content.Context
import audio.funkwhale.ffa.model.*
import audio.funkwhale.ffa.utils.*
import audio.funkwhale.ffa.model.FFAResponse
import audio.funkwhale.ffa.model.Favorite
import audio.funkwhale.ffa.model.FavoritesResponse
import audio.funkwhale.ffa.model.FavoritedCache
import audio.funkwhale.ffa.model.FavoritedResponse
import audio.funkwhale.ffa.model.FavoritesCache
import audio.funkwhale.ffa.utils.FFACache
import audio.funkwhale.ffa.utils.OAuth
import audio.funkwhale.ffa.utils.Settings
import audio.funkwhale.ffa.utils.authorize
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import audio.funkwhale.ffa.utils.mustNormalizeUrl
import audio.funkwhale.ffa.utils.untilNetwork
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitByteArrayResponseResult
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
......@@ -16,9 +27,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.koin.core.qualifier.named
import org.koin.java.KoinJavaComponent.inject
import java.io.BufferedReader
class FavoritesRepository(override val context: Context?) : Repository<Track, TracksCache>() {
class FavoritesRepository(override val context: Context?) : Repository<Favorite, FavoritesCache>() {
private val exoDownloadManager: DownloadManager by inject(DownloadManager::class.java)
private val exoCache: Cache by inject(Cache::class.java, named("exoCache"))
......@@ -26,34 +36,34 @@ class FavoritesRepository(override val context: Context?) : Repository<Track, Tr
override val cacheId = "favorites.v2"
override val upstream = HttpUpstream<Track, FFAResponse<Track>>(
override val upstream = HttpUpstream<Favorite, FFAResponse<Favorite>>(
context!!,
HttpUpstream.Behavior.AtOnce,
"/api/v1/tracks/?favorites=true&playable=true&ordering=title",
object : TypeToken<TracksResponse>() {}.type,
"/api/v1/favorites/tracks/?scope=all&ordering=-creation_date",
object : TypeToken<FavoritesResponse>() {}.type,
oAuth
)
override fun cache(data: List<Track>) = TracksCache(data)
override fun uncache(reader: BufferedReader) =
gsonDeserializerOf(TracksCache::class.java).deserialize(reader)
override fun cache(data: List<Favorite>) = FavoritesCache(data)
override fun uncache(json: String) =
gsonDeserializerOf(FavoritesCache::class.java).deserialize(json.reader())
private val favoritedRepository = FavoritedRepository(context!!)
override fun onDataFetched(data: List<Track>): List<Track> = runBlocking {
override fun onDataFetched(data: List<Favorite>): List<Favorite> = runBlocking {
val downloaded = TracksRepository.getDownloadedIds(exoDownloadManager) ?: listOf()
data.map { track ->
track.favorite = true
track.downloaded = downloaded.contains(track.id)
data.map { favorite ->
favorite.track.favorite = true
favorite.track.downloaded = downloaded.contains(favorite.track.id)
track.bestUpload()?.let { upload ->
favorite.track.bestUpload()?.let { upload ->
maybeNormalizeUrl(upload.listen_url)?.let { url ->
track.cached = exoCache.isCached(url, 0, upload.duration * 1000L)
favorite.track.cached = exoCache.isCached(url, 0, upload.duration * 1000L)
}
}
track
favorite
}
}
......@@ -116,12 +126,12 @@ class FavoritedRepository(override val context: Context?) : Repository<Int, Favo
)
override fun cache(data: List<Int>) = FavoritedCache(data)
override fun uncache(reader: BufferedReader) =
gsonDeserializerOf(FavoritedCache::class.java).deserialize(reader)
override fun uncache(json: String) =
gsonDeserializerOf(FavoritedCache::class.java).deserialize(json.reader())
fun update(context: Context?, scope: CoroutineScope) {
fetch(Origin.Network.origin).untilNetwork(scope, IO) { favorites, _, _, _ ->
FFACache.set(context, cacheId, Gson().toJson(cache(favorites)).toByteArray())
FFACache.set(context, cacheId, Gson().toJson(cache(favorites)).toString())
}
}
}
......@@ -4,7 +4,14 @@ import android.content.Context
import android.net.Uri
import android.util.Log
import audio.funkwhale.ffa.model.FFAResponse
import audio.funkwhale.ffa.utils.*
import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.utils.Event
import audio.funkwhale.ffa.utils.EventBus
import audio.funkwhale.ffa.utils.OAuth
import audio.funkwhale.ffa.utils.RefreshError
import audio.funkwhale.ffa.utils.Settings
import audio.funkwhale.ffa.utils.authorize
import audio.funkwhale.ffa.utils.mustNormalizeUrl
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.core.FuelError
import com.github.kittinunf.fuel.core.ResponseDeserializable
......
......@@ -12,7 +12,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.koin.java.KoinJavaComponent.inject
import java.io.BufferedReader
class PlaylistTracksRepository(override val context: Context?, playlistId: Int) :
Repository<PlaylistTrack, PlaylistTracksCache>() {
......@@ -30,8 +29,8 @@ class PlaylistTracksRepository(override val context: Context?, playlistId: Int)
)
override fun cache(data: List<PlaylistTrack>) = PlaylistTracksCache(data)
override fun uncache(reader: BufferedReader) =
gsonDeserializerOf(PlaylistTracksCache::class.java).deserialize(reader)
override fun uncache(json: String) =
gsonDeserializerOf(PlaylistTracksCache::class.java).deserialize(json.reader())
override fun onDataFetched(data: List<PlaylistTrack>): List<PlaylistTrack> = runBlocking {
val favorites = FavoritedRepository(context).fetch(Origin.Network.origin)
......