Add artist images
Add artist images back, now with proper mosaics.
This commit is contained in:
parent
016d664e51
commit
a93c0f4af3
6 changed files with 177 additions and 11 deletions
|
@ -3,12 +3,14 @@ package org.oxycblt.auxio.music
|
||||||
import android.content.ContentUris
|
import android.content.ContentUris
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.databinding.BindingAdapter
|
import androidx.databinding.BindingAdapter
|
||||||
|
import coil.Coil
|
||||||
import coil.load
|
import coil.load
|
||||||
|
import coil.request.ImageRequest
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.music.coil.ArtistImageFetcher
|
||||||
import org.oxycblt.auxio.music.models.Album
|
import org.oxycblt.auxio.music.models.Album
|
||||||
import org.oxycblt.auxio.music.models.Artist
|
import org.oxycblt.auxio.music.models.Artist
|
||||||
import org.oxycblt.auxio.music.models.Song
|
import org.oxycblt.auxio.music.models.Song
|
||||||
|
@ -82,8 +84,6 @@ fun TextView.getArtistCounts(artist: Artist) {
|
||||||
R.plurals.format_song_count, artist.numSongs, artist.numSongs
|
R.plurals.format_song_count, artist.numSongs, artist.numSongs
|
||||||
)
|
)
|
||||||
|
|
||||||
Log.d("getArtistCounts", albums)
|
|
||||||
|
|
||||||
text = context.getString(R.string.format_double_counts, albums, songs)
|
text = context.getString(R.string.format_double_counts, albums, songs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +102,34 @@ fun ImageView.getCoverArt(album: Album) {
|
||||||
load(album.coverUri) {
|
load(album.coverUri) {
|
||||||
crossfade(true)
|
crossfade(true)
|
||||||
placeholder(android.R.color.transparent)
|
placeholder(android.R.color.transparent)
|
||||||
error(R.drawable.ic_music)
|
error(R.drawable.ic_album)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("artistImage")
|
||||||
|
fun ImageView.getArtistImage(artist: Artist) {
|
||||||
|
if (artist.numAlbums >= 4) {
|
||||||
|
val uris = mutableListOf<Uri>()
|
||||||
|
|
||||||
|
for (i in 0..3) {
|
||||||
|
uris.add(artist.albums[i].coverUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
val request = ImageRequest.Builder(context)
|
||||||
|
.data(uris)
|
||||||
|
.fetcher(ArtistImageFetcher(context))
|
||||||
|
.crossfade(true)
|
||||||
|
.placeholder(android.R.color.transparent)
|
||||||
|
.error(R.drawable.ic_artist)
|
||||||
|
.target(this)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
Coil.imageLoader(context).enqueue(request)
|
||||||
|
} else {
|
||||||
|
load(artist.albums[0].coverUri) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(android.R.color.transparent)
|
||||||
|
error(R.drawable.ic_music)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
package org.oxycblt.auxio.music.coil
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
|
import coil.bitmap.BitmapPool
|
||||||
|
import coil.decode.DataSource
|
||||||
|
import coil.decode.Options
|
||||||
|
import coil.fetch.DrawableResult
|
||||||
|
import coil.fetch.FetchResult
|
||||||
|
import coil.fetch.Fetcher
|
||||||
|
import coil.fetch.SourceResult
|
||||||
|
import coil.size.Size
|
||||||
|
import okio.buffer
|
||||||
|
import okio.source
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
const val MOSAIC_BITMAP_SIZE = 512
|
||||||
|
|
||||||
|
class ArtistImageFetcher(private val context: Context) : Fetcher<List<Uri>> {
|
||||||
|
override suspend fun fetch(
|
||||||
|
pool: BitmapPool,
|
||||||
|
data: List<Uri>,
|
||||||
|
size: Size,
|
||||||
|
options: Options
|
||||||
|
): FetchResult {
|
||||||
|
val streams = mutableListOf<InputStream>()
|
||||||
|
|
||||||
|
for (uri in data) {
|
||||||
|
val stream: InputStream? = context.contentResolver.openInputStream(uri)
|
||||||
|
|
||||||
|
if (stream != null) {
|
||||||
|
streams.add(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If so many streams failed that there's not enough images to make a mosaic, then
|
||||||
|
// just return the first album.
|
||||||
|
if (streams.size < 4) {
|
||||||
|
streams.forEach { it.close() }
|
||||||
|
|
||||||
|
return SourceResult(
|
||||||
|
source = streams[0].source().buffer(),
|
||||||
|
mimeType = context.contentResolver.getType(data[0]),
|
||||||
|
dataSource = DataSource.DISK
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the mosaic, code adapted from Phonograph.
|
||||||
|
// https://github.com/kabouzeid/Phonograph
|
||||||
|
val finalBitmap = Bitmap.createBitmap(
|
||||||
|
MOSAIC_BITMAP_SIZE, MOSAIC_BITMAP_SIZE, Bitmap.Config.RGB_565
|
||||||
|
)
|
||||||
|
|
||||||
|
val canvas = Canvas(finalBitmap)
|
||||||
|
|
||||||
|
var x = 0
|
||||||
|
var y = 0
|
||||||
|
val increment = MOSAIC_BITMAP_SIZE / 2
|
||||||
|
|
||||||
|
for (stream in streams) {
|
||||||
|
val bitmap = Bitmap.createScaledBitmap(
|
||||||
|
BitmapFactory.decodeStream(stream),
|
||||||
|
increment,
|
||||||
|
increment,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
|
||||||
|
|
||||||
|
x += increment
|
||||||
|
|
||||||
|
if (x == MOSAIC_BITMAP_SIZE) {
|
||||||
|
x = 0
|
||||||
|
y += increment
|
||||||
|
|
||||||
|
if (y == MOSAIC_BITMAP_SIZE) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close all the streams when done.
|
||||||
|
streams.forEach { it.close() }
|
||||||
|
|
||||||
|
return DrawableResult(
|
||||||
|
drawable = finalBitmap.toDrawable(context.resources),
|
||||||
|
isSampled = false,
|
||||||
|
dataSource = DataSource.DISK
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun key(data: List<Uri>): String? = data.toString()
|
||||||
|
}
|
|
@ -154,10 +154,18 @@ class MusicSorter(
|
||||||
albums.forEach { if (it.name == "") it.name = albumPlaceholder }
|
albums.forEach { if (it.name == "") it.name = albumPlaceholder }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort all music into
|
// Sort all music
|
||||||
private fun finalizeMusic() {
|
private fun finalizeMusic() {
|
||||||
genres.sortBy { it.name }
|
genres.sortWith(
|
||||||
artists.sortBy { it.name }
|
compareBy(String.CASE_INSENSITIVE_ORDER, { it.name })
|
||||||
albums.sortBy { it.name }
|
)
|
||||||
|
|
||||||
|
artists.sortWith(
|
||||||
|
compareBy(String.CASE_INSENSITIVE_ORDER, { it.name })
|
||||||
|
)
|
||||||
|
|
||||||
|
albums.sortWith(
|
||||||
|
compareBy(String.CASE_INSENSITIVE_ORDER, { it.name })
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
app/src/main/res/drawable/ic_album.xml
Normal file
11
app/src/main/res/drawable/ic_album.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorPrimary">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,16.5c-2.49,0 -4.5,-2.01 -4.5,-4.5S9.51,7.5 12,7.5s4.5,2.01 4.5,4.5 -2.01,4.5 -4.5,4.5zM12,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1z" />
|
||||||
|
</vector>
|
11
app/src/main/res/drawable/ic_artist.xml
Normal file
11
app/src/main/res/drawable/ic_artist.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorPrimary">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M9,11.75c-0.69,0 -1.25,0.56 -1.25,1.25s0.56,1.25 1.25,1.25 1.25,-0.56 1.25,-1.25 -0.56,-1.25 -1.25,-1.25zM15,11.75c-0.69,0 -1.25,0.56 -1.25,1.25s0.56,1.25 1.25,1.25 1.25,-0.56 1.25,-1.25 -0.56,-1.25 -1.25,-1.25zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8 0,-0.29 0.02,-0.58 0.05,-0.86 2.36,-1.05 4.23,-2.98 5.21,-5.37C11.07,8.33 14.05,10 17.42,10c0.78,0 1.53,-0.09 2.25,-0.26 0.21,0.71 0.33,1.47 0.33,2.26 0,4.41 -3.59,8 -8,8z" />
|
||||||
|
</vector>
|
|
@ -18,7 +18,16 @@
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:padding="@dimen/padding_medium">
|
android:padding="@dimen/padding_medium">
|
||||||
|
|
||||||
<!-- TODO: Artist images -->
|
<ImageView
|
||||||
|
android:id="@+id/artist_image"
|
||||||
|
android:layout_width="@dimen/cover_size_normal"
|
||||||
|
android:layout_height="@dimen/cover_size_normal"
|
||||||
|
app:artistImage="@{artist}"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@tools:sample/backgrounds/scenic"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/artist_name"
|
android:id="@+id/artist_name"
|
||||||
|
@ -27,11 +36,12 @@
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:text="@{artist.name}"
|
android:text="@{artist.name}"
|
||||||
|
android:layout_marginStart="@dimen/margin_medium"
|
||||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/album_song_count"
|
app:layout_constraintBottom_toTopOf="@+id/album_song_count"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toEndOf="@+id/artist_image"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintVertical_chainStyle="packed"
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
tools:text="Artist Name" />
|
tools:text="Artist Name" />
|
||||||
|
@ -41,10 +51,11 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||||
|
android:layout_marginStart="@dimen/margin_medium"
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
app:artistCounts="@{artist}"
|
app:artistCounts="@{artist}"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toEndOf="@+id/artist_image"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/artist_name"
|
app:layout_constraintTop_toBottomOf="@+id/artist_name"
|
||||||
tools:text="2 Albums, 20 Songs" />
|
tools:text="2 Albums, 20 Songs" />
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue