From e0485ebad93ece569637d734e7f6bdcd4fc6169f Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Fri, 12 Feb 2021 22:28:41 -0700 Subject: [PATCH] Refactor coil Completely refactor coil and how its used so that it centers around data objects instead of a hodgepodge of URIs and Song data. --- .../org/oxycblt/auxio/coil/AlbumArtFetcher.kt | 81 +++++++ .../java/org/oxycblt/auxio/coil/CoilUtils.kt | 185 +++++----------- .../org/oxycblt/auxio/coil/MosaicFetcher.kt | 73 ++++-- .../oxycblt/auxio/coil/QualityCoverFetcher.kt | 77 ------- .../auxio/playback/NotificationUtils.kt | 4 +- .../oxycblt/auxio/playback/PlaybackService.kt | 4 +- .../layout-land/fragment_compact_playback.xml | 2 +- .../res/layout-land/fragment_playback.xml | 2 +- .../res/layout-land/item_album_header.xml | 2 +- .../res/layout-large/fragment_playback.xml | 208 ------------------ .../res/layout-large/item_album_header.xml | 2 +- .../fragment_playback.xml | 2 +- .../res/layout/fragment_compact_playback.xml | 2 +- app/src/main/res/layout/fragment_playback.xml | 2 +- app/src/main/res/layout/item_album.xml | 2 +- app/src/main/res/layout/item_album_header.xml | 2 +- app/src/main/res/layout/item_artist_album.xml | 2 +- app/src/main/res/layout/item_genre_song.xml | 2 +- app/src/main/res/layout/item_queue_song.xml | 2 +- app/src/main/res/layout/item_song.xml | 2 +- app/src/main/res/values/dimens.xml | 2 +- info/ADDITIONS.md | 2 +- info/ARCHITECTURE.md | 10 +- 23 files changed, 211 insertions(+), 461 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/coil/AlbumArtFetcher.kt delete mode 100644 app/src/main/java/org/oxycblt/auxio/coil/QualityCoverFetcher.kt delete mode 100644 app/src/main/res/layout-large/fragment_playback.xml rename app/src/main/res/{layout-large-land => layout-xlarge-land}/fragment_playback.xml (99%) diff --git a/app/src/main/java/org/oxycblt/auxio/coil/AlbumArtFetcher.kt b/app/src/main/java/org/oxycblt/auxio/coil/AlbumArtFetcher.kt new file mode 100644 index 000000000..eda026938 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/coil/AlbumArtFetcher.kt @@ -0,0 +1,81 @@ +package org.oxycblt.auxio.coil + +import android.content.Context +import android.media.MediaMetadataRetriever +import coil.bitmap.BitmapPool +import coil.decode.DataSource +import coil.decode.Options +import coil.fetch.FetchResult +import coil.fetch.Fetcher +import coil.fetch.SourceResult +import coil.size.Size +import okio.buffer +import okio.source +import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.music.toURI +import org.oxycblt.auxio.settings.SettingsManager +import java.io.ByteArrayInputStream +import java.io.InputStream + +/** + * Fetcher that returns the album art for a given [Album]. Handles settings on whether to use + * quality covers or not. + */ +class AlbumArtFetcher(private val context: Context) : Fetcher { + private val settingsManager = SettingsManager.getInstance() + + override suspend fun fetch( + pool: BitmapPool, + data: Album, + size: Size, + options: Options + ): FetchResult { + return if (settingsManager.useQualityCovers) { + loadQualityCovers(data) + } else { + loadMediaStoreCovers(data) + } + } + + private fun loadMediaStoreCovers(data: Album): SourceResult { + val stream: InputStream? = context.contentResolver.openInputStream(data.coverUri) + + stream?.let { stm -> + return SourceResult( + source = stm.source().buffer(), + mimeType = context.contentResolver.getType(data.coverUri), + dataSource = DataSource.DISK + ) + } + + error("No cover art for album ${data.name}") + } + + private fun loadQualityCovers(data: Album): SourceResult { + val extractor = MediaMetadataRetriever() + + extractor.use { ext -> + val songUri = data.songs[0].id.toURI() + ext.setDataSource(context, songUri) + + // Get the embedded picture from MediaMetadataRetriever, which will return a full + // ByteArray of the cover without any compression artifacts. + // If its null [a.k.a there is no embedded cover], than just ignore it and move on + ext.embeddedPicture?.let { coverBytes -> + val stream = ByteArrayInputStream(coverBytes) + + return SourceResult( + source = stream.source().buffer(), + mimeType = context.contentResolver.getType(songUri), + dataSource = DataSource.DISK + ) + } + } + + // If we are here, the extractor likely failed so instead attempt to return the compressed + // cover instead. + return loadMediaStoreCovers(data) + } + + override fun key(data: Album) = data.id.toString() +} diff --git a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt index 27d8a266b..651c71bb2 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt @@ -2,7 +2,6 @@ package org.oxycblt.auxio.coil import android.content.Context import android.graphics.Bitmap -import android.net.Uri import android.widget.ImageView import androidx.core.graphics.drawable.toBitmap import androidx.databinding.BindingAdapter @@ -15,185 +14,111 @@ import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.settings.SettingsManager +private val ignoreCovers: Boolean get() = !SettingsManager.getInstance().showCovers + /** * Get a bitmap for a song. onDone will be called when the bitmap is loaded. - * **Do not use this on the UI elements, instead use the Binding Adapters.** + * **This not meant for UIs, instead use the Binding Adapters.** * @param context [Context] required * @param song Song to load the cover for * @param onDone What to do with the bitmap when the loading is finished. Bitmap will be null if loading failed/shouldn't occur. */ -fun getBitmap(context: Context, song: Song, onDone: (Bitmap?) -> Unit) { - if (!SettingsManager.getInstance().showCovers) { +fun loadBitmap(context: Context, song: Song, onDone: (Bitmap?) -> Unit) { + if (ignoreCovers) { onDone(null) - return } - val request = ImageRequest.Builder(context) - .doCoverSetup(context, song) - .target(onError = { onDone(null) }, onSuccess = { onDone(it.toBitmap()) }) - .build() - - Coil.imageLoader(context).enqueue(request) + Coil.imageLoader(context).enqueue( + ImageRequest.Builder(context) + .data(song.album) + .fetcher(AlbumArtFetcher(context)) + .target( + onError = { onDone(null) }, + onSuccess = { onDone(it.toBitmap()) } + ) + .build() + ) } // --- BINDING ADAPTERS --- /** - * Bind the cover art for a song. + * Bind the album art for a [Song]. */ -@BindingAdapter("coverArt") -fun ImageView.bindCoverArt(song: Song) { - if (!SettingsManager.getInstance().showCovers) { +@BindingAdapter("albumArt") +fun ImageView.bindAlbumArt(song: Song) { + if (ignoreCovers) { setImageResource(R.drawable.ic_song) return } - val request = newRequest() - .doCoverSetup(context, song) - .error(R.drawable.ic_song) - .build() - - Coil.imageLoader(context).enqueue(request) + Coil.imageLoader(context).enqueue( + ImageRequest.Builder(context) + .target(this) + .data(song.album) + .fetcher(AlbumArtFetcher(context)) + .error(R.drawable.ic_song) + .build() + ) } /** - * Bind the cover art for an album + * Bind the album art for an [Album]. */ -@BindingAdapter("coverArt") -fun ImageView.bindCoverArt(album: Album) { - if (!SettingsManager.getInstance().showCovers) { +@BindingAdapter("albumArt") +fun ImageView.bindAlbumArt(album: Album) { + if (ignoreCovers) { setImageResource(R.drawable.ic_album) return } - val request = newRequest() - .doCoverSetup(context, album) - .error(R.drawable.ic_album) - .build() - - Coil.imageLoader(context).enqueue(request) + Coil.imageLoader(context).enqueue( + ImageRequest.Builder(context) + .target(this) + .data(album) + .fetcher(AlbumArtFetcher(context)) + .error(R.drawable.ic_album) + .build() + ) } /** - * Bind the artist image for an artist. + * Bind the image for an [Artist] */ @BindingAdapter("artistImage") fun ImageView.bindArtistImage(artist: Artist) { - if (!SettingsManager.getInstance().showCovers) { + if (ignoreCovers) { setImageResource(R.drawable.ic_artist) return } - val request: ImageRequest - - // If there is more than one album, then create a mosaic of them. - if (artist.albums.size >= 4) { - val uris = mutableListOf() - - for (i in 0..3) { - uris.add(artist.albums[i].coverUri) - } - - val fetcher = MosaicFetcher(context) - - request = newRequest() - .data(uris) - .fetcher(fetcher) + Coil.imageLoader(context).enqueue( + ImageRequest.Builder(context) + .target(this) + .data(artist) + .fetcher(MosaicFetcher(context)) .error(R.drawable.ic_artist) .build() - } else { - // Otherwise, just get the first cover and use that - // If the artist doesn't have any albums [Which happens], then don't even bother with that. - if (artist.albums.isNotEmpty()) { - request = newRequest() - .doCoverSetup(context, artist.albums[0]) - .error(R.drawable.ic_artist) - .build() - } else { - setImageResource(R.drawable.ic_artist) - - return - } - } - - Coil.imageLoader(context).enqueue(request) + ) } /** - * Bind the genre image for a genre. + * Bind the image for a [Genre] */ @BindingAdapter("genreImage") fun ImageView.bindGenreImage(genre: Genre) { - if (!SettingsManager.getInstance().showCovers) { + if (ignoreCovers) { setImageResource(R.drawable.ic_genre) return } - val request: ImageRequest - val genreCovers = mutableListOf() - - // Group the genre's songs by their album's cover and add them - genre.songs.groupBy { it.album.coverUri }.forEach { genreCovers.add(it.key) } - - if (genreCovers.size >= 4) { - val fetcher = MosaicFetcher(context) - - request = newRequest() - .data(genreCovers.slice(0..3)) - .fetcher(fetcher) + Coil.imageLoader(context).enqueue( + ImageRequest.Builder(context) + .target(this) + .data(genre) + .fetcher(MosaicFetcher(context)) .error(R.drawable.ic_genre) .build() - } else { - if (genreCovers.isNotEmpty()) { - request = newRequest() - .doCoverSetup(context, genre.songs[0]) - .error(R.drawable.ic_genre) - .build() - } else { - setImageResource(R.drawable.ic_genre) - - return - } - } - - Coil.imageLoader(context).enqueue(request) -} - -/** - * Determine if a high quality or low-quality cover needs to be loaded for a specific [Album] - * @return The same builder that this is applied to - */ -private fun ImageRequest.Builder.doCoverSetup(context: Context, data: Album): ImageRequest.Builder { - if (SettingsManager.getInstance().useQualityCovers) { - fetcher(QualityCoverFetcher(context)) - data(data.songs[0]) - } else { - data(data.coverUri) - } - - return this -} - -/** - * Determine if a high quality or low-quality cover needs to be loaded for a specific [Song] - * @return The same builder that this is applied to - */ -private fun ImageRequest.Builder.doCoverSetup(context: Context, data: Song): ImageRequest.Builder { - if (SettingsManager.getInstance().useQualityCovers) { - fetcher(QualityCoverFetcher(context)) - data(data) - } else { - data(data.album.coverUri) - } - - return this -} - -/** - * Get the base request used by the above functions - * @return The base request - */ -private fun ImageView.newRequest(): ImageRequest.Builder { - return ImageRequest.Builder(context).target(this) + ) } diff --git a/app/src/main/java/org/oxycblt/auxio/coil/MosaicFetcher.kt b/app/src/main/java/org/oxycblt/auxio/coil/MosaicFetcher.kt index 561660f01..d332dd664 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/MosaicFetcher.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/MosaicFetcher.kt @@ -16,24 +16,43 @@ import coil.fetch.SourceResult import coil.size.Size import okio.buffer import okio.source +import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.Parent import java.io.Closeable import java.io.InputStream /** - * A [Fetcher] that takes multiple cover uris and turns them into a 2x2 mosaic image. + * A [Fetcher] that takes an [Artist] or [Genre] and returns a mosaic of its albums. * @author OxygenCobalt */ -class MosaicFetcher(private val context: Context) : Fetcher> { +class MosaicFetcher(private val context: Context) : Fetcher { override suspend fun fetch( pool: BitmapPool, - data: List, + data: Parent, size: Size, options: Options ): FetchResult { + // Get the URIs for either a genre or artist + val uris = mutableListOf() + + when (data) { + is Artist -> data.albums.forEachIndexed { index, album -> + if (index < 4) { uris.add(album.coverUri) } + } + + is Genre -> data.songs.groupBy { it.album.coverUri }.keys.forEachIndexed { index, uri -> + if (index < 4) { uris.add(uri) } + } + + else -> {} + } + val streams = mutableListOf() // Load MediaStore streams - data.forEach { + uris.forEach { val stream: InputStream? = context.contentResolver.openInputStream(it) if (stream != null) { @@ -44,26 +63,35 @@ class MosaicFetcher(private val context: Context) : Fetcher> { // If so many streams failed that there's not enough images to make a mosaic, then // just return the first cover image. if (streams.size < 4) { - streams.forEach { it.close() } + // Dont even bother if ALL the streams have failed. + check(streams.isNotEmpty()) { "All streams have failed. " } - return if (streams.isNotEmpty()) { - SourceResult( - source = streams[0].source().buffer(), - mimeType = context.contentResolver.getType(data[0]), - dataSource = DataSource.DISK - ) - } else { - error("All streams failed. Not bothering.") - } + return SourceResult( + source = streams[0].source().buffer(), + mimeType = context.contentResolver.getType(uris[0]), + dataSource = DataSource.DISK + ) } - // Create the mosaic, code adapted from Phonograph. - // https://github.com/kabouzeid/Phonograph - val finalBitmap = Bitmap.createBitmap( + val bitmap = drawMosaic(streams) + + return DrawableResult( + drawable = bitmap.toDrawable(context.resources), + isSampled = false, + dataSource = DataSource.DISK + ) + } + + /** + * Create the mosaic, Code adapted from Phonograph + * https://github.com/kabouzeid/Phonograph + */ + private fun drawMosaic(streams: List): Bitmap { + val mosaicBitmap = Bitmap.createBitmap( MOSAIC_BITMAP_SIZE, MOSAIC_BITMAP_SIZE, Bitmap.Config.RGB_565 ) - val canvas = Canvas(finalBitmap) + val canvas = Canvas(mosaicBitmap) var x = 0 var y = 0 @@ -90,11 +118,7 @@ class MosaicFetcher(private val context: Context) : Fetcher> { } } - return DrawableResult( - drawable = finalBitmap.toDrawable(context.resources), - isSampled = false, - dataSource = DataSource.DISK - ) + return mosaicBitmap } /** @@ -107,7 +131,8 @@ class MosaicFetcher(private val context: Context) : Fetcher> { } } - override fun key(data: List): String = data.toString() + override fun key(data: Parent): String = data.id.toString() + override fun handles(data: Parent) = data !is Album // Albums are not used here companion object { private const val MOSAIC_BITMAP_SIZE = 512 diff --git a/app/src/main/java/org/oxycblt/auxio/coil/QualityCoverFetcher.kt b/app/src/main/java/org/oxycblt/auxio/coil/QualityCoverFetcher.kt deleted file mode 100644 index 112504a10..000000000 --- a/app/src/main/java/org/oxycblt/auxio/coil/QualityCoverFetcher.kt +++ /dev/null @@ -1,77 +0,0 @@ -package org.oxycblt.auxio.coil - -import android.content.Context -import android.media.MediaMetadataRetriever -import android.net.Uri -import coil.bitmap.BitmapPool -import coil.decode.DataSource -import coil.decode.Options -import coil.fetch.FetchResult -import coil.fetch.Fetcher -import coil.fetch.SourceResult -import coil.size.Size -import okio.buffer -import okio.source -import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.music.toURI -import java.io.ByteArrayInputStream -import java.io.InputStream - -/** - * A [Fetcher] for fetching high-quality embedded covers instead of the compressed covers, albeit - * at the cost of load time & memory usage. - */ -class QualityCoverFetcher(private val context: Context) : Fetcher { - @Suppress("BlockingMethodInNonBlockingContext") - override suspend fun fetch( - pool: BitmapPool, - data: Song, - size: Size, - options: Options - ): FetchResult { - val extractor = MediaMetadataRetriever() - val stream: InputStream? - val uri: Uri - - extractor.use { ext -> - ext.setDataSource(context, data.id.toURI()) - val cover = ext.embeddedPicture - - stream = if (cover != null) { - uri = data.id.toURI() - - ByteArrayInputStream(cover) - } else { - // Fallback to the compressed cover if the cover loading failed. - uri = data.album.coverUri - - // Blocking call, but coil is on a background thread so it doesn't matter - context.contentResolver.openInputStream(data.album.coverUri) - } - - stream?.use { - return SourceResult( - source = it.source().buffer(), - mimeType = context.contentResolver.getType(uri), - dataSource = DataSource.DISK - ) - } - } - - // If we are here, the extractor likely failed so instead attempt to return the compressed - // cover instead. - context.contentResolver.openInputStream(data.album.coverUri)?.use { - return SourceResult( - source = it.source().buffer(), - mimeType = context.contentResolver.getType(uri), - dataSource = DataSource.DISK - ) - } - - // If even that failed, then error out entirely. - error("Likely no bitmap for this song/album.") - } - - // Group bitmaps by their album so that caching is more efficent - override fun key(data: Song): String = data.album.id.toString() -} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt b/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt index daa8021af..c2633d738 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt @@ -13,7 +13,7 @@ import androidx.media.app.NotificationCompat.MediaStyle import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.MainActivity import org.oxycblt.auxio.R -import org.oxycblt.auxio.coil.getBitmap +import org.oxycblt.auxio.coil.loadBitmap import org.oxycblt.auxio.logE import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song @@ -110,7 +110,7 @@ fun NotificationCompat.Builder.setMetadata( if (colorize) { // getBitmap() is concurrent, so only call back to the object calling this function when // the loading is over. - getBitmap(context, song) { + loadBitmap(context, song) { setLargeIcon(it) onDone() diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt index ea730cc63..af1bb64c0 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt @@ -38,7 +38,7 @@ import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch -import org.oxycblt.auxio.coil.getBitmap +import org.oxycblt.auxio.coil.loadBitmap import org.oxycblt.auxio.logD import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.toURI @@ -392,7 +392,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.album.name) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration) - getBitmap(this, song) { + loadBitmap(this, song) { builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, it) mediaSession.setMetadata(builder.build()) } diff --git a/app/src/main/res/layout-land/fragment_compact_playback.xml b/app/src/main/res/layout-land/fragment_compact_playback.xml index 21219a3d1..dc618a3d2 100644 --- a/app/src/main/res/layout-land/fragment_compact_playback.xml +++ b/app/src/main/res/layout-land/fragment_compact_playback.xml @@ -32,7 +32,7 @@ android:layout_marginTop="@dimen/margin_small" android:layout_marginBottom="@dimen/margin_small" android:contentDescription="@{@string/description_album_cover(song.name)}" - app:coverArt="@{song}" + app:albumArt="@{song}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="1:1" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout-land/fragment_playback.xml b/app/src/main/res/layout-land/fragment_playback.xml index 6d8040780..255579cfa 100644 --- a/app/src/main/res/layout-land/fragment_playback.xml +++ b/app/src/main/res/layout-land/fragment_playback.xml @@ -47,7 +47,7 @@ android:contentDescription="@{@string/description_album_cover(song.name)}" android:elevation="@dimen/elevation_normal" android:outlineProvider="bounds" - app:coverArt="@{song}" + app:albumArt="@{song}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="1" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout-land/item_album_header.xml b/app/src/main/res/layout-land/item_album_header.xml index d7b99c758..144c4c67a 100644 --- a/app/src/main/res/layout-land/item_album_header.xml +++ b/app/src/main/res/layout-land/item_album_header.xml @@ -32,7 +32,7 @@ android:contentDescription="@{@string/description_album_cover(album.name)}" android:elevation="@dimen/elevation_normal" android:outlineProvider="bounds" - app:coverArt="@{album}" + app:albumArt="@{album}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:src="@drawable/ic_album" /> diff --git a/app/src/main/res/layout-large/fragment_playback.xml b/app/src/main/res/layout-large/fragment_playback.xml deleted file mode 100644 index 60b25b325..000000000 --- a/app/src/main/res/layout-large/fragment_playback.xml +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-large/item_album_header.xml b/app/src/main/res/layout-large/item_album_header.xml index 96ec20a2b..bb423709b 100644 --- a/app/src/main/res/layout-large/item_album_header.xml +++ b/app/src/main/res/layout-large/item_album_header.xml @@ -32,7 +32,7 @@ android:contentDescription="@{@string/description_album_cover(album.name)}" android:elevation="@dimen/elevation_normal" android:outlineProvider="bounds" - app:coverArt="@{album}" + app:albumArt="@{album}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:src="@drawable/ic_album" /> diff --git a/app/src/main/res/layout-large-land/fragment_playback.xml b/app/src/main/res/layout-xlarge-land/fragment_playback.xml similarity index 99% rename from app/src/main/res/layout-large-land/fragment_playback.xml rename to app/src/main/res/layout-xlarge-land/fragment_playback.xml index 2272ac674..3833ceb0f 100644 --- a/app/src/main/res/layout-large-land/fragment_playback.xml +++ b/app/src/main/res/layout-xlarge-land/fragment_playback.xml @@ -46,7 +46,7 @@ android:contentDescription="@{@string/description_album_cover(song.name)}" android:elevation="@dimen/elevation_normal" android:outlineProvider="bounds" - app:coverArt="@{song}" + app:albumArt="@{song}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="1" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/fragment_compact_playback.xml b/app/src/main/res/layout/fragment_compact_playback.xml index 019035e22..442677dd1 100644 --- a/app/src/main/res/layout/fragment_compact_playback.xml +++ b/app/src/main/res/layout/fragment_compact_playback.xml @@ -42,7 +42,7 @@ android:layout_height="@dimen/size_cover_compact" android:layout_margin="@dimen/margin_mid_small" android:contentDescription="@{@string/description_album_cover(song.name)}" - app:coverArt="@{song}" + app:albumArt="@{song}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/playback_progress" diff --git a/app/src/main/res/layout/fragment_playback.xml b/app/src/main/res/layout/fragment_playback.xml index c2598554e..6636914d9 100644 --- a/app/src/main/res/layout/fragment_playback.xml +++ b/app/src/main/res/layout/fragment_playback.xml @@ -45,7 +45,7 @@ android:contentDescription="@{@string/description_album_cover(song.name)}" android:elevation="@dimen/elevation_normal" android:outlineProvider="bounds" - app:coverArt="@{song}" + app:albumArt="@{song}" app:layout_constraintBottom_toTopOf="@+id/playback_song" app:layout_constraintDimensionRatio="1:1" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/item_album.xml b/app/src/main/res/layout/item_album.xml index e5e8456e2..c83776578 100644 --- a/app/src/main/res/layout/item_album.xml +++ b/app/src/main/res/layout/item_album.xml @@ -18,7 +18,7 @@ android:layout_width="@dimen/size_cover_normal" android:layout_height="@dimen/size_cover_normal" android:contentDescription="@{@string/description_album_cover(album.name)}" - app:coverArt="@{album}" + app:albumArt="@{album}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/layout/item_album_header.xml b/app/src/main/res/layout/item_album_header.xml index ec5cd2644..d51444961 100644 --- a/app/src/main/res/layout/item_album_header.xml +++ b/app/src/main/res/layout/item_album_header.xml @@ -31,7 +31,7 @@ android:contentDescription="@{@string/description_album_cover(album.name)}" android:elevation="@dimen/elevation_normal" android:outlineProvider="bounds" - app:coverArt="@{album}" + app:albumArt="@{album}" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/layout/item_artist_album.xml b/app/src/main/res/layout/item_artist_album.xml index e61c220bd..0b79a30a6 100644 --- a/app/src/main/res/layout/item_artist_album.xml +++ b/app/src/main/res/layout/item_artist_album.xml @@ -18,7 +18,7 @@ android:layout_width="@dimen/size_cover_large" android:layout_height="@dimen/size_cover_large" android:contentDescription="@{@string/description_album_cover(album.name)}" - app:coverArt="@{album}" + app:albumArt="@{album}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/layout/item_genre_song.xml b/app/src/main/res/layout/item_genre_song.xml index 799b57877..14fcceca6 100644 --- a/app/src/main/res/layout/item_genre_song.xml +++ b/app/src/main/res/layout/item_genre_song.xml @@ -18,7 +18,7 @@ android:layout_width="@dimen/size_cover_compact" android:layout_height="@dimen/size_cover_compact" android:contentDescription="@{@string/description_album_cover(song.name)}" - app:coverArt="@{song}" + app:albumArt="@{song}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/layout/item_queue_song.xml b/app/src/main/res/layout/item_queue_song.xml index c103fa24b..93d54c633 100644 --- a/app/src/main/res/layout/item_queue_song.xml +++ b/app/src/main/res/layout/item_queue_song.xml @@ -21,7 +21,7 @@ android:layout_width="@dimen/size_cover_compact" android:layout_height="@dimen/size_cover_compact" android:contentDescription="@{@string/description_album_cover(song.name)}" - app:coverArt="@{song}" + app:albumArt="@{song}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/layout/item_song.xml b/app/src/main/res/layout/item_song.xml index c93be38a9..f01d29ae6 100644 --- a/app/src/main/res/layout/item_song.xml +++ b/app/src/main/res/layout/item_song.xml @@ -18,7 +18,7 @@ android:layout_width="@dimen/size_cover_compact" android:layout_height="@dimen/size_cover_compact" android:contentDescription="@{@string/description_album_cover(song.name)}" - app:coverArt="@{song}" + app:albumArt="@{song}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 35c925d2c..820ea9d99 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -12,7 +12,7 @@ 16dp 24dp 32dp - 48dp + 48dp 128dp diff --git a/info/ADDITIONS.md b/info/ADDITIONS.md index a63445141..db2f9ad00 100644 --- a/info/ADDITIONS.md +++ b/info/ADDITIONS.md @@ -8,7 +8,7 @@ These will likely be accepted as long as they do not cause too much harm to the ## New Customizations/Options -While I do like adding new behavior/UI customizations, these will be looked at more closely as certain additions can cause harm to the apps UI/UX while not providing alot of benefit. These tend to be accpeted however. +While I do like adding new behavior/UI customizations, these will be looked at more closely as certain additions can cause harm to the apps UI/UX while not providing alot of benefit. These tend to be accepted however. ## Feature Addtions and UI Changes diff --git a/info/ARCHITECTURE.md b/info/ARCHITECTURE.md index 790c94742..57a161ae1 100644 --- a/info/ARCHITECTURE.md +++ b/info/ARCHITECTURE.md @@ -64,7 +64,7 @@ org.oxycblt.auxio # Main UI's and logging utilities - `app:genreImage`: Binding Adapter that will load the genre image - `getBitmap`: Function that will take a song and return a bitmap, this should not be used in anything UI related, that is what the binding adapters above are for. -This should be enough to cover most use cases in Auxio. There are also fetchers for artist/genre images and higher quality covers, but these are not used outside of the module. +This should be enough to cover most use cases in Auxio. There are also fetchers for artist/genre images and album covers, but these are not used outside of the module. #### `.database` @@ -117,6 +117,10 @@ PlaybackStateManager───────────────────┘ Shared RecyclerView utilities, often for adapters and ViewHolders. Important ones to note are `DiffCallback`, which acts as a reusable differ callback based off of `BaseModel` for `ListAdapter`s, and the shared ViewHolders for each data type, such as `SongViewHolder` or `HeaderViewHolder`. +#### `.settings` + +The settings system is primarily based off of `SettingsManager`, a wrapper around `SharedPreferences`. This allows settings to be read/written in a much simpler/safer manner and without a context being needed. The Settings UI is largely contained in `SettingsListFragment`, while the `.ui` sub-package contains UIs related to the settings UI, such as the About Dialog. + #### `.search` Package for Auxio's search functionality, `SearchViewHolder` handles the data results and filtering while `SearchFragment`/`SearchAdapter` handles the display of the results and user input. @@ -129,5 +133,5 @@ Package for the songs UI, there is no data management here, only a user interfac Shared User Interface utilities. This is primarily made up of convenience/extension functions relating to Views, Resources, Configurations, and Contexts. It also contains some dedicated utilities, such as: - The Accent Management system -- `newMenu` and `ActionMenu`, which automates menu creation for most datatypes -- `memberBinding` and `MemberBinder`, which allows for viewbindings to be used as a member variable without memory leaks or nullability issues. \ No newline at end of file +- `newMenu` and `ActionMenu`, which automates menu creation for most data types +- `memberBinding` and `MemberBinder`, which allows for ViewBindings to be used as a member variable without memory leaks or nullability issues. \ No newline at end of file