From e6f6d1ccf89e66e07194cf13bba4d247b3e0ba2f Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Wed, 15 Jun 2022 16:11:41 -0600 Subject: [PATCH] image: remove animated indicator Remove the animated indicator, replacing it with a static one. I wish I could have kept this, but once again android is a sh******ed mess and makes it impossible to dynamically animate something depending on the playing state. It will restart the animation, ignore calls to stop, or just flat out now run the code path in the first place due to race conditions. --- CHANGELOG.md | 2 +- .../auxio/detail/DetailAppBarLayout.kt | 3 +- .../auxio/detail/recycler/DetailAdapter.kt | 1 - .../org/oxycblt/auxio/home/HomeFragment.kt | 19 +- .../org/oxycblt/auxio/image/ImageGroup.kt | 9 +- .../auxio/image/ImageGroupIndicator.kt | 101 ---- .../oxycblt/auxio/music/StorageFramework.kt | 13 +- .../auxio/music/backend/ExoPlayerBackend.kt | 1 - .../auxio/music/backend/MediaStoreBackend.kt | 2 +- .../auxio/settings/ui/IntListPreference.kt | 2 +- .../org/oxycblt/auxio/ui/BottomSheetLayout.kt | 4 +- .../java/org/oxycblt/auxio/ui/MenuFragment.kt | 25 + .../org/oxycblt/auxio/util/PrimitiveUtil.kt | 15 +- .../res/drawable/ic_animated_equalizer.xml | 491 ------------------ app/src/main/res/drawable/ic_equalizer.xml | 13 + 15 files changed, 72 insertions(+), 629 deletions(-) delete mode 100644 app/src/main/java/org/oxycblt/auxio/image/ImageGroupIndicator.kt delete mode 100644 app/src/main/res/drawable/ic_animated_equalizer.xml create mode 100644 app/src/main/res/drawable/ic_equalizer.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index c5ed2bee1..70c754a45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - Added a new view for song properties (Such as Bitrate) - The playback bar now has a new design, with an improved progress indicator and a skip action -- When playing, covers now shows an animated indicator +- When playing, covers now show an 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/DetailAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt index 9a8bae0cb..3394d33c1 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt @@ -153,6 +153,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr } companion object { - private val TOOLBAR_TITLE_TEXT_FIELD: Field by lazyReflectedField("mTitleTextView") + private val TOOLBAR_TITLE_TEXT_FIELD: Field by + lazyReflectedField(Toolbar::class, "mTitleTextView") } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/DetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/DetailAdapter.kt index 234b496d8..c3e1314ab 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/DetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/DetailAdapter.kt @@ -95,7 +95,6 @@ abstract class DetailAdapter( } } - // TODO: Pause indicator animation when not playing viewHolder.itemView.isActivated = shouldHighlightViewHolder(item) } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index c10c726c7..e1785b6a3 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -388,18 +388,9 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI */ private fun ViewPager2.reduceSensitivity(by: Int) { try { - val recycler = - ViewPager2::class.java.getDeclaredField("mRecyclerView").run { - isAccessible = true - get(this@reduceSensitivity) - } - - RecyclerView::class.java.getDeclaredField("mTouchSlop").apply { - isAccessible = true - - val slop = get(recycler) as Int - set(recycler, slop * by) - } + val recycler = VIEW_PAGER_RECYCLER_FIELD.get(this@reduceSensitivity) + val slop = VIEW_PAGER_TOUCH_SLOP_FIELD.get(recycler) as Int + VIEW_PAGER_TOUCH_SLOP_FIELD.set(recycler, slop * by) } catch (e: Exception) { logE("Unable to reduce ViewPager sensitivity (likely an internal code change)") e.logTraceOrThrow() @@ -429,8 +420,8 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI companion object { private val VIEW_PAGER_RECYCLER_FIELD: Field by - lazyReflectedField("mRecyclerView") + lazyReflectedField(ViewPager2::class, "mRecyclerView") private val VIEW_PAGER_TOUCH_SLOP_FIELD: Field by - lazyReflectedField("mTouchSlop") + lazyReflectedField(RecyclerView::class, "mTouchSlop") } } diff --git a/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt b/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt index af7e9d8e1..905f0a3b9 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt @@ -31,12 +31,13 @@ import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.util.getColorStateListSafe +import org.oxycblt.auxio.util.getDrawableSafe /** * Effectively a super-charged [StyledImageView]. * * This class enables the following features alongside the base features pf [StyledImageView]: - * - Activation indicator with an animated icon + * - Activation indicator * - (Eventually) selection indicator * - Support for ONE custom view * @@ -53,7 +54,11 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr private val inner = BaseStyledImageView(context, attrs) private var customView: View? = null - private val indicator = ImageGroupIndicator(context) + private val indicator = + BaseStyledImageView(context).apply { + setImageDrawable( + StyledDrawable(context, context.getDrawableSafe(R.drawable.ic_equalizer))) + } init { // Android wants you to make separate attributes for each view type, but will diff --git a/app/src/main/java/org/oxycblt/auxio/image/ImageGroupIndicator.kt b/app/src/main/java/org/oxycblt/auxio/image/ImageGroupIndicator.kt deleted file mode 100644 index e8725b3b9..000000000 --- a/app/src/main/java/org/oxycblt/auxio/image/ImageGroupIndicator.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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/music/StorageFramework.kt b/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt index 81a1f72f2..cad73113c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt @@ -81,8 +81,8 @@ data class Directory(val volume: StorageVolume, val relativePath: String) { } private val SM_API21_GET_VOLUME_LIST_METHOD: Method by - lazyReflectedMethod("getVolumeList") -private val SV_API21_GET_PATH_METHOD: Method by lazyReflectedMethod("getPath") + lazyReflectedMethod(StorageManager::class, "getVolumeList") +private val SV_API21_GET_PATH_METHOD: Method by lazyReflectedMethod(StorageVolume::class, "getPath") /** * A list of recognized volumes, retrieved in a compatible manner. Note that these volumes may be @@ -132,12 +132,21 @@ val StorageVolume.isEmulatedCompat: Boolean val StorageVolume.isInternalCompat: Boolean get() = isPrimaryCompat && isEmulatedCompat +/** Returns the UUID of the volume in a compatible manner. */ val StorageVolume.uuidCompat: String? @SuppressLint("NewApi") get() = uuid +/* + * Returns the state of the volume in a compatible manner. + */ val StorageVolume.stateCompat: String @SuppressLint("NewApi") get() = state +/** + * Returns the name of this volume as it is used in [MediaStore]. This will be + * [MediaStore.VOLUME_EXTERNAL_PRIMARY] if it is the primary volume, and the lowercase UUID of the + * volume otherwise. + */ val StorageVolume.mediaStoreVolumeNameCompat: String? get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt index f5ed0e372..0af14cb9d 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt @@ -122,7 +122,6 @@ class ExoPlayerBackend(private val inner: MediaStoreBackend) : Indexer.Backend { /** * Wraps an ExoPlayer metadata retrieval task in a safe abstraction. Access is done with [get]. - * * @author OxygenCobalt */ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt index 275c3c3e7..02629b1c4 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt @@ -160,7 +160,7 @@ abstract class MediaStoreBackend : Indexer.Backend { selector += ')' } - logD("Starting query [selector: $selector, args: $args]") + logD("Starting query [proj: ${projection.map { it }}, selector: $selector, args: $args]") return requireNotNull( context.contentResolverSafe.queryCursor( diff --git a/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreference.kt b/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreference.kt index 942385925..d96461ad4 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreference.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreference.kt @@ -111,6 +111,6 @@ constructor( companion object { private val PREFERENCE_DEFAULT_VALUE_FIELD: Field by - lazyReflectedField("mDefaultValue") + lazyReflectedField(Preference::class, "mDefaultValue") } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt index 02d0aebdd..4559ed93a 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt @@ -387,7 +387,6 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : // not apply any window insets at all, which results in scroll desynchronization on // certain views. This is considered tolerable as the other options are to convert // the playback fragments to views, which is not nice. - logD("Readjusting window insets") val bars = insets.getSystemBarInsetsCompat(this) val consumedByPanel = computePanelTopPosition(panelOffset) - measuredHeight val adjustedBottomInset = (consumedByPanel + bars.bottom).coerceAtLeast(0) @@ -500,7 +499,6 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : private fun setPanelStateInternal(state: PanelState) { if (panelState == state) { - logD("State is already $state, not applying") return } @@ -672,7 +670,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : companion object { private val INIT_PANEL_STATE = PanelState.HIDDEN private val VIEW_DRAG_HELPER_STATE_FIELD: Field by - lazyReflectedField("mDragState") + lazyReflectedField(ViewDragHelper::class, "mDragState") private const val MIN_FLING_VEL = 400 private const val KEY_PANEL_STATE = BuildConfig.APPLICATION_ID + ".key.PANEL_STATE" diff --git a/app/src/main/java/org/oxycblt/auxio/ui/MenuFragment.kt b/app/src/main/java/org/oxycblt/auxio/ui/MenuFragment.kt index 208ab28a5..ce53bcd00 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/MenuFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/MenuFragment.kt @@ -33,12 +33,21 @@ import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logEOrThrow import org.oxycblt.auxio.util.showToast +/** + * A fragment capable of creating menus. Automatically keeps track of and disposes of menus, + * preventing UI issues and memory leaks. + * @author OxygenCobalt + */ abstract class MenuFragment : ViewBindingFragment() { private var currentMenu: PopupMenu? = null protected val playbackModel: PlaybackViewModel by activityViewModels() protected val navModel: NavigationViewModel by activityViewModels() + /** + * Opens the given menu in context of [song]. Assumes that the menu is only composed of common + * [Song] options. + */ protected fun musicMenu(anchor: View, @MenuRes menuRes: Int, song: Song) { logD("Launching new song menu: ${song.rawName}") @@ -71,6 +80,10 @@ abstract class MenuFragment : ViewBindingFragment() { } } + /** + * Opens the given menu in context of [album]. Assumes that the menu is only composed of common + * [Album] options. + */ protected fun musicMenu(anchor: View, @MenuRes menuRes: Int, album: Album) { logD("Launching new album menu: ${album.rawName}") @@ -103,6 +116,10 @@ abstract class MenuFragment : ViewBindingFragment() { } } + /** + * Opens the given menu in context of [artist]. Assumes that the menu is only composed of common + * [Artist] options. + */ protected fun musicMenu(anchor: View, @MenuRes menuRes: Int, artist: Artist) { logD("Launching new artist menu: ${artist.rawName}") @@ -132,6 +149,10 @@ abstract class MenuFragment : ViewBindingFragment() { } } + /** + * Opens the given menu in context of [genre]. Assumes that the menu is only composed of common + * [Genre] options. + */ protected fun musicMenu(anchor: View, @MenuRes menuRes: Int, genre: Genre) { logD("Launching new genre menu: ${genre.rawName}") @@ -173,6 +194,10 @@ abstract class MenuFragment : ViewBindingFragment() { } } + /** + * Open a generic menu with configuration in [block]. If a menu is already opened, then this + * function is a no-op. + */ protected fun menu(anchor: View, @MenuRes menuRes: Int, block: PopupMenu.() -> Unit) { if (currentMenu != null) { logD("Menu already present, not launching") diff --git a/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt index f2d708eac..d26ae2b3b 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt @@ -22,6 +22,7 @@ import android.text.format.DateUtils import androidx.core.math.MathUtils import java.lang.reflect.Field import java.lang.reflect.Method +import kotlin.reflect.KClass import org.oxycblt.auxio.BuildConfig /** Assert that we are on a background thread. */ @@ -68,17 +69,11 @@ fun Long.formatDuration(isElapsed: Boolean): String { } /** Lazily reflect to retrieve a [Field]. */ -inline fun lazyReflectedField(field: String): Lazy = lazy { - T::class.java.getDeclaredField(field).also { it.isAccessible = true } +fun lazyReflectedField(clazz: KClass<*>, field: String) = lazy { + clazz.java.getDeclaredField(field).also { it.isAccessible = true } } /** Lazily reflect to retrieve a [Method]. */ -inline fun lazyReflectedMethod( - methodName: String, - vararg parameterTypes: Any -): Lazy = lazy { - T::class - .java - .getDeclaredMethod(methodName, *parameterTypes.map { it::class.java }.toTypedArray()) - .also { it.isAccessible = true } +fun lazyReflectedMethod(clazz: KClass<*>, method: String) = lazy { + clazz.java.getDeclaredMethod(method).also { it.isAccessible = true } } diff --git a/app/src/main/res/drawable/ic_animated_equalizer.xml b/app/src/main/res/drawable/ic_animated_equalizer.xml deleted file mode 100644 index a6531fd52..000000000 --- a/app/src/main/res/drawable/ic_animated_equalizer.xml +++ /dev/null @@ -1,491 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_equalizer.xml b/app/src/main/res/drawable/ic_equalizer.xml new file mode 100644 index 000000000..c131a0cea --- /dev/null +++ b/app/src/main/res/drawable/ic_equalizer.xml @@ -0,0 +1,13 @@ + + + + +