From d80c49f11fac13e5d20ce9bc87c2f6369a30db06 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Fri, 10 Jun 2022 11:57:50 -0600 Subject: [PATCH] image: add image group Add a new view called ImageGroup that will handle all advanced image hacks from now on. This includes the indicator (which is now animated), any selection indicators, and the weirdness of the album song image. All of that is now handled by ImageGroup. This is the culmination of probably a day and a half of wrangling with android insanity and having to remove a lot of what I liked about the indicator in order to make this work on a basic level. The only major bug I am currently aware of with this is that the indicator is bugged out on Lollipop devices due to bad vectors. Again. I never want to do this again. I cannot believe that adding a basic indicator took this long and required so much stupid hacks and inefficient code. And then google wonders why android apps are so visually unappealing and janky and laggy. Hm. Must be that devs aren't using the brand new FooBarBlasterFlow library! --- CHANGELOG.md | 1 + .../auxio/detail/AlbumDetailFragment.kt | 1 - .../detail/recycler/AlbumDetailAdapter.kt | 10 - .../BaseStyledImageView.kt} | 78 +-- .../org/oxycblt/auxio/image/ImageGroup.kt | 156 ++++++ .../auxio/image/ImageGroupIndicator.kt | 101 ++++ .../org/oxycblt/auxio/image/StyledDrawable.kt | 66 +++ .../oxycblt/auxio/image/StyledImageView.kt | 91 ++++ .../IndicatorMaterialButton.kt | 2 +- app/src/main/res/color/sel_track_cover.xml | 5 - .../res/drawable/ic_animated_equalizer.xml | 491 ++++++++++++++++++ app/src/main/res/drawable/ic_equalizer.xml | 11 - .../main/res/layout-h600dp/item_detail.xml | 4 +- .../layout-land/fragment_playback_panel.xml | 6 +- app/src/main/res/layout-land/item_detail.xml | 4 +- .../fragment_playback_panel.xml | 6 +- .../fragment_playback_panel.xml | 6 +- .../main/res/layout-sw600dp/item_detail.xml | 4 +- .../layout-sw640dp/fragment_playback_bar.xml | 2 +- .../main/res/layout-sw840dp/item_detail.xml | 4 +- .../fragment_playback_panel.xml | 6 +- .../main/res/layout/fragment_playback_bar.xml | 2 +- .../res/layout/fragment_playback_panel.xml | 8 +- app/src/main/res/layout/item_album_song.xml | 58 ++- app/src/main/res/layout/item_artist.xml | 4 +- app/src/main/res/layout/item_detail.xml | 4 +- app/src/main/res/layout/item_disc_header.xml | 4 +- app/src/main/res/layout/item_parent.xml | 4 +- app/src/main/res/layout/item_queue_song.xml | 4 +- app/src/main/res/layout/item_song.xml | 4 +- app/src/main/res/values-sw600dp/styles_ui.xml | 5 - .../main/res/values-v31/styles_android.xml | 1 - app/src/main/res/values/attrs.xml | 1 + 33 files changed, 993 insertions(+), 161 deletions(-) rename app/src/main/java/org/oxycblt/auxio/{ui/StyledImageView.kt => image/BaseStyledImageView.kt} (53%) create mode 100644 app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/image/ImageGroupIndicator.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/image/StyledDrawable.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt rename app/src/main/java/org/oxycblt/auxio/{ui => playback}/IndicatorMaterialButton.kt (98%) delete mode 100644 app/src/main/res/color/sel_track_cover.xml create mode 100644 app/src/main/res/drawable/ic_animated_equalizer.xml delete mode 100644 app/src/main/res/drawable/ic_equalizer.xml 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 @@ +