diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4289388ff..b9e7d6ecc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
#### What's New
- Folders on external drives can now be excluded on Android Q+ [#134]
- Playback bar now has a skip action
+- When playing, the cover now shows an animated indicator
#### What's Improved
- The toolbar in the home UI now collapses when scrolling
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
index 69c5b40fc..ddbaf79ec 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
@@ -209,7 +209,6 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener {
}
if (parent is Album && parent.id == unlikelyToBeNull(detailModel.currentAlbum.value).id) {
- logD("update $song")
detailAdapter.highlightSong(song)
} else {
// Clear the ViewHolders if the mode isn't ALL_SONGS
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt
index ed4c2318c..215fb631a 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt
@@ -31,7 +31,6 @@ import org.oxycblt.auxio.ui.BindingViewHolder
import org.oxycblt.auxio.ui.Item
import org.oxycblt.auxio.ui.MenuItemListener
import org.oxycblt.auxio.ui.SimpleItemCallback
-import org.oxycblt.auxio.ui.StyledImageView
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.getPluralSafe
@@ -194,21 +193,12 @@ private class AlbumSongViewHolder private constructor(private val binding: ItemA
isInvisible = false
contentDescription = context.getString(R.string.desc_track_number, item.track)
}
-
- binding.songTrackBg.setImageDrawable(null)
} else {
binding.songTrack.apply {
textSafe = ""
isInvisible = true
contentDescription = context.getString(R.string.def_track)
}
-
- // Normally we would not re-load the drawable each time and instead
- // change the alpha value, but Lollipop gets in our way yet again
- // and does some stupid insanity with the alpha value that results
- // in such branching.
- binding.songTrackBg.setImageDrawable(
- StyledImageView.StyledDrawable(binding.context, R.drawable.ic_song))
}
binding.songName.textSafe = item.resolveName(binding.context)
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/StyledImageView.kt b/app/src/main/java/org/oxycblt/auxio/image/BaseStyledImageView.kt
similarity index 53%
rename from app/src/main/java/org/oxycblt/auxio/ui/StyledImageView.kt
rename to app/src/main/java/org/oxycblt/auxio/image/BaseStyledImageView.kt
index 8ac6a7094..f5d64307f 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/StyledImageView.kt
+++ b/app/src/main/java/org/oxycblt/auxio/image/BaseStyledImageView.kt
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package org.oxycblt.auxio.ui
+package org.oxycblt.auxio.image
import android.content.Context
import android.graphics.Canvas
@@ -25,111 +25,67 @@ import android.graphics.drawable.Drawable
import android.util.AttributeSet
import androidx.annotation.AttrRes
import androidx.annotation.DrawableRes
-import androidx.annotation.StringRes
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.graphics.drawable.DrawableCompat
import coil.dispose
-import coil.drawable.CrossfadeDrawable
import coil.load
import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.R
-import org.oxycblt.auxio.image.SquareFrameTransform
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
-import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.getColorStateListSafe
import org.oxycblt.auxio.util.getDrawableSafe
/**
- * An [AppCompatImageView] that applies many of the stylistic choices that Auxio uses regarding
- * images.
+ * The base class for Auxio's images. Do not use this class outside of this module.
+ *
+ * Default behavior includes the addition of a tonal background and automatic icon sizing. Other
+ * behavior is implemented by [StyledImageView] and [ImageGroup].
*
- * Default behavior includes the addition of a tonal background, automatic sizing of icons to half
- * of the view size, and corner radius application depending on user preference.
* @author OxygenCobalt
- *
- * TODO: I'll need to make it a whole ViewGroup eventually
*/
-class StyledImageView
+open class BaseStyledImageView
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
AppCompatImageView(context, attrs, defStyleAttr) {
- private var cornerRadius = 0f
- private var indicator = StyledDrawable(context, R.drawable.ic_equalizer)
+ private var staticIcon = 0
init {
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
- cornerRadius = styledAttrs.getDimension(R.styleable.StyledImageView_cornerRadius, 0f)
+ staticIcon = styledAttrs.getResourceId(R.styleable.StyledImageView_staticIcon, -1)
styledAttrs.recycle()
- // Use clipToOutline and a background drawable to crop images. While Coil's transformation
- // could theoretically be used to round corners, the corner radius is dependent on the
- // dimensions of the image, which will result in inconsistent corners across different
- // album covers unless we resize all covers to be the same size. clipToOutline is both
- // cheaper and more elegant. As a side-note, this also allows us to re-use the same
- // background for both the tonal background color and the corner rounding.
- clipToOutline = true
background =
MaterialShapeDrawable().apply {
fillColor = context.getColorStateListSafe(R.color.sel_cover_bg)
}
-
- // If we have a pre-set drawable, ensure that it's half-size.
- val drawable = drawable
- if (drawable != null) {
- setImageDrawable(StyledDrawable(context, drawable))
- }
- }
-
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
-
- if (!isInEditMode) {
- val settingsManager = SettingsManager.getInstance()
- if (settingsManager.roundCovers) {
- (background as MaterialShapeDrawable).setCornerSize(cornerRadius)
- }
- }
- }
-
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
- if (isActivated) {
- // We can't modify the view alpha since that would change the background opacity,
- // but we also can't modify the image alpha since that breaks CrossfadeDrawable.
- // Instead, we just draw the background again when activated in order to obscure
- // the actual image, and then draw the indicator. The only other option is to make
- // a proper ViewGroup, and I really don't want that.
- val src = drawable?.let { if (it is CrossfadeDrawable) it.end else it }
- background.alpha = if (src is StyledDrawable) 255 else 128
- background.draw(canvas)
- background.alpha = 255
- indicator.draw(canvas)
- }
}
/** Bind the album cover for a [song]. */
- fun bind(song: Song) = loadImpl(song, R.drawable.ic_song, R.string.desc_album_cover)
+ open fun bind(song: Song) = loadImpl(song, R.drawable.ic_song)
/** Bind the album cover for an [album]. */
- fun bind(album: Album) = loadImpl(album, R.drawable.ic_album, R.string.desc_album_cover)
+ open fun bind(album: Album) = loadImpl(album, R.drawable.ic_album)
/** Bind the image for an [artist] */
- fun bind(artist: Artist) = loadImpl(artist, R.drawable.ic_artist, R.string.desc_artist_image)
+ open fun bind(artist: Artist) = loadImpl(artist, R.drawable.ic_artist)
/** Bind the image for a [genre] */
- fun bind(genre: Genre) = loadImpl(genre, R.drawable.ic_genre, R.string.desc_genre_image)
+ open fun bind(genre: Genre) = loadImpl(genre, R.drawable.ic_genre)
+
+ private fun loadImpl(music: T, @DrawableRes error: Int) {
+ if (staticIcon > -1) {
+ throw IllegalStateException("Static StyledImageViews cannot bind new images")
+ }
- private fun loadImpl(music: T, @DrawableRes error: Int, @StringRes desc: Int) {
dispose()
load(music) {
error(StyledDrawable(context, error))
transformations(SquareFrameTransform.INSTANCE)
}
- contentDescription = context.getString(desc, music.resolveName(context))
}
/**
diff --git a/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt b/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt
new file mode 100644
index 000000000..67dd80cc1
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2022 Auxio Project
+ *
+ * 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.image
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+import androidx.annotation.AttrRes
+import com.google.android.material.shape.MaterialShapeDrawable
+import org.oxycblt.auxio.R
+import org.oxycblt.auxio.music.Album
+import org.oxycblt.auxio.music.Artist
+import org.oxycblt.auxio.music.Genre
+import org.oxycblt.auxio.music.Song
+import org.oxycblt.auxio.settings.SettingsManager
+import org.oxycblt.auxio.util.getColorStateListSafe
+
+/**
+ * Effectively a super-charged [StyledImageView].
+ *
+ * This class enables the following features alongside the base features pf [StyledImageView]:
+ * - Activation indicator with an animated icon
+ * - (Eventually) selection indicator
+ * - Support for ONE custom view
+ *
+ * This class is primarily intended for list items. For most uses, the simpler [StyledImageView] is
+ * more efficient and suitable.
+ *
+ * @author OxygenCobalt
+ */
+class ImageGroup
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
+ FrameLayout(context, attrs, defStyleAttr) {
+ private val cornerRadius: Float
+
+ private val inner = BaseStyledImageView(context, attrs)
+ private var customView: View? = null
+ private val indicator = ImageGroupIndicator(context)
+
+ init {
+ // Android wants you to make separate attributes for each view type, but will
+ // then throw an error if you do because of duplicate attribute names.
+ @SuppressLint("CustomViewStyleable")
+ val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
+ cornerRadius = styledAttrs.getDimension(R.styleable.StyledImageView_cornerRadius, 0f)
+ styledAttrs.recycle()
+
+ addView(inner)
+
+ // Use clipToOutline and a background drawable to crop images. While Coil's transformation
+ // could theoretically be used to round corners, the corner radius is dependent on the
+ // dimensions of the image, which will result in inconsistent corners across different
+ // album covers unless we resize all covers to be the same size. clipToOutline is both
+ // cheaper and more elegant. As a side-note, this also allows us to re-use the same
+ // background for both the tonal background color and the corner rounding.
+ background = MaterialShapeDrawable()
+ clipToOutline = true
+
+ if (!isInEditMode) {
+ val settingsManager = SettingsManager.getInstance()
+ if (settingsManager.roundCovers) {
+ (background as MaterialShapeDrawable).setCornerSize(cornerRadius)
+ }
+ }
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+
+ if (childCount > 2) {
+ error("Only one custom view is allowed")
+ }
+
+ customView =
+ getChildAt(1)?.apply {
+ background =
+ MaterialShapeDrawable().apply {
+ fillColor = context.getColorStateListSafe(R.color.sel_cover_bg)
+ }
+ }
+
+ addView(indicator)
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+
+ if (!isInEditMode) {
+ val settingsManager = SettingsManager.getInstance()
+ if (settingsManager.roundCovers) {
+ (background as MaterialShapeDrawable).setCornerSize(cornerRadius)
+ }
+ }
+
+ invalidateIndicator()
+ }
+
+ override fun setActivated(activated: Boolean) {
+ super.setActivated(activated)
+ invalidateIndicator()
+ }
+
+ private fun invalidateIndicator() {
+ if (isActivated) {
+ indicator.alpha = 1f
+ customView?.alpha = 0f
+ inner.alpha = 0f
+ } else {
+ indicator.alpha = 0f
+ customView?.alpha = 1f
+ inner.alpha = 1f
+ }
+ }
+
+ fun bind(song: Song) {
+ inner.bind(song)
+ contentDescription =
+ context.getString(R.string.desc_album_cover, song.album.resolveName(context))
+ }
+
+ fun bind(album: Album) {
+ inner.bind(album)
+ contentDescription =
+ context.getString(R.string.desc_album_cover, album.resolveName(context))
+ }
+
+ fun bind(artist: Artist) {
+ inner.bind(artist)
+ contentDescription =
+ context.getString(R.string.desc_artist_image, artist.resolveName(context))
+ }
+
+ fun bind(genre: Genre) {
+ inner.bind(genre)
+ contentDescription =
+ context.getString(R.string.desc_genre_image, genre.resolveName(context))
+ }
+}
diff --git a/app/src/main/java/org/oxycblt/auxio/image/ImageGroupIndicator.kt b/app/src/main/java/org/oxycblt/auxio/image/ImageGroupIndicator.kt
new file mode 100644
index 000000000..e8725b3b9
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/image/ImageGroupIndicator.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2022 Auxio Project
+ *
+ * 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.image
+
+import android.content.Context
+import android.graphics.Matrix
+import android.graphics.RectF
+import android.graphics.drawable.AnimationDrawable
+import android.util.AttributeSet
+import androidx.annotation.AttrRes
+import androidx.appcompat.widget.AppCompatImageView
+import com.google.android.material.shape.MaterialShapeDrawable
+import org.oxycblt.auxio.R
+import org.oxycblt.auxio.util.getColorStateListSafe
+
+/**
+ * Represents the animated indicator that is shown when [ImageGroup] is active.
+ *
+ * AnimationDrawable, the drawable that this view is backed by, is really finicky. Basically, it has
+ * to be set as the drawable of an ImageView to work correctly, and will just not draw anywhere
+ * else. As a result, we have to create a custom view that emulates [StyledImageView] and
+ * [StyledDrawable] simultaneously while also managing the animation state.
+ *
+ * @author OxygenCobalt
+ */
+class ImageGroupIndicator
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
+ AppCompatImageView(context, attrs, defStyleAttr) {
+ private val centerMatrix = Matrix()
+ private val matrixSrc = RectF()
+ private val matrixDst = RectF()
+
+ init {
+ scaleType = ScaleType.MATRIX
+ setImageResource(R.drawable.ic_animated_equalizer)
+ imageTintList = context.getColorStateListSafe(R.color.sel_on_cover_bg)
+ background =
+ MaterialShapeDrawable().apply {
+ fillColor = context.getColorStateListSafe(R.color.sel_cover_bg)
+ }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+ // Instead of using StyledDrawable (which would break the animation), we scale
+ // up the animated icon using a matrix. This is okay, as it won't fall victim to
+ // the same issues that come with using a matrix in StyledImageView
+ imageMatrix =
+ centerMatrix.apply {
+ reset()
+ drawable?.let { drawable ->
+ // Android is too good to allow us to set a fixed image size, so we instead need
+ // to define a matrix to scale an image directly.
+
+ val iconWidth = measuredWidth / 2f
+ val iconHeight = measuredHeight / 2f
+
+ // First scale the icon up to the desired size.
+ matrixSrc.set(
+ 0f,
+ 0f,
+ drawable.intrinsicWidth.toFloat(),
+ drawable.intrinsicHeight.toFloat())
+ matrixDst.set(0f, 0f, iconWidth, iconHeight)
+ centerMatrix.setRectToRect(matrixSrc, matrixDst, Matrix.ScaleToFit.CENTER)
+
+ // Then actually center it into the icon, which the previous call does not
+ // actually do.
+ centerMatrix.postTranslate(
+ (measuredWidth - iconWidth) / 2f, (measuredHeight - iconHeight) / 2f)
+ }
+ }
+ }
+
+ override fun setActivated(activated: Boolean) {
+ super.setActivated(activated)
+ val icon = drawable as AnimationDrawable
+ if (activated) {
+ icon.start()
+ } else {
+ icon.stop()
+ }
+ }
+}
diff --git a/app/src/main/java/org/oxycblt/auxio/image/StyledDrawable.kt b/app/src/main/java/org/oxycblt/auxio/image/StyledDrawable.kt
new file mode 100644
index 000000000..96ea2e0df
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/image/StyledDrawable.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2022 Auxio Project
+ *
+ * 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.image
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+import androidx.core.graphics.drawable.DrawableCompat
+import org.oxycblt.auxio.R
+import org.oxycblt.auxio.util.getColorStateListSafe
+
+/**
+ * The internal drawable used by Auxio's images. Do not use this outside of this module.
+ *
+ * This enables a few features:
+ * - Automatic tinting to the correct image tint
+ * - Automatic sizing to HALF of the canvas.
+ *
+ * @author OxygenCobalt
+ */
+class StyledDrawable(context: Context, private val src: Drawable) : Drawable() {
+ init {
+ // Re-tint the drawable to something that will play along with the background.
+ // Done here because this call (and nothing else) miraculously works on Lollipop devices
+ DrawableCompat.setTintList(src, context.getColorStateListSafe(R.color.sel_on_cover_bg))
+ }
+
+ override fun draw(canvas: Canvas) {
+ src.bounds.set(canvas.clipBounds)
+ val adjustWidth = src.bounds.width() / 4
+ val adjustHeight = src.bounds.height() / 4
+ src.bounds.set(
+ adjustWidth,
+ adjustHeight,
+ src.bounds.width() - adjustWidth,
+ src.bounds.height() - adjustHeight)
+ src.draw(canvas)
+ }
+
+ override fun setAlpha(alpha: Int) {
+ src.alpha = alpha
+ }
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ src.colorFilter = colorFilter
+ }
+
+ override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
+}
diff --git a/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt b/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt
new file mode 100644
index 000000000..87dde24b3
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2022 Auxio Project
+ *
+ * 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.image
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.annotation.AttrRes
+import androidx.appcompat.widget.AppCompatImageView
+import com.google.android.material.shape.MaterialShapeDrawable
+import org.oxycblt.auxio.R
+import org.oxycblt.auxio.music.Album
+import org.oxycblt.auxio.music.Artist
+import org.oxycblt.auxio.music.Genre
+import org.oxycblt.auxio.music.Song
+import org.oxycblt.auxio.settings.SettingsManager
+
+/**
+ * An [AppCompatImageView] that applies many of the stylistic choices that Auxio uses regarding
+ * images.
+ *
+ * Default behavior includes the addition of a tonal background, automatic sizing of icons to half
+ * of the view size, and corner radius application depending on user preference.
+ *
+ * @author OxygenCobalt
+ */
+class StyledImageView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
+ BaseStyledImageView(context, attrs, defStyleAttr) {
+ private var cornerRadius = 0f
+
+ init {
+ val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
+ cornerRadius = styledAttrs.getDimension(R.styleable.StyledImageView_cornerRadius, 0f)
+ styledAttrs.recycle()
+
+ // Use clipToOutline and a background drawable to crop images. While Coil's transformation
+ // could theoretically be used to round corners, the corner radius is dependent on the
+ // dimensions of the image, which will result in inconsistent corners across different
+ // album covers unless we resize all covers to be the same size. clipToOutline is both
+ // cheaper and more elegant. As a side-note, this also allows us to re-use the same
+ // background for both the tonal background color and the corner rounding.
+ clipToOutline = true
+
+ if (!isInEditMode) {
+ val settingsManager = SettingsManager.getInstance()
+ if (settingsManager.roundCovers) {
+ (background as MaterialShapeDrawable).setCornerSize(cornerRadius)
+ }
+ }
+ }
+
+ override fun bind(song: Song) {
+ super.bind(song)
+ contentDescription =
+ context.getString(R.string.desc_album_cover, song.album.resolveName(context))
+ }
+
+ override fun bind(album: Album) {
+ super.bind(album)
+ contentDescription =
+ context.getString(R.string.desc_album_cover, album.resolveName(context))
+ }
+
+ override fun bind(artist: Artist) {
+ super.bind(artist)
+ contentDescription =
+ context.getString(R.string.desc_artist_image, artist.resolveName(context))
+ }
+
+ override fun bind(genre: Genre) {
+ super.bind(genre)
+ contentDescription =
+ context.getString(R.string.desc_genre_image, genre.resolveName(context))
+ }
+}
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/IndicatorMaterialButton.kt b/app/src/main/java/org/oxycblt/auxio/playback/IndicatorMaterialButton.kt
similarity index 98%
rename from app/src/main/java/org/oxycblt/auxio/ui/IndicatorMaterialButton.kt
rename to app/src/main/java/org/oxycblt/auxio/playback/IndicatorMaterialButton.kt
index 7183286b3..7cfdc50a2 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/IndicatorMaterialButton.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/IndicatorMaterialButton.kt
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package org.oxycblt.auxio.ui
+package org.oxycblt.auxio.playback
import android.content.Context
import android.graphics.Canvas
diff --git a/app/src/main/res/color/sel_track_cover.xml b/app/src/main/res/color/sel_track_cover.xml
deleted file mode 100644
index d7ade8a18..000000000
--- a/app/src/main/res/color/sel_track_cover.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_animated_equalizer.xml b/app/src/main/res/drawable/ic_animated_equalizer.xml
new file mode 100644
index 000000000..9b81fe2db
--- /dev/null
+++ b/app/src/main/res/drawable/ic_animated_equalizer.xml
@@ -0,0 +1,491 @@
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_equalizer.xml b/app/src/main/res/drawable/ic_equalizer.xml
deleted file mode 100644
index c1aa427a0..000000000
--- a/app/src/main/res/drawable/ic_equalizer.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/layout-h600dp/item_detail.xml b/app/src/main/res/layout-h600dp/item_detail.xml
index 25a2bffda..3e942fbfd 100644
--- a/app/src/main/res/layout-h600dp/item_detail.xml
+++ b/app/src/main/res/layout-h600dp/item_detail.xml
@@ -6,14 +6,14 @@
android:layout_height="match_parent"
android:padding="@dimen/spacing_medium">
-
+ tools:staticIcon="@drawable/ic_artist" />
-
-
-
-
+ tools:staticIcon="@drawable/ic_artist" />
-
-
-
-
-
-
-
+ tools:staticIcon="@drawable/ic_artist" />
-
-
+ tools:staticIcon="@drawable/ic_artist" />
-
-
-
-
-
+ tools:placeholderIcon="@drawable/ic_song" />
-
-
-
-
-
+ app:staticIcon="@drawable/ic_song"
+ tools:visibility="invisible">
+
+
+
+
+
@@ -59,9 +61,9 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"
- app:layout_constraintBottom_toBottomOf="@+id/song_track"
+ app:layout_constraintBottom_toBottomOf="@+id/song_track_bg"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toEndOf="@+id/song_track"
+ app:layout_constraintStart_toEndOf="@+id/song_track_bg"
app:layout_constraintTop_toBottomOf="@+id/song_name"
tools:text="16:16" />
diff --git a/app/src/main/res/layout/item_artist.xml b/app/src/main/res/layout/item_artist.xml
index 3c321027d..19483e1f8 100644
--- a/app/src/main/res/layout/item_artist.xml
+++ b/app/src/main/res/layout/item_artist.xml
@@ -4,13 +4,13 @@
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.Auxio.ItemLayout">
-
+ tools:staticIcon="@drawable/ic_artist" />
-
+ tools:staticIcon="@drawable/ic_artist" />
-
-
+ tools:staticIcon="@drawable/ic_artist" />
-
+ tools:staticIcon="@drawable/ic_album" />
-
+ tools:staticIcon="@drawable/ic_album" />
-
- \
- \
- \
- \
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 670a35a76..33611e92d 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -2,6 +2,7 @@
+