mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
add star/unstar and long-press item menus to the library lists
Star is a new write path: MusicSource.setStar posts star.view/unstar.view and LibraryRepository updates the local row on success (toast on failure). Long-press an album, artist, song or playlist in the library to open a bottom sheet with play/shuffle, star/unstar and view-album/view-artist.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
package com.subtracks.data.repo
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room3.Room
|
||||
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.subtracks.data.db.SubtracksDatabase
|
||||
import com.subtracks.data.model.Song
|
||||
import com.subtracks.data.prefs.fakeUserPreferences
|
||||
import com.subtracks.data.source.StarType
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LibraryRepositoryTest {
|
||||
private lateinit var db: SubtracksDatabase
|
||||
private lateinit var sourceRepository: SourceRepository
|
||||
private lateinit var repository: LibraryRepository
|
||||
private val messages = ArrayList<String>()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db =
|
||||
Room
|
||||
.inMemoryDatabaseBuilder(context, SubtracksDatabase::class.java)
|
||||
.setDriver(BundledSQLiteDriver())
|
||||
.build()
|
||||
sourceRepository = SourceRepository(db, OkHttpClient(), fakeUserPreferences())
|
||||
repository = LibraryRepository(db, sourceRepository, showMessage = { messages += it })
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
sourceRepository.close()
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun starringASongPostsToTheServerAndUpdatesTheRow() =
|
||||
runBlocking {
|
||||
withSource { server ->
|
||||
server.enqueue(ok())
|
||||
|
||||
assertTrue(repository.setStar(StarType.Song, "s1", true).isSuccess)
|
||||
|
||||
val request = server.takeRequest()
|
||||
assertEquals("/rest/star.view", request.requestUrl!!.encodedPath)
|
||||
assertEquals("s1", request.requestUrl!!.queryParameter("id"))
|
||||
assertNotNull(starred())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unstarringPostsToTheServerAndClearsTheRow() =
|
||||
runBlocking {
|
||||
withSource { server ->
|
||||
server.enqueue(ok())
|
||||
repository.setStar(StarType.Song, "s1", true)
|
||||
server.enqueue(ok())
|
||||
|
||||
repository.setStar(StarType.Song, "s1", false)
|
||||
|
||||
server.takeRequest()
|
||||
assertEquals("/rest/unstar.view", server.takeRequest().requestUrl!!.encodedPath)
|
||||
assertNull(starred())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aRejectedStarLeavesTheRowUntouchedAndReports() =
|
||||
runBlocking {
|
||||
withSource { server ->
|
||||
server.enqueue(failed(40))
|
||||
|
||||
assertTrue(repository.setStar(StarType.Song, "s1", true).isFailure)
|
||||
|
||||
assertNull(starred())
|
||||
assertEquals(listOf("Could not update star"), messages)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun withSource(block: suspend (MockWebServer) -> Unit) {
|
||||
val server = MockWebServer()
|
||||
server.start()
|
||||
try {
|
||||
val sourceId = sourceRepository.addSource("nav", server.url("/").toString(), "u", "p", false)
|
||||
db.libraryDao().upsertSongs(listOf(seedSong(sourceId)))
|
||||
block(server)
|
||||
} finally {
|
||||
server.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun starred(): Long? =
|
||||
repository
|
||||
.albumSongs(sourceRepository.activeSourceIdOnce()!!, "al1")
|
||||
.first()
|
||||
.first { it.id == "s1" }
|
||||
.starred
|
||||
|
||||
private fun seedSong(sourceId: Long) =
|
||||
Song(
|
||||
sourceId = sourceId,
|
||||
id = "s1",
|
||||
albumId = "al1",
|
||||
artistId = "ar1",
|
||||
title = "Track",
|
||||
album = "Album",
|
||||
artist = "Artist",
|
||||
duration = 1_000,
|
||||
track = 1,
|
||||
disc = 1,
|
||||
starred = null,
|
||||
genre = null,
|
||||
)
|
||||
|
||||
private fun ok() = MockResponse().setBody("<subsonic-response status=\"ok\" version=\"1.16.1\"/>")
|
||||
|
||||
private fun failed(code: Int) =
|
||||
MockResponse().setBody("<subsonic-response status=\"failed\"><error code=\"$code\" message=\"nope\"/></subsonic-response>")
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import com.subtracks.data.model.PlaylistSong
|
||||
import com.subtracks.data.model.Song
|
||||
import com.subtracks.data.model.Source
|
||||
import com.subtracks.data.source.MusicSource
|
||||
import com.subtracks.data.source.StarType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
@@ -679,6 +680,12 @@ private class FakeMusicSource(
|
||||
|
||||
override suspend fun ping() = Unit
|
||||
|
||||
override suspend fun setStar(
|
||||
type: StarType,
|
||||
id: String,
|
||||
starred: Boolean,
|
||||
) = Unit
|
||||
|
||||
override fun artists(): Flow<List<Artist>> = batches(artists)
|
||||
|
||||
override fun albums(): Flow<List<Album>> = batches(albums)
|
||||
|
||||
Reference in New Issue
Block a user