From e4339a76bf5a25ec6800cf48fe51dea9dc6ba28a Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Sun, 19 Mar 2023 19:50:34 -0600 Subject: [PATCH] playlist: add runtime boilerplate Add the boilerplate code for the playlist system. Any functionality is runtime only and is not integrated in-app. Plan is to build the UI scaffold first followed by the backing playlist. --- .../org/oxycblt/auxio/music/system/Indexer.kt | 16 ++-- .../playback/persist/PersistenceDatabase.kt | 2 +- .../org/oxycblt/auxio/playlist/Playlist.kt | 29 +++++++ .../oxycblt/auxio/playlist/PlaylistModule.kt | 31 ++++++++ .../auxio/playlist/PlaylistRepository.kt | 77 +++++++++++++++++++ 5 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/playlist/Playlist.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/playlist/PlaylistModule.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/playlist/PlaylistRepository.kt diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt index 321724d2c..8928a059e 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt @@ -144,9 +144,11 @@ interface Indexer { * * @param result The outcome of the music loading process. */ - data class Complete(val result: Result) : State() + data class Complete(val result: Result) : State() } + data class Response(val result: Library, val playlists: List) + /** * Represents the current progress of the music loader. Usually encapsulated in a [State]. * @@ -237,7 +239,7 @@ constructor( private val mediaStoreExtractor: MediaStoreExtractor, private val tagExtractor: TagExtractor ) : Indexer { - @Volatile private var lastResponse: Result? = null + @Volatile private var lastResponse: Result? = null @Volatile private var indexingState: Indexer.Indexing? = null @Volatile private var controller: Indexer.Controller? = null @Volatile private var listener: Indexer.Listener? = null @@ -303,11 +305,11 @@ constructor( val result = try { val start = System.currentTimeMillis() - val library = indexImpl(context, withCache, this) + val response = indexImpl(context, withCache, this) logD( "Music indexing completed successfully in " + "${System.currentTimeMillis() - start}ms") - Result.success(library) + Result.success(response) } catch (e: CancellationException) { // Got cancelled, propagate upwards to top-level co-routine. logD("Loading routine was cancelled") @@ -337,7 +339,7 @@ constructor( context: Context, withCache: Boolean, scope: CoroutineScope - ): Library { + ): Indexer.Response { if (ContextCompat.checkSelfPermission(context, Indexer.PERMISSION_READ_AUDIO) == PackageManager.PERMISSION_DENIED) { logE("Permission check failed") @@ -395,7 +397,7 @@ constructor( if (cache == null || cache.invalidated) { cacheRepository.writeCache(rawSongs) } - return libraryJob.await() + return Indexer.Response(libraryJob.await(), listOf()) } /** @@ -426,7 +428,7 @@ constructor( * @param result The new [Result] to emit, representing the outcome of the music loading * process. */ - private suspend fun emitCompletion(result: Result) { + private suspend fun emitCompletion(result: Result) { yield() // Swap to the Main thread so that downstream callbacks don't crash from being on // a background thread. Does not occur in emitIndexing due to efficiency reasons. diff --git a/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceDatabase.kt b/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceDatabase.kt index a61731213..3ac52c1fd 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceDatabase.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceDatabase.kt @@ -40,7 +40,7 @@ import org.oxycblt.auxio.playback.state.RepeatMode entities = [PlaybackState::class, QueueHeapItem::class, QueueMappingItem::class], version = 27, exportSchema = false) -@TypeConverters(PersistenceDatabase.Converters::class) +@TypeConverters(Music.UID.Converter::class) abstract class PersistenceDatabase : RoomDatabase() { /** * Get the current [PlaybackStateDao]. diff --git a/app/src/main/java/org/oxycblt/auxio/playlist/Playlist.kt b/app/src/main/java/org/oxycblt/auxio/playlist/Playlist.kt new file mode 100644 index 000000000..d7e4d9303 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playlist/Playlist.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 Auxio Project + * Playlist.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.playlist + +import java.util.UUID +import org.oxycblt.auxio.music.Song + +interface Playlist { + val id: UUID + val name: String + val songs: List +} + diff --git a/app/src/main/java/org/oxycblt/auxio/playlist/PlaylistModule.kt b/app/src/main/java/org/oxycblt/auxio/playlist/PlaylistModule.kt new file mode 100644 index 000000000..fba4dc489 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playlist/PlaylistModule.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Auxio Project + * PlaylistModule.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.playlist + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +interface PlaylistModule { + @Binds + fun playlistRepository(playlistRepositoryImpl: PlaylistRepositoryImpl): PlaylistRepository +} diff --git a/app/src/main/java/org/oxycblt/auxio/playlist/PlaylistRepository.kt b/app/src/main/java/org/oxycblt/auxio/playlist/PlaylistRepository.kt new file mode 100644 index 000000000..90fcb5f0c --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playlist/PlaylistRepository.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Auxio Project + * PlaylistRepository.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.playlist + +import java.util.UUID +import javax.inject.Inject +import org.oxycblt.auxio.music.Song + +interface PlaylistRepository { + val playlists: List + suspend fun createPlaylist(name: String, songs: List) + suspend fun deletePlaylist(playlist: Playlist) + suspend fun addToPlaylist(playlist: Playlist, songs: List) + suspend fun removeFromPlaylist(playlist: Playlist, song: Song) + suspend fun rewritePlaylist(playlist: Playlist, songs: List) +} + +class PlaylistRepositoryImpl @Inject constructor() : PlaylistRepository { + private val playlistMap = mutableMapOf() + override val playlists: List + get() = playlistMap.values.toList() + + override suspend fun createPlaylist(name: String, songs: List) { + val uuid = UUID.randomUUID() + playlistMap[uuid] = PlaylistImpl(uuid, name, songs) + } + + override suspend fun deletePlaylist(playlist: Playlist) { + playlistMap.remove(playlist.id) + } + + override suspend fun addToPlaylist(playlist: Playlist, songs: List) { + editPlaylist(playlist) { + addAll(songs) + this + } + } + + override suspend fun removeFromPlaylist(playlist: Playlist, song: Song) { + editPlaylist(playlist) { + remove(song) + this + } + } + + override suspend fun rewritePlaylist(playlist: Playlist, songs: List) { + editPlaylist(playlist) { songs } + } + + private inline fun editPlaylist(playlist: Playlist, edits: MutableList.() -> List) { + check(playlistMap.containsKey(playlist.id)) { "Invalid playlist argument provided" } + playlistMap[playlist.id] = + PlaylistImpl(playlist.id, playlist.name, edits(playlist.songs.toMutableList())) + } +} + +private data class PlaylistImpl( + override val id: UUID, + override val name: String, + override val songs: List +) : Playlist