From 4b919b121ac4e2581cec656c96937bbec5601cb8 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sun, 6 Feb 2022 10:35:21 -0700 Subject: [PATCH] util: rework context utilities Completely rework the Context extensions for resources. Previously, Auxio has used a strange hodge-podge of context extensions and verbose code to get resources. Fix this by unifying most of the resource accesses under a single, unified set of extensions. The only ones excluded for now is the getString call, as that is used in far too many places to effectively replace. --- .../org/oxycblt/auxio/accent/AccentAdapter.kt | 13 +- .../auxio/accent/AutoGridLayoutManager.kt | 6 +- .../detail/recycler/AlbumDetailAdapter.kt | 4 +- .../detail/recycler/ArtistDetailAdapter.kt | 6 +- .../detail/recycler/GenreDetailAdapter.kt | 4 +- .../home/AdaptiveFloatingActionButton.kt | 15 +- .../fastscroll/FastScrollPopupDrawable.kt | 9 +- .../home/fastscroll/FastScrollRecyclerView.kt | 26 +-- .../org/oxycblt/auxio/music/MusicUtils.kt | 6 +- .../oxycblt/auxio/playback/PlaybackBarView.kt | 4 +- .../oxycblt/auxio/playback/PlaybackLayout.kt | 16 +- .../oxycblt/auxio/playback/PlaybackSeekBar.kt | 12 +- .../auxio/playback/queue/QueueAdapter.kt | 4 +- .../auxio/playback/queue/QueueDragCallback.kt | 3 +- .../auxio/settings/pref/M3SwitchPreference.kt | 12 +- .../org/oxycblt/auxio/util/ContextUtil.kt | 210 +++++++++++++++--- .../java/org/oxycblt/auxio/util/ViewUtil.kt | 66 +----- .../oxycblt/auxio/widgets/WidgetProvider.kt | 8 +- app/src/main/res/values/styles_android.xml | 3 + app/src/main/res/values/styles_ui.xml | 2 +- 20 files changed, 261 insertions(+), 168 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt index 9a48209a5..f42ecb3c9 100644 --- a/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt @@ -18,16 +18,15 @@ package org.oxycblt.auxio.accent -import android.content.res.ColorStateList import android.view.ViewGroup import androidx.appcompat.widget.TooltipCompat import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemAccentBinding +import org.oxycblt.auxio.util.getAttrColorSafe +import org.oxycblt.auxio.util.getColorSafe import org.oxycblt.auxio.util.inflater -import org.oxycblt.auxio.util.resolveAttr -import org.oxycblt.auxio.util.resolveColor -import org.oxycblt.auxio.util.resolveStateList +import org.oxycblt.auxio.util.stateList /** * An adapter that displays the list of all possible accents, and highlights the current one. @@ -63,7 +62,7 @@ class AccentAdapter( setSelected(accent == curAccent) binding.accent.apply { - backgroundTintList = ColorStateList.valueOf(accent.primary.resolveColor(context)) + backgroundTintList = context.getColorSafe(accent.primary).stateList contentDescription = context.getString(accent.name) TooltipCompat.setTooltipText(this, contentDescription) } @@ -84,9 +83,9 @@ class AccentAdapter( selectedViewHolder?.setSelected(false) selectedViewHolder = this - ColorStateList.valueOf(R.attr.colorSurface.resolveAttr(context)) + context.getAttrColorSafe(R.attr.colorSurface).stateList } else { - android.R.color.transparent.resolveStateList(context) + context.getColorSafe(android.R.color.transparent).stateList } } } diff --git a/app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt b/app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt index ab853c447..4d371927e 100644 --- a/app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt @@ -20,9 +20,9 @@ package org.oxycblt.auxio.accent import android.content.Context import android.util.AttributeSet -import android.util.TypedValue import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.util.pxOfDp import kotlin.math.max /** @@ -38,9 +38,7 @@ class AutoGridLayoutManager( ) : GridLayoutManager(context, attrs, defStyleAttr, defStyleRes) { // We use 72dp here since that's the rough size of the accent item. // This will need to be modified if this is used beyond the accent dialog. - private var columnWidth = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 72F, context.resources.displayMetrics - ).toInt() + private var columnWidth = context.pxOfDp(72f) private var lastWidth = -1 private var lastHeight = -1 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 bbc3e0c33..9ed3d3998 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 @@ -36,7 +36,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.ActionHeaderViewHolder import org.oxycblt.auxio.ui.BaseViewHolder import org.oxycblt.auxio.ui.DiffCallback -import org.oxycblt.auxio.util.getPlural +import org.oxycblt.auxio.util.getPluralSafe import org.oxycblt.auxio.util.inflater /** @@ -156,7 +156,7 @@ class AlbumDetailAdapter( binding.detailInfo.text = binding.detailInfo.context.getString( R.string.fmt_three, data.year.toDate(binding.detailInfo.context), - binding.detailInfo.context.getPlural( + binding.detailInfo.context.getPluralSafe( R.plurals.fmt_song_count, data.songs.size ), diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt index 44d5a4cc8..02b89073c 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt @@ -38,7 +38,7 @@ import org.oxycblt.auxio.ui.ActionHeaderViewHolder import org.oxycblt.auxio.ui.BaseViewHolder import org.oxycblt.auxio.ui.DiffCallback import org.oxycblt.auxio.ui.HeaderViewHolder -import org.oxycblt.auxio.util.getPlural +import org.oxycblt.auxio.util.getPluralSafe import org.oxycblt.auxio.util.inflater /** @@ -207,8 +207,8 @@ class ArtistDetailAdapter( binding.detailInfo.text = context.getString( R.string.fmt_counts, - context.getPlural(R.plurals.fmt_album_count, data.albums.size), - context.getPlural(R.plurals.fmt_song_count, data.songs.size) + context.getPluralSafe(R.plurals.fmt_album_count, data.albums.size), + context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size) ) binding.detailPlayButton.setOnClickListener { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt index 30cfa5a54..9f0e8366c 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt @@ -34,7 +34,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.ActionHeaderViewHolder import org.oxycblt.auxio.ui.BaseViewHolder import org.oxycblt.auxio.ui.DiffCallback -import org.oxycblt.auxio.util.getPlural +import org.oxycblt.auxio.util.getPluralSafe import org.oxycblt.auxio.util.inflater /** @@ -145,7 +145,7 @@ class GenreDetailAdapter( binding.detailName.text = data.resolvedName binding.detailSubhead.apply { - text = context.getPlural(R.plurals.fmt_song_count, data.songs.size) + text = context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size) } binding.detailInfo.text = data.totalDuration diff --git a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt index a022a4964..434820910 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt @@ -3,6 +3,7 @@ package org.oxycblt.auxio.home import android.content.Context import android.util.AttributeSet import com.google.android.material.floatingactionbutton.FloatingActionButton +import org.oxycblt.auxio.util.getDimenSizeSafe import com.google.android.material.R as MaterialR /** @@ -20,11 +21,17 @@ class AdaptiveFloatingActionButton @JvmOverloads constructor( size = SIZE_NORMAL if (resources.configuration.smallestScreenWidthDp >= 640) { - // Use a large FAB on large screens, as it makes it easier to touch. - customSize = resources.getDimensionPixelSize(MaterialR.dimen.m3_large_fab_size) - setMaxImageSize( - resources.getDimensionPixelSize(MaterialR.dimen.m3_large_fab_max_image_size) + val largeFabSize = context.getDimenSizeSafe( + MaterialR.dimen.m3_large_fab_size ) + + val largeImageSize = context.getDimenSizeSafe( + MaterialR.dimen.m3_large_fab_max_image_size + ) + + // Use a large FAB on large screens, as it makes it easier to touch. + customSize = largeFabSize + setMaxImageSize(largeImageSize) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt index a15b6cc65..1f08b1468 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt @@ -31,7 +31,8 @@ import android.os.Build import android.view.View import androidx.core.graphics.drawable.DrawableCompat import org.oxycblt.auxio.R -import org.oxycblt.auxio.util.resolveAttr +import org.oxycblt.auxio.util.getAttrColorSafe +import org.oxycblt.auxio.util.getDimenOffsetSafe import kotlin.math.sqrt /** @@ -54,15 +55,15 @@ import kotlin.math.sqrt class FastScrollPopupDrawable(context: Context) : Drawable() { private val paint: Paint = Paint().apply { isAntiAlias = true - color = R.attr.colorSecondary.resolveAttr(context) + color = context.getAttrColorSafe(R.attr.colorSecondary) style = Paint.Style.FILL } private val path = Path() private val matrix = Matrix() - private val paddingStart = context.resources.getDimensionPixelOffset(R.dimen.spacing_medium) - private val paddingEnd = context.resources.getDimensionPixelOffset(R.dimen.popup_padding_end) + private val paddingStart = context.getDimenOffsetSafe(R.dimen.spacing_medium) + private val paddingEnd = context.getDimenOffsetSafe(R.dimen.popup_padding_end) override fun draw(canvas: Canvas) { canvas.drawPath(path, paint) diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt index 74c9ed99d..77a1ec5e2 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt @@ -42,8 +42,10 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R import org.oxycblt.auxio.util.canScroll -import org.oxycblt.auxio.util.resolveAttr -import org.oxycblt.auxio.util.resolveDrawable +import org.oxycblt.auxio.util.getAttrColorSafe +import org.oxycblt.auxio.util.getDimenOffsetSafe +import org.oxycblt.auxio.util.getDimenSizeSafe +import org.oxycblt.auxio.util.getDrawableSafe import org.oxycblt.auxio.util.systemBarInsetsCompat import kotlin.math.abs @@ -86,7 +88,7 @@ class FastScrollRecyclerView @JvmOverloads constructor( */ var onDragListener: ((Boolean) -> Unit)? = null - private val minTouchTargetSize: Int = resources.getDimensionPixelSize(R.dimen.size_btn_small) + private val minTouchTargetSize: Int = context.getDimenSizeSafe(R.dimen.size_btn_small) private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop // Views for the track, thumb, and popup. Note that the track view is mostly vestigial @@ -122,7 +124,7 @@ class FastScrollRecyclerView @JvmOverloads constructor( private val scrollerPadding = Rect(0, 0, 0, 0) init { - val thumbDrawable = R.drawable.ui_scroll_thumb.resolveDrawable(context) + val thumbDrawable = context.getDrawableSafe(R.drawable.ui_scroll_thumb) trackView = View(context) thumbView = View(context).apply { @@ -136,25 +138,19 @@ class FastScrollRecyclerView @JvmOverloads constructor( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ) - minimumWidth = resources.getDimensionPixelSize( - R.dimen.popup_min_width - ) - minimumHeight = resources.getDimensionPixelSize( - R.dimen.size_btn_large - ) + minimumWidth = context.getDimenSizeSafe(R.dimen.popup_min_width) + minimumHeight = context.getDimenSizeSafe(R.dimen.size_btn_large) (layoutParams as FrameLayout.LayoutParams).apply { gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP - marginEnd = resources.getDimensionPixelOffset( - R.dimen.spacing_small - ) + marginEnd = context.getDimenOffsetSafe(R.dimen.spacing_small) } TextViewCompat.setTextAppearance(this, R.style.TextAppearance_Auxio_HeadlineLarge) - setTextColor(R.attr.colorOnSecondary.resolveAttr(context)) + setTextColor(context.getAttrColorSafe(R.attr.colorOnSecondary)) background = FastScrollPopupDrawable(context) - elevation = resources.getDimensionPixelOffset(R.dimen.elevation_normal).toFloat() + elevation = context.getDimenSizeSafe(R.dimen.elevation_normal).toFloat() ellipsize = TextUtils.TruncateAt.MIDDLE gravity = Gravity.CENTER includeFontPadding = false diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt index a20a83ca2..cc3d87b49 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt @@ -24,7 +24,7 @@ import android.widget.TextView import androidx.core.text.isDigitsOnly import androidx.databinding.BindingAdapter import org.oxycblt.auxio.R -import org.oxycblt.auxio.util.getPlural +import org.oxycblt.auxio.util.getPluralSafe /** * A complete array of all the hardcoded genre values for ID3(v2), contains standard genres and @@ -128,7 +128,7 @@ fun Int.toDate(context: Context): String { fun TextView.bindArtistCounts(artist: Artist) { text = context.getString( R.string.fmt_counts, - context.getPlural(R.plurals.fmt_album_count, artist.albums.size), - context.getPlural(R.plurals.fmt_song_count, artist.songs.size) + context.getPluralSafe(R.plurals.fmt_album_count, artist.albums.size), + context.getPluralSafe(R.plurals.fmt_song_count, artist.songs.size) ) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarView.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarView.kt index 81599c23b..695170e97 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarView.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarView.kt @@ -30,8 +30,8 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ViewPlaybackBarBinding import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.util.getAttrColorSafe import org.oxycblt.auxio.util.inflater -import org.oxycblt.auxio.util.resolveAttr import org.oxycblt.auxio.util.systemBarInsetsCompat /** @@ -53,7 +53,7 @@ class PlaybackBarView @JvmOverloads constructor( // colorSurfaceVariant is used with the assumption that the view that is using it is // not elevated and is therefore not colored. This view is elevated. binding.playbackProgressBar.trackColor = MaterialColors.compositeARGBWithAlpha( - R.attr.colorSecondary.resolveAttr(context), (255 * 0.2).toInt() + context.getAttrColorSafe(R.attr.colorSecondary), (255 * 0.2).toInt() ) } 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 bb02d6102..fb553cb8b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt @@ -1,7 +1,6 @@ package org.oxycblt.auxio.playback import android.content.Context -import android.content.res.ColorStateList import android.graphics.Canvas import android.graphics.Rect import android.graphics.drawable.LayerDrawable @@ -24,9 +23,12 @@ import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.util.getAttrColorSafe +import org.oxycblt.auxio.util.getDimenSafe +import org.oxycblt.auxio.util.getDrawableSafe +import org.oxycblt.auxio.util.pxOfDp import org.oxycblt.auxio.util.replaceInsetsCompat -import org.oxycblt.auxio.util.resolveAttr -import org.oxycblt.auxio.util.resolveDrawable +import org.oxycblt.auxio.util.stateList import org.oxycblt.auxio.util.systemBarInsetsCompat import kotlin.math.abs import kotlin.math.max @@ -96,6 +98,7 @@ class PlaybackLayout @JvmOverloads constructor( private var initMotionX = 0f private var initMotionY = 0f private val tRect = Rect() + private val elevationNormal = context.getDimenSafe(R.dimen.elevation_normal) /** See [isDragging] */ private val dragStateField = ViewDragHelper::class.java.getDeclaredField("mDragState").apply { @@ -115,15 +118,15 @@ class PlaybackLayout @JvmOverloads constructor( isFocusableInTouchMode = false playbackContainerBg = MaterialShapeDrawable.createWithElevationOverlay(context).apply { - fillColor = ColorStateList.valueOf(R.attr.colorSurface.resolveAttr(context)) - elevation = resources.getDimensionPixelSize(R.dimen.elevation_normal).toFloat() + fillColor = context.getAttrColorSafe(R.attr.colorSurface).stateList + elevation = context.pxOfDp(elevationNormal).toFloat() } // The way we fade out the elevation overlay is not by actually reducing the elevation // but by fading out the background drawable itself. To be safe, we apply this // background drawable to a layer list with another colorSurface shape drawable, just // in case weird things happen if background drawable is completely transparent. - background = (R.drawable.ui_panel_bg.resolveDrawable(context) as LayerDrawable).apply { + background = (context.getDrawableSafe(R.drawable.ui_panel_bg) as LayerDrawable).apply { setDrawableByLayerId(R.id.panel_overlay, playbackContainerBg) } } @@ -534,6 +537,7 @@ class PlaybackLayout @JvmOverloads constructor( // Slowly reduce the elevation of the container as we slide up, eventually resulting in a // neutral color instead of an elevated one when fully expanded. playbackContainerBg.alpha = (outRatio * 255).toInt() + playbackContainerView.translationZ = elevationNormal * outRatio // Fade out our bar view as we slide up playbackBarView.apply { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt index 4e219388e..f2e517cd0 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt @@ -20,7 +20,6 @@ package org.oxycblt.auxio.playback import android.annotation.SuppressLint import android.content.Context -import android.content.res.ColorStateList import android.util.AttributeSet import androidx.constraintlayout.widget.ConstraintLayout import com.google.android.material.color.MaterialColors @@ -28,8 +27,9 @@ import com.google.android.material.slider.Slider import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ViewSeekBarBinding import org.oxycblt.auxio.music.toDuration +import org.oxycblt.auxio.util.getAttrColorSafe import org.oxycblt.auxio.util.inflater -import org.oxycblt.auxio.util.resolveAttr +import org.oxycblt.auxio.util.stateList /** * A custom view that bundles together a seekbar with a current duration and a total duration. @@ -53,11 +53,9 @@ class PlaybackSeekBar @JvmOverloads constructor( binding.seekBar.addOnSliderTouchListener(this) // Override the inactive color so that it lines up with the playback progress bar. - binding.seekBar.trackInactiveTintList = ColorStateList.valueOf( - MaterialColors.compositeARGBWithAlpha( - R.attr.colorSecondary.resolveAttr(context), (255 * 0.2).toInt() - ) - ) + binding.seekBar.trackInactiveTintList = MaterialColors.compositeARGBWithAlpha( + context.getAttrColorSafe(R.attr.colorSecondary), (255 * 0.2).toInt() + ).stateList } fun setProgress(seconds: Long) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt index ccecc8a41..4d1984a3d 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -19,7 +19,6 @@ package org.oxycblt.auxio.playback.queue import android.annotation.SuppressLint -import android.content.res.ColorStateList import android.graphics.drawable.ColorDrawable import android.view.MotionEvent import android.view.View @@ -40,6 +39,7 @@ import org.oxycblt.auxio.ui.DiffCallback import org.oxycblt.auxio.ui.HeaderViewHolder import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.logE +import org.oxycblt.auxio.util.stateList /** * The single adapter for both the Next Queue and the User Queue. @@ -130,7 +130,7 @@ class QueueAdapter( binding.body.background = MaterialShapeDrawable.createWithElevationOverlay( binding.root.context ).apply { - fillColor = ColorStateList.valueOf((binding.body.background as ColorDrawable).color) + fillColor = (binding.body.background as ColorDrawable).color.stateList } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt index 4cffd8bb9..b18bbcca3 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt @@ -26,6 +26,7 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.shape.MaterialShapeDrawable import org.oxycblt.auxio.R import org.oxycblt.auxio.playback.PlaybackViewModel +import org.oxycblt.auxio.util.getDimenSafe import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -90,7 +91,7 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc if (shouldLift && isCurrentlyActive && actionState == ItemTouchHelper.ACTION_STATE_DRAG) { val bg = holder.bodyView.background as MaterialShapeDrawable - val elevation = recyclerView.resources.getDimension(R.dimen.elevation_small) + val elevation = recyclerView.context.getDimenSafe(R.dimen.elevation_small) holder.itemView.animate() .translationZ(elevation) diff --git a/app/src/main/java/org/oxycblt/auxio/settings/pref/M3SwitchPreference.kt b/app/src/main/java/org/oxycblt/auxio/settings/pref/M3SwitchPreference.kt index 25bc1a598..db2598f1e 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/pref/M3SwitchPreference.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/pref/M3SwitchPreference.kt @@ -7,8 +7,8 @@ import androidx.appcompat.widget.SwitchCompat import androidx.preference.PreferenceViewHolder import androidx.preference.SwitchPreferenceCompat import org.oxycblt.auxio.R -import org.oxycblt.auxio.util.resolveDrawable -import org.oxycblt.auxio.util.resolveStateList +import org.oxycblt.auxio.util.getColorStateListSafe +import org.oxycblt.auxio.util.getDrawableSafe /** * A [SwitchPreferenceCompat] that emulates the M3 switches until the design team @@ -31,10 +31,10 @@ class M3SwitchPreference @JvmOverloads constructor( if (switch is SwitchCompat) { switch.apply { - trackDrawable = R.drawable.ui_m3_switch_track.resolveDrawable(context) - trackTintList = R.color.sel_m3_switch_track.resolveStateList(context) - thumbDrawable = R.drawable.ui_m3_switch_thumb.resolveDrawable(context) - thumbTintList = R.color.sel_m3_switch_thumb.resolveStateList(context) + trackDrawable = context.getDrawableSafe(R.drawable.ui_m3_switch_track) + trackTintList = context.getColorStateListSafe(R.color.sel_m3_switch_track) + thumbDrawable = context.getDrawableSafe(R.drawable.ui_m3_switch_thumb) + thumbTintList = context.getColorStateListSafe(R.color.sel_m3_switch_thumb) } needToUpdateSwitch = false diff --git a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt index ddbdcd723..722e23303 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt @@ -21,13 +21,25 @@ package org.oxycblt.auxio.util import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.res.ColorStateList import android.content.res.Configuration +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable import android.os.Build +import android.util.TypedValue import android.view.LayoutInflater import android.widget.Toast +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes +import androidx.annotation.DimenRes +import androidx.annotation.Dimension +import androidx.annotation.DrawableRes import androidx.annotation.PluralsRes +import androidx.annotation.Px import androidx.annotation.StringRes import androidx.core.content.ContextCompat +import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.MainActivity import kotlin.reflect.KClass import kotlin.system.exitProcess @@ -47,6 +59,150 @@ val Context.isNight: Boolean get() = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES +/** + * Returns if this device is in landscape. + */ +val Context.isLandscape get() = + resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + +/** + * Convenience method for getting a plural. + * @param pluralsRes Resource for the plural + * @param value Int value for the plural. + * @return The formatted string requested + */ +fun Context.getPluralSafe(@PluralsRes pluralsRes: Int, value: Int): String { + return try { + resources.getQuantityString(pluralsRes, value, value) + } catch (e: Exception) { + logE("plural load failed") + return "" + } +} + +/** + * Convenience method for getting a color safely. + * @param color The color resource + * @return The color integer requested, or black if an error occurred. + */ +@ColorInt +fun Context.getColorSafe(@ColorRes color: Int): Int { + return try { + ContextCompat.getColor(this, color) + } catch (e: Exception) { + handleResourceFailure(e, "color", getColorSafe(android.R.color.black)) + } +} + +/** + * Convenience method for getting a [ColorStateList] resource safely. + * @param color The color resource + * @return The [ColorStateList] requested, or black if an error occurred. + */ +fun Context.getColorStateListSafe(@ColorRes color: Int): ColorStateList { + return try { + requireNotNull(ContextCompat.getColorStateList(this, color)) + } catch (e: Exception) { + handleResourceFailure(e, "color state list", getColorSafe(android.R.color.black).stateList) + } +} + +/** + * Convenience method for getting a color attribute safely. + * @param attr The color attribute + * @return The attribute requested, or black if an error occurred. + */ +@ColorInt +fun Context.getAttrColorSafe(@AttrRes attr: Int): Int { + // First resolve the attribute into its ID + val resolvedAttr = TypedValue() + theme.resolveAttribute(attr, resolvedAttr, true) + + // Then convert it to a proper color + val color = if (resolvedAttr.resourceId != 0) { + resolvedAttr.resourceId + } else { + resolvedAttr.data + } + + return getColorSafe(color) +} + +/** + * Convenience method for getting a [Drawable] safely. + * @param drawable The drawable resource + * @return The drawable requested, or black if an error occurred. + */ +fun Context.getDrawableSafe(@DrawableRes drawable: Int): Drawable { + return try { + requireNotNull(ContextCompat.getDrawable(this, drawable)) + } catch (e: Exception) { + handleResourceFailure(e, "drawable", ColorDrawable(getColorSafe(android.R.color.black))) + } +} + +/** + * Convenience method for getting a dimension safely. + * @param dimen The dimension resource + * @return The dimension requested, or 0 if an error occurred. + */ +@Dimension +fun Context.getDimenSafe(@DimenRes dimen: Int): Float { + return try { + resources.getDimension(dimen) + } catch (e: Exception) { + handleResourceFailure(e, "dimen", 0f) + } +} + +/** + * Convenience method for getting a dimension pixel size safely. + * @param dimen The dimension resource + * @return The dimension requested, in pixels, or 0 if an error occurred. + */ +@Px +fun Context.getDimenSizeSafe(@DimenRes dimen: Int): Int { + return try { + resources.getDimensionPixelSize(dimen) + } catch (e: Exception) { + handleResourceFailure(e, "dimen", 0) + } +} + +/** + * Convenience method for getting a dimension pixel offset safely. + * @param dimen The dimension resource + * @return The dimension requested, in pixels, or 0 if an error occurred. + */ +@Px +fun Context.getDimenOffsetSafe(@DimenRes dimen: Int): Int { + return try { + resources.getDimensionPixelOffset(dimen) + } catch (e: Exception) { + handleResourceFailure(e, "dimen", 0) + } +} + +@Px +fun Context.pxOfDp(@Dimension dp: Float): Int { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics + ).toInt() +} + +private fun Context.handleResourceFailure(e: Exception, what: String, default: T): T { + logE("$what load failed.") + + if (BuildConfig.DEBUG) { + // I'd rather be aware of a sudden crash when debugging. + throw e + } else { + // Not so much when the app is in production. + logE(e.stackTraceToString()) + return default + } +} + /** * Convenience method for getting a system service without nullability issues. * @param T The system service in question. @@ -60,6 +216,25 @@ fun Context.getSystemServiceSafe(serviceClass: KClass): T { } } +/** + * Create a toast using the provided string resource. + */ +fun Context.showToast(@StringRes str: Int) { + Toast.makeText(applicationContext, getString(str), Toast.LENGTH_SHORT).show() +} + +/** + * Create a [PendingIntent] that leads to Auxio's [MainActivity] + */ +fun Context.newMainIntent(): PendingIntent { + return PendingIntent.getActivity( + this, INTENT_REQUEST_CODE, Intent(this, MainActivity::class.java), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + PendingIntent.FLAG_IMMUTABLE + else 0 + ) +} + /** * Create a broadcast [PendingIntent] */ @@ -73,17 +248,8 @@ fun Context.newBroadcastIntent(what: String): PendingIntent { } /** - * Create a [PendingIntent] that leads to Auxio's [MainActivity] + * Hard-restarts the app. Useful for forcing the app to reload music. */ -fun Context.newMainIntent(): PendingIntent { - return PendingIntent.getActivity( - this, INTENT_REQUEST_CODE, Intent(this, MainActivity::class.java), - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - PendingIntent.FLAG_IMMUTABLE - else 0 - ) -} - fun Context.hardRestart() { // Instead of having to do a ton of cleanup and horrible code changes // to restart this application non-destructively, I just restart the UI task [There is only @@ -96,27 +262,3 @@ fun Context.hardRestart() { exitProcess(0) } - -/** - * Create a toast using the provided string resource. - */ -fun Context.showToast(@StringRes str: Int) { - Toast.makeText(applicationContext, getString(str), Toast.LENGTH_SHORT).show() -} - -/** - * Convenience method for getting a plural. - * @param pluralsRes Resource for the plural - * @param value Int value for the plural. - * @return The formatted string requested - */ -fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String { - return resources.getQuantityString(pluralsRes, value, value) -} - -/** - * Determine if the device is currently in landscape. - */ -fun Context.isLandscape(): Boolean { - return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE -} diff --git a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt index 4641ee208..1c8a22455 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt @@ -18,23 +18,21 @@ package org.oxycblt.auxio.util -import android.content.Context import android.content.res.ColorStateList -import android.content.res.Resources import android.graphics.Insets import android.graphics.Rect import android.os.Build -import android.util.TypedValue import android.view.WindowInsets -import androidx.annotation.AttrRes -import androidx.annotation.ColorInt import androidx.annotation.ColorRes -import androidx.annotation.DrawableRes -import androidx.core.content.ContextCompat import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R +/** + * Converts this color to a single-color [ColorStateList]. + */ +val @receiver:ColorRes Int.stateList get() = ColorStateList.valueOf(this) + /** * Apply the recommended spans for a [RecyclerView]. * @@ -64,60 +62,6 @@ fun RecyclerView.applySpans(shouldBeFullWidth: ((Int) -> Boolean)? = null) { */ fun RecyclerView.canScroll(): Boolean = computeVerticalScrollRange() > height -/** - * Resolve a color. - * @param context [Context] required - * @return The resolved color, black if the resolving process failed. - */ -@ColorInt -fun @receiver:ColorRes Int.resolveColor(context: Context): Int { - return try { - ContextCompat.getColor(context, this) - } catch (e: Resources.NotFoundException) { - logE("Attempted color load failed: ${e.stackTraceToString()}") - - // Default to the emergency color [Black] if the loading fails. - ContextCompat.getColor(context, android.R.color.black) - } -} - -/** - * Resolve a color and turn it into a [ColorStateList] - * @param context [Context] required - * @return The resolved color as a [ColorStateList] - * @see resolveColor - */ -fun @receiver:ColorRes Int.resolveStateList(context: Context) = - ContextCompat.getColorStateList(context, this) - -/* - * Resolve a color and turn it into a [ColorStateList] - * @param context [Context] required - * @return The resolved color as a [ColorStateList] - * @see resolveColor - */ -fun @receiver:DrawableRes Int.resolveDrawable(context: Context) = - requireNotNull(ContextCompat.getDrawable(context, this)) - -/** - * Resolve this int into a color as if it was an attribute - */ -@ColorInt -fun @receiver:AttrRes Int.resolveAttr(context: Context): Int { - // First resolve the attribute into its ID - val resolvedAttr = TypedValue() - context.theme.resolveAttribute(this, resolvedAttr, true) - - // Then convert it to a proper color - val color = if (resolvedAttr.resourceId != 0) { - resolvedAttr.resourceId - } else { - resolvedAttr.data - } - - return color.resolveColor(context) -} - /** * Resolve window insets in a version-aware manner. This can be used to apply padding to * a view that properly follows all the frustrating changes that were made between 8-11. diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt index 43f72c41b..6014f7072 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt @@ -36,6 +36,7 @@ import coil.transform.RoundedCornersTransformation import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.state.PlaybackStateManager +import org.oxycblt.auxio.util.getDimenSizeSafe import org.oxycblt.auxio.util.isLandscape import org.oxycblt.auxio.util.logD import kotlin.math.min @@ -106,9 +107,8 @@ class WidgetProvider : AppWidgetProvider() { // we get a 1:1 aspect ratio image results in clipToOutline not working well. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val transform = RoundedCornersTransformation( - context.resources.getDimensionPixelSize( - android.R.dimen.system_app_widget_inner_radius - ).toFloat() + context.getDimenSizeSafe(android.R.dimen.system_app_widget_inner_radius) + .toFloat() ) coverRequest.transformations(transform) @@ -199,7 +199,7 @@ class WidgetProvider : AppWidgetProvider() { var height: Int // Landscape/Portrait modes use different dimen bounds - if (context.isLandscape()) { + if (context.isLandscape) { width = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) height = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) } else { diff --git a/app/src/main/res/values/styles_android.xml b/app/src/main/res/values/styles_android.xml index 598557730..3bd708c22 100644 --- a/app/src/main/res/values/styles_android.xml +++ b/app/src/main/res/values/styles_android.xml @@ -39,12 +39,15 @@ diff --git a/app/src/main/res/values/styles_ui.xml b/app/src/main/res/values/styles_ui.xml index 2959d66bb..c2cd41c8c 100644 --- a/app/src/main/res/values/styles_ui.xml +++ b/app/src/main/res/values/styles_ui.xml @@ -163,7 +163,7 @@ 0dp 0dp