diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index 9048acf13..c39f267a5 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -45,7 +45,7 @@ import java.lang.Exception * so that songs don't end up fragmented across artists. Pretty much every OEM has added some * extension or quirk to MediaStore that I cannot reproduce, with some OEMs (COUGHSAMSUNGCOUGH) * crippling the normal tables so that you're railroaded into their music app. The way I do - * blacklisting relies on a deprecated method, and the supposedly "modern" method is SLOWER and + * blacklisting relies on a semi-deprecated method, and the supposedly "modern" method is SLOWER and * causes even more problems since I have to manage databases across version boundaries. Sometimes * music will have a deformed clone that I can't filter out, sometimes Genres will just break for * no reason, and sometimes tags encoded in UTF-8 will be interpreted as anything from UTF-16 to @@ -119,6 +119,8 @@ class MusicLoader { // DATA was deprecated on Android 10, but is set to be un-deprecated in Android 12L. // The only reason we'd want to change this is to add external partitions support, but // that's less efficient and there's no demand for that right now. + // TODO: Determine if grokking the actual DATA value outside of SQL is more or less + // efficient than the current system for (path in paths) { selector += " AND ${MediaStore.Audio.Media.DATA} NOT LIKE ?" args += "$path%" // Append % so that the selector properly detects children @@ -196,6 +198,7 @@ class MusicLoader { } } + // Deduplicate songs to prevent (most) deformed music clones songs = songs.distinctBy { it.name to it.internalMediaStoreAlbumName to it.internalMediaStoreArtistName to it.internalMediaStoreAlbumArtistName to it.track to it.duration @@ -261,6 +264,8 @@ class MusicLoader { // Album deduplication does not eliminate every case of fragmented artists, do // we deduplicate in the artist creation step as well. + // Note that we actually don't do this in groupBy. This is generally because we + // only want to default to a lowercase artist name when we have no other choice. val previousArtistIndex = artists.indexOfFirst { artist -> artist.name.lowercase() == artistName.lowercase() } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt new file mode 100644 index 000000000..c89d8ccba --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt @@ -0,0 +1,92 @@ +package org.oxycblt.auxio.playback + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.RectF +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageButton +import org.oxycblt.auxio.R +import org.oxycblt.auxio.util.getDimenSizeSafe +import org.oxycblt.auxio.util.getDrawableSafe + +/** + * An [AppCompatImageButton] designed for the buttons used in the playback display. + * + * Auxio's playback buttons have never followed the typical 24dp icon size that all + * other UI elements do, mostly because those icons just look bad at that size with + * all the gobs of whitespace surrounding them. So, this view resizes the icons to a + * fixed 32dp in a way that doesn't require a whole new icon set. + * + * This view also enables use of an "indicator", which is a dot that can denote when a + * button is active. This is useful for the shuffle/loop buttons, as at times highlighting + * them is not enough to + */ +class PlaybackButton @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = -1 +) : AppCompatImageButton(context, attrs, defStyleAttr) { + private val iconSize = context.getDimenSizeSafe(R.dimen.size_playback_icon) + private val centerMatrix = Matrix() + private val matrixSrc = RectF() + private val matrixDst = RectF() + private val indicatorDrawable: Drawable? + + init { + val size = context.getDimenSizeSafe(R.dimen.size_btn_small) + minimumWidth = size + minimumHeight = size + scaleType = ScaleType.MATRIX + setBackgroundResource(R.drawable.ui_large_unbounded_ripple) + + context.obtainStyledAttributes(attrs, R.styleable.PlaybackButton).use { arr -> + val hasIndicator = arr.getBoolean(R.styleable.PlaybackButton_hasIndicator, false) + indicatorDrawable = if (hasIndicator) { + context.getDrawableSafe(R.drawable.ui_indicator) + } else { + null + } + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + 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. + + // First scale the icon up to the desired size. + matrixSrc.set(0f, 0f, drawable.intrinsicWidth.toFloat(), drawable.intrinsicHeight.toFloat()) + matrixDst.set(0f, 0f, iconSize.toFloat(), iconSize.toFloat()) + 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 - iconSize) / 2f, (measuredHeight - iconSize) / 2f + ) + } + } + + indicatorDrawable?.let { indicator -> + val x = (measuredWidth - indicator.intrinsicWidth) / 2 + val y = ((measuredHeight - iconSize) / 2) + iconSize + + indicator.bounds.set( + x, y, x + indicator.intrinsicWidth, y + indicator.intrinsicHeight + ) + } + } + + override fun onDrawForeground(canvas: Canvas) { + super.onDrawForeground(canvas) + + if (indicatorDrawable != null && isActivated) { + indicatorDrawable.draw(canvas) + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt index a67f375d3..92ae2cd93 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -126,7 +126,10 @@ class PlaybackFragment : Fragment() { LoopMode.TRACK -> R.drawable.ic_loop_one } - binding.playbackLoop.setImageResource(resId) + binding.playbackLoop.apply { + isActivated = loopMode != LoopMode.NONE + setImageResource(resId) + } } playbackModel.position.observe(viewLifecycleOwner) { pos -> diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt index 6d5a7a2ca..027ccbf00 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt @@ -108,8 +108,6 @@ class PlaybackLayout @JvmOverloads constructor( } init { - setWillNotDraw(false) - // Set up our playback views. Doing this allows us to abstract away the implementation // of these views from the user of this layout [MainFragment]. playbackContainerView = FrameLayout(context).apply { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt index d56a2663d..d8a49490e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt @@ -142,7 +142,7 @@ class PlaybackNotification private constructor( loopMode: LoopMode ): NotificationCompat.Action { val drawableRes = when (loopMode) { - LoopMode.NONE -> R.drawable.ic_loop_off + LoopMode.NONE -> R.drawable.ic_remote_loop_off LoopMode.ALL -> R.drawable.ic_loop LoopMode.TRACK -> R.drawable.ic_loop_one } @@ -154,7 +154,7 @@ class PlaybackNotification private constructor( context: Context, isShuffled: Boolean ): NotificationCompat.Action { - val drawableRes = if (isShuffled) R.drawable.ic_shuffle else R.drawable.ic_shuffle_off + val drawableRes = if (isShuffled) R.drawable.ic_shuffle else R.drawable.ic_remote_shuffle_off return buildAction(context, PlaybackService.ACTION_SHUFFLE, drawableRes) } diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt index bd0a64a39..50e607e9e 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt @@ -179,17 +179,22 @@ private fun RemoteViews.applyFullControls(context: Context, state: WidgetState): ) ) - // While it is technically possible to use the setColorFilter to tint these buttons, its - // actually less efficient than using duplicate drawables. - // And no, we can't control state drawables with RemoteViews. Because of course we can't. - + // RemoteView is so restrictive that emulating auxio's playback icons in a sensible way is + // more or less impossible, including: + // 1. Setting foreground drawables + // 2. Applying custom image matrices + // 3. Tinting icons at all + // + // So, we have to do the dumbest possible method of duplicating each drawable and hard-coding + // indicators, tints, and icon sizes. And then google wonders why nobody uses widgets on + // android. val shuffleRes = when { - state.isShuffled -> R.drawable.ic_shuffle_on - else -> R.drawable.ic_shuffle + state.isShuffled -> R.drawable.ic_remote_shuffle_on + else -> R.drawable.ic_remote_shuffle_off } val loopRes = when (state.loopMode) { - LoopMode.NONE -> R.drawable.ic_loop + LoopMode.NONE -> R.drawable.ic_remote_loop_off LoopMode.ALL -> R.drawable.ic_loop_on LoopMode.TRACK -> R.drawable.ic_loop_one } diff --git a/app/src/main/res/drawable/ic_loop_off.xml b/app/src/main/res/drawable/ic_remote_loop_off.xml similarity index 89% rename from app/src/main/res/drawable/ic_loop_off.xml rename to app/src/main/res/drawable/ic_remote_loop_off.xml index fb09414e1..433c53d66 100644 --- a/app/src/main/res/drawable/ic_loop_off.xml +++ b/app/src/main/res/drawable/ic_remote_loop_off.xml @@ -2,6 +2,7 @@ diff --git a/app/src/main/res/drawable/ic_shuffle_on.xml b/app/src/main/res/drawable/ic_remote_shuffle_on.xml similarity index 100% rename from app/src/main/res/drawable/ic_shuffle_on.xml rename to app/src/main/res/drawable/ic_remote_shuffle_on.xml diff --git a/app/src/main/res/drawable/ic_shuffle.xml b/app/src/main/res/drawable/ic_shuffle.xml index 4a14cbffa..a22ea2e59 100644 --- a/app/src/main/res/drawable/ic_shuffle.xml +++ b/app/src/main/res/drawable/ic_shuffle.xml @@ -2,7 +2,7 @@ + + + + diff --git a/app/src/main/res/drawable/ui_small_unbounded_ripple.xml b/app/src/main/res/drawable/ui_large_unbounded_ripple.xml similarity index 72% rename from app/src/main/res/drawable/ui_small_unbounded_ripple.xml rename to app/src/main/res/drawable/ui_large_unbounded_ripple.xml index b4367d84b..3c999556f 100644 --- a/app/src/main/res/drawable/ui_small_unbounded_ripple.xml +++ b/app/src/main/res/drawable/ui_large_unbounded_ripple.xml @@ -1,4 +1,4 @@ + android:radius="@dimen/size_large_unbounded_ripple" /> diff --git a/app/src/main/res/drawable/ui_widget_aspect_ratio.xml b/app/src/main/res/drawable/ui_remote_aspect_ratio.xml similarity index 100% rename from app/src/main/res/drawable/ui_widget_aspect_ratio.xml rename to app/src/main/res/drawable/ui_remote_aspect_ratio.xml diff --git a/app/src/main/res/drawable/ui_unbounded_ripple.xml b/app/src/main/res/drawable/ui_unbounded_ripple.xml index 003c9a27b..3118661f4 100644 --- a/app/src/main/res/drawable/ui_unbounded_ripple.xml +++ b/app/src/main/res/drawable/ui_unbounded_ripple.xml @@ -1,4 +1,4 @@ + android:radius="@dimen/size_small_unbounded_ripple" /> diff --git a/app/src/main/res/layout-land/fragment_playback.xml b/app/src/main/res/layout-land/fragment_playback.xml index 89e89c49f..6a5d9cee3 100644 --- a/app/src/main/res/layout-land/fragment_playback.xml +++ b/app/src/main/res/layout-land/fragment_playback.xml @@ -120,7 +120,7 @@ app:layout_constraintTop_toBottomOf="@+id/playback_cover" app:layout_constraintVertical_chainStyle="packed" /> - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -76,7 +76,7 @@ @@ -77,7 +77,7 @@ @@ -63,7 +63,7 @@ @@ -63,7 +63,7 @@ \ No newline at end of file diff --git a/app/src/main/res/values-v31/styles_core.xml b/app/src/main/res/values-v31/styles_core.xml index 977e3313c..8b2afb083 100644 --- a/app/src/main/res/values-v31/styles_core.xml +++ b/app/src/main/res/values-v31/styles_core.xml @@ -4,12 +4,13 @@ - - + + +