image: simplify implementation
Reduce the accepted datatype of extractors down to a list of songs, moving the other datatypes to the UI layer. This massively reduces the amount of components that must be managed, and enables functionality related to playlist editing.
This commit is contained in:
parent
996c86b361
commit
5fff1bd0b3
12 changed files with 129 additions and 287 deletions
|
@ -280,7 +280,13 @@ class PlaylistDetailFragment :
|
|||
|
||||
private fun updateEditedPlaylist(editedPlaylist: List<Song>?) {
|
||||
// TODO: Disable check item when no edits have been made
|
||||
// TODO: Improve how this state change looks
|
||||
|
||||
// TODO: Massively improve how this UI is indicated:
|
||||
// - Make playlist header dynamically respond to song changes
|
||||
// - Disable play and pause buttons
|
||||
// - Add an additional toolbar to indicate editing
|
||||
// - Header should flip to re-sort button eventually
|
||||
|
||||
playlistListAdapter.setEditing(editedPlaylist != null)
|
||||
}
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ constructor(
|
|||
target
|
||||
.onConfigRequest(
|
||||
ImageRequest.Builder(context)
|
||||
.data(song)
|
||||
.data(listOf(song))
|
||||
// Use ORIGINAL sizing, as we are not loading into any View-like component.
|
||||
.size(Size.ORIGINAL)
|
||||
.transformations(SquareFrameTransform.INSTANCE))
|
||||
|
|
|
@ -49,6 +49,9 @@ import org.oxycblt.auxio.util.getInteger
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*
|
||||
* TODO: Rework content descriptions here
|
||||
* TODO: Attempt unification with StyledImageView with some kind of dynamic configuration to avoid
|
||||
* superfluous elements
|
||||
* TODO: Handle non-square covers by gracefully placing them in the layout
|
||||
*/
|
||||
class ImageGroup
|
||||
@JvmOverloads
|
||||
|
|
|
@ -96,7 +96,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
*
|
||||
* @param song The [Song] to bind.
|
||||
*/
|
||||
fun bind(song: Song) = bindImpl(song, R.drawable.ic_song_24, R.string.desc_album_cover)
|
||||
fun bind(song: Song) = bind(song.album)
|
||||
|
||||
/**
|
||||
* Bind an [Album]'s cover to this view, also updating the content description.
|
||||
|
@ -130,15 +130,15 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
/**
|
||||
* Internally bind a [Music]'s image to this view.
|
||||
*
|
||||
* @param music The music to find.
|
||||
* @param parent The music to bind, in the form of it's [MusicParent]s.
|
||||
* @param errorRes The error drawable resource to use if the music cannot be loaded.
|
||||
* @param descRes The content description string resource to use. The resource must have one
|
||||
* field for the name of the [Music].
|
||||
*/
|
||||
private fun bindImpl(music: Music, @DrawableRes errorRes: Int, @StringRes descRes: Int) {
|
||||
private fun bindImpl(parent: MusicParent, @DrawableRes errorRes: Int, @StringRes descRes: Int) {
|
||||
val request =
|
||||
ImageRequest.Builder(context)
|
||||
.data(music)
|
||||
.data(parent.songs)
|
||||
.error(StyledDrawable(context, context.getDrawableCompat(errorRes)))
|
||||
.transformations(SquareFrameTransform.INSTANCE)
|
||||
.target(this)
|
||||
|
@ -147,7 +147,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
CoilUtils.dispose(this)
|
||||
imageLoader.enqueue(request)
|
||||
// Update the content description to the specified resource.
|
||||
contentDescription = context.getString(descRes, music.name.resolve(context))
|
||||
contentDescription = context.getString(descRes, parent.name.resolve(context))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,163 +18,31 @@
|
|||
|
||||
package org.oxycblt.auxio.image.extractor
|
||||
|
||||
import android.content.Context
|
||||
import coil.ImageLoader
|
||||
import coil.decode.DataSource
|
||||
import coil.decode.ImageSource
|
||||
import coil.fetch.FetchResult
|
||||
import coil.fetch.Fetcher
|
||||
import coil.fetch.SourceResult
|
||||
import coil.key.Keyer
|
||||
import coil.request.Options
|
||||
import coil.size.Size
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.min
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.oxycblt.auxio.list.Sort
|
||||
import org.oxycblt.auxio.music.*
|
||||
|
||||
class SongKeyer @Inject constructor() : Keyer<Song> {
|
||||
override fun key(data: Song, options: Options) = "${data.album.uid}${data.album.hashCode()}"
|
||||
class SongKeyer @Inject constructor(private val coverExtractor: CoverExtractor) :
|
||||
Keyer<List<Song>> {
|
||||
override fun key(data: List<Song>, options: Options) =
|
||||
"${coverExtractor.computeAlbumOrdering(data).hashCode()}"
|
||||
}
|
||||
|
||||
// TODO: Key on the actual mosaic items used
|
||||
class ParentKeyer @Inject constructor() : Keyer<MusicParent> {
|
||||
override fun key(data: MusicParent, options: Options) = "${data.uid}${data.hashCode()}"
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic [Fetcher] for [Album] covers. Works with both [Album] and [Song]. Use [SongFactory] or
|
||||
* [AlbumFactory] for instantiation.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class AlbumCoverFetcher
|
||||
class SongCoverFetcher
|
||||
private constructor(
|
||||
private val context: Context,
|
||||
private val extractor: CoverExtractor,
|
||||
private val album: Album
|
||||
) : Fetcher {
|
||||
override suspend fun fetch(): FetchResult? =
|
||||
extractor.extract(album)?.run {
|
||||
SourceResult(
|
||||
source = ImageSource(source().buffer(), context),
|
||||
mimeType = null,
|
||||
dataSource = DataSource.DISK)
|
||||
}
|
||||
|
||||
class SongFactory @Inject constructor(private val coverExtractor: CoverExtractor) :
|
||||
Fetcher.Factory<Song> {
|
||||
override fun create(data: Song, options: Options, imageLoader: ImageLoader) =
|
||||
AlbumCoverFetcher(options.context, coverExtractor, data.album)
|
||||
}
|
||||
|
||||
class AlbumFactory @Inject constructor(private val coverExtractor: CoverExtractor) :
|
||||
Fetcher.Factory<Album> {
|
||||
override fun create(data: Album, options: Options, imageLoader: ImageLoader) =
|
||||
AlbumCoverFetcher(options.context, coverExtractor, data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [Fetcher] for [Artist] images. Use [Factory] for instantiation.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class ArtistImageFetcher
|
||||
private constructor(
|
||||
private val context: Context,
|
||||
private val extractor: CoverExtractor,
|
||||
private val songs: List<Song>,
|
||||
private val size: Size,
|
||||
private val artist: Artist
|
||||
private val coverExtractor: CoverExtractor,
|
||||
) : Fetcher {
|
||||
override suspend fun fetch(): FetchResult? {
|
||||
// Pick the "most prominent" albums (i.e albums with the most songs) to show in the image.
|
||||
val albums = Sort(Sort.Mode.ByCount, Sort.Direction.DESCENDING).albums(artist.albums)
|
||||
val results = albums.mapAtMostNotNull(4) { album -> extractor.extract(album) }
|
||||
return Images.createMosaic(context, results, size)
|
||||
}
|
||||
override suspend fun fetch() = coverExtractor.extract(songs, size)
|
||||
|
||||
class Factory @Inject constructor(private val extractor: CoverExtractor) :
|
||||
Fetcher.Factory<Artist> {
|
||||
override fun create(data: Artist, options: Options, imageLoader: ImageLoader) =
|
||||
ArtistImageFetcher(options.context, extractor, options.size, data)
|
||||
class Factory @Inject constructor(private val coverExtractor: CoverExtractor) :
|
||||
Fetcher.Factory<List<Song>> {
|
||||
override fun create(data: List<Song>, options: Options, imageLoader: ImageLoader) =
|
||||
SongCoverFetcher(data, options.size, coverExtractor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [Fetcher] for [Genre] images. Use [Factory] for instantiation.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class GenreImageFetcher
|
||||
private constructor(
|
||||
private val context: Context,
|
||||
private val extractor: CoverExtractor,
|
||||
private val size: Size,
|
||||
private val genre: Genre
|
||||
) : Fetcher {
|
||||
override suspend fun fetch(): FetchResult? {
|
||||
val results = genre.albums.mapAtMostNotNull(4) { album -> extractor.extract(album) }
|
||||
return Images.createMosaic(context, results, size)
|
||||
}
|
||||
|
||||
class Factory @Inject constructor(private val extractor: CoverExtractor) :
|
||||
Fetcher.Factory<Genre> {
|
||||
override fun create(data: Genre, options: Options, imageLoader: ImageLoader) =
|
||||
GenreImageFetcher(options.context, extractor, options.size, data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [Fetcher] for [Playlist] images. Use [Factory] for instantiation.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class PlaylistImageFetcher
|
||||
private constructor(
|
||||
private val context: Context,
|
||||
private val extractor: CoverExtractor,
|
||||
private val size: Size,
|
||||
private val playlist: Playlist
|
||||
) : Fetcher {
|
||||
override suspend fun fetch(): FetchResult? {
|
||||
val results = playlist.albums.mapAtMostNotNull(4) { album -> extractor.extract(album) }
|
||||
return Images.createMosaic(context, results, size)
|
||||
}
|
||||
|
||||
class Factory @Inject constructor(private val extractor: CoverExtractor) :
|
||||
Fetcher.Factory<Playlist> {
|
||||
override fun create(data: Playlist, options: Options, imageLoader: ImageLoader) =
|
||||
PlaylistImageFetcher(options.context, extractor, options.size, data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map at most N [T] items a collection into a collection of [R], ignoring [T] that cannot be
|
||||
* transformed into [R].
|
||||
*
|
||||
* @param n The maximum amount of items to map.
|
||||
* @param transform The function that transforms data [T] from the original list into data [R] in
|
||||
* the new list. Can return null if the [T] cannot be transformed into an [R].
|
||||
* @return A new list of at most N non-null [R] items.
|
||||
*/
|
||||
private inline fun <T : Any, R : Any> Collection<T>.mapAtMostNotNull(
|
||||
n: Int,
|
||||
transform: (T) -> R?
|
||||
): List<R> {
|
||||
val until = min(size, n)
|
||||
val out = mutableListOf<R>()
|
||||
|
||||
for (item in this) {
|
||||
if (out.size >= until) {
|
||||
break
|
||||
}
|
||||
|
||||
// Still have more data we can transform.
|
||||
transform(item)?.let(out::add)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -19,13 +19,26 @@
|
|||
package org.oxycblt.auxio.image.extractor
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.util.Size as AndroidSize
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.exoplayer.MetadataRetriever
|
||||
import androidx.media3.exoplayer.source.MediaSource
|
||||
import androidx.media3.extractor.metadata.flac.PictureFrame
|
||||
import androidx.media3.extractor.metadata.id3.ApicFrame
|
||||
import coil.decode.DataSource
|
||||
import coil.decode.ImageSource
|
||||
import coil.fetch.DrawableResult
|
||||
import coil.fetch.FetchResult
|
||||
import coil.fetch.SourceResult
|
||||
import coil.size.Dimension
|
||||
import coil.size.Size
|
||||
import coil.size.pxOrElse
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
|
@ -33,9 +46,12 @@ import javax.inject.Inject
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.guava.asDeferred
|
||||
import kotlinx.coroutines.withContext
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.oxycblt.auxio.image.CoverMode
|
||||
import org.oxycblt.auxio.image.ImageSettings
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
|
||||
|
@ -46,7 +62,28 @@ constructor(
|
|||
private val imageSettings: ImageSettings,
|
||||
private val mediaSourceFactory: MediaSource.Factory
|
||||
) {
|
||||
suspend fun extract(album: Album): InputStream? =
|
||||
suspend fun extract(songs: List<Song>, size: Size): FetchResult? {
|
||||
val albums = computeAlbumOrdering(songs)
|
||||
val streams = mutableListOf<InputStream>()
|
||||
for (album in albums) {
|
||||
if (streams.size == 4) {
|
||||
return createMosaic(streams, size)
|
||||
}
|
||||
openInputStream(album)?.let(streams::add)
|
||||
}
|
||||
|
||||
return streams.firstOrNull()?.let { stream ->
|
||||
SourceResult(
|
||||
source = ImageSource(stream.source().buffer(), context),
|
||||
mimeType = null,
|
||||
dataSource = DataSource.DISK)
|
||||
}
|
||||
}
|
||||
|
||||
fun computeAlbumOrdering(songs: List<Song>): Collection<Album> =
|
||||
songs.groupByTo(sortedMapOf(compareByDescending { it.songs.size })) { it.album }.keys
|
||||
|
||||
private suspend fun openInputStream(album: Album): InputStream? =
|
||||
try {
|
||||
when (imageSettings.coverMode) {
|
||||
CoverMode.OFF -> null
|
||||
|
@ -125,4 +162,58 @@ constructor(
|
|||
private suspend fun extractMediaStoreCover(album: Album) =
|
||||
// Eliminate any chance that this blocking call might mess up the loading process
|
||||
withContext(Dispatchers.IO) { context.contentResolver.openInputStream(album.coverUri) }
|
||||
|
||||
/** Derived from phonograph: https://github.com/kabouzeid/Phonograph */
|
||||
private suspend fun createMosaic(streams: List<InputStream>, size: Size): FetchResult {
|
||||
// Use whatever size coil gives us to create the mosaic.
|
||||
val mosaicSize = AndroidSize(size.width.mosaicSize(), size.height.mosaicSize())
|
||||
val mosaicFrameSize =
|
||||
Size(Dimension(mosaicSize.width / 2), Dimension(mosaicSize.height / 2))
|
||||
|
||||
val mosaicBitmap =
|
||||
Bitmap.createBitmap(mosaicSize.width, mosaicSize.height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(mosaicBitmap)
|
||||
|
||||
var x = 0
|
||||
var y = 0
|
||||
|
||||
// For each stream, create a bitmap scaled to 1/4th of the mosaics combined size
|
||||
// and place it on a corner of the canvas.
|
||||
for (stream in streams) {
|
||||
if (y == mosaicSize.height) {
|
||||
break
|
||||
}
|
||||
|
||||
// Run the bitmap through a transform to reflect the configuration of other images.
|
||||
val bitmap =
|
||||
SquareFrameTransform.INSTANCE.transform(
|
||||
BitmapFactory.decodeStream(stream), mosaicFrameSize)
|
||||
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
|
||||
|
||||
x += bitmap.width
|
||||
if (x == mosaicSize.width) {
|
||||
x = 0
|
||||
y += bitmap.height
|
||||
}
|
||||
}
|
||||
|
||||
// It's way easier to map this into a drawable then try to serialize it into an
|
||||
// BufferedSource. Just make sure we mark it as "sampled" so Coil doesn't try to
|
||||
// load low-res mosaics into high-res ImageViews.
|
||||
return DrawableResult(
|
||||
drawable = mosaicBitmap.toDrawable(context.resources),
|
||||
isSampled = true,
|
||||
dataSource = DataSource.DISK)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an image dimension suitable to create a mosaic with.
|
||||
*
|
||||
* @return A pixel dimension derived from the given [Dimension] that will always be even,
|
||||
* allowing it to be sub-divided.
|
||||
*/
|
||||
private fun Dimension.mosaicSize(): Int {
|
||||
val size = pxOrElse { 512 }
|
||||
return if (size.mod(2) > 0) size + 1 else size
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,23 +36,13 @@ class ExtractorModule {
|
|||
fun imageLoader(
|
||||
@ApplicationContext context: Context,
|
||||
songKeyer: SongKeyer,
|
||||
parentKeyer: ParentKeyer,
|
||||
songFactory: AlbumCoverFetcher.SongFactory,
|
||||
albumFactory: AlbumCoverFetcher.AlbumFactory,
|
||||
artistFactory: ArtistImageFetcher.Factory,
|
||||
genreFactory: GenreImageFetcher.Factory,
|
||||
playlistFactory: PlaylistImageFetcher.Factory
|
||||
songFactory: SongCoverFetcher.Factory
|
||||
) =
|
||||
ImageLoader.Builder(context)
|
||||
.components {
|
||||
// Add fetchers for Music components to make them usable with ImageRequest
|
||||
add(songKeyer)
|
||||
add(parentKeyer)
|
||||
add(songFactory)
|
||||
add(albumFactory)
|
||||
add(artistFactory)
|
||||
add(genreFactory)
|
||||
add(playlistFactory)
|
||||
}
|
||||
// Use our own crossfade with error drawable support
|
||||
.transitionFactory(ErrorCrossfadeTransitionFactory())
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Auxio Project
|
||||
* Images.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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.image.extractor
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.util.Size as AndroidSize
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import coil.decode.DataSource
|
||||
import coil.decode.ImageSource
|
||||
import coil.fetch.DrawableResult
|
||||
import coil.fetch.FetchResult
|
||||
import coil.fetch.SourceResult
|
||||
import coil.size.Dimension
|
||||
import coil.size.Size
|
||||
import coil.size.pxOrElse
|
||||
import java.io.InputStream
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
|
||||
/**
|
||||
* Utilities for constructing Artist and Genre images.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt), Karim Abou Zeid
|
||||
*/
|
||||
object Images {
|
||||
/**
|
||||
* Create a mosaic image from the given image [InputStream]s. Derived from phonograph:
|
||||
* https://github.com/kabouzeid/Phonograph
|
||||
*
|
||||
* @param context [Context] required to generate the mosaic.
|
||||
* @param streams [InputStream]s of image data to create the mosaic out of.
|
||||
* @param size [Size] of the Mosaic to generate.
|
||||
*/
|
||||
suspend fun createMosaic(
|
||||
context: Context,
|
||||
streams: List<InputStream>,
|
||||
size: Size
|
||||
): FetchResult? {
|
||||
if (streams.size < 4) {
|
||||
return streams.firstOrNull()?.let { stream ->
|
||||
SourceResult(
|
||||
source = ImageSource(stream.source().buffer(), context),
|
||||
mimeType = null,
|
||||
dataSource = DataSource.DISK)
|
||||
}
|
||||
}
|
||||
|
||||
// Use whatever size coil gives us to create the mosaic.
|
||||
val mosaicSize = AndroidSize(size.width.mosaicSize(), size.height.mosaicSize())
|
||||
val mosaicFrameSize =
|
||||
Size(Dimension(mosaicSize.width / 2), Dimension(mosaicSize.height / 2))
|
||||
|
||||
val mosaicBitmap =
|
||||
Bitmap.createBitmap(mosaicSize.width, mosaicSize.height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(mosaicBitmap)
|
||||
|
||||
var x = 0
|
||||
var y = 0
|
||||
|
||||
// For each stream, create a bitmap scaled to 1/4th of the mosaics combined size
|
||||
// and place it on a corner of the canvas.
|
||||
for (stream in streams) {
|
||||
if (y == mosaicSize.height) {
|
||||
break
|
||||
}
|
||||
|
||||
// Run the bitmap through a transform to reflect the configuration of other images.
|
||||
val bitmap =
|
||||
SquareFrameTransform.INSTANCE.transform(
|
||||
BitmapFactory.decodeStream(stream), mosaicFrameSize)
|
||||
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
|
||||
|
||||
x += bitmap.width
|
||||
if (x == mosaicSize.width) {
|
||||
x = 0
|
||||
y += bitmap.height
|
||||
}
|
||||
}
|
||||
|
||||
// It's way easier to map this into a drawable then try to serialize it into an
|
||||
// BufferedSource. Just make sure we mark it as "sampled" so Coil doesn't try to
|
||||
// load low-res mosaics into high-res ImageViews.
|
||||
return DrawableResult(
|
||||
drawable = mosaicBitmap.toDrawable(context.resources),
|
||||
isSampled = true,
|
||||
dataSource = DataSource.DISK)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an image dimension suitable to create a mosaic with.
|
||||
*
|
||||
* @return A pixel dimension derived from the given [Dimension] that will always be even,
|
||||
* allowing it to be sub-divided.
|
||||
*/
|
||||
private fun Dimension.mosaicSize(): Int {
|
||||
val size = pxOrElse { 512 }
|
||||
return if (size.mod(2) > 0) size + 1 else size
|
||||
}
|
||||
}
|
|
@ -35,6 +35,8 @@ import org.oxycblt.auxio.util.logD
|
|||
* current selection state.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*
|
||||
* TODO: Generalize this into a "view flipper" class and then derive it through other means?
|
||||
*/
|
||||
class SelectionToolbarOverlay
|
||||
@JvmOverloads
|
||||
|
|
|
@ -147,7 +147,7 @@ private class UserLibraryImpl(
|
|||
|
||||
@Synchronized
|
||||
override fun deletePlaylist(playlist: Playlist) {
|
||||
playlistMap.remove(playlist.uid)
|
||||
requireNotNull(playlistMap.remove(playlist.uid)) { "Cannot remove invalid playlist" }
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
|
|
@ -36,12 +36,12 @@ interface UserModule {
|
|||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class UserRoomModule {
|
||||
@Provides fun playlistDao(database: PlaylistDatabase) = database.playlistDao()
|
||||
@Provides fun playlistDao(database: UserMusicDatabase) = database.playlistDao()
|
||||
|
||||
@Provides
|
||||
fun playlistDatabase(@ApplicationContext context: Context) =
|
||||
fun userMusicDatabase(@ApplicationContext context: Context) =
|
||||
Room.databaseBuilder(
|
||||
context.applicationContext, PlaylistDatabase::class.java, "playlists.db")
|
||||
context.applicationContext, UserMusicDatabase::class.java, "user_music.db")
|
||||
.fallbackToDestructiveMigration()
|
||||
.fallbackToDestructiveMigrationFrom(0)
|
||||
.fallbackToDestructiveMigrationOnDowngrade()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* PlaylistDatabase.kt is part of Auxio.
|
||||
* UserMusicDatabase.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
|
||||
|
@ -26,7 +26,7 @@ import org.oxycblt.auxio.music.Music
|
|||
version = 28,
|
||||
exportSchema = false)
|
||||
@TypeConverters(Music.UID.TypeConverters::class)
|
||||
abstract class PlaylistDatabase : RoomDatabase() {
|
||||
abstract class UserMusicDatabase : RoomDatabase() {
|
||||
abstract fun playlistDao(): PlaylistDao
|
||||
}
|
||||
|
Loading…
Reference in a new issue