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.
This commit is contained in:
parent
bd099aee7b
commit
4b919b121a
20 changed files with 261 additions and 168 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 "<plural error>"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <T> 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 <T : Any> Context.getSystemServiceSafe(serviceClass: KClass<T>): 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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -39,12 +39,15 @@
|
|||
|
||||
<!-- Widget TextView that mimics the main Auxio Primary TextView -->
|
||||
<style name="Widget.Auxio.TextView.Primary.AppWidget" parent="Widget.Auxio.TextView.AppWidget">
|
||||
<item name="android:textStyle">normal</item>
|
||||
<item name="android:fontFamily">sans-serif-medium</item>
|
||||
<item name="android:textAppearance">@style/TextAppearance.Auxio.TitleMidLarge</item>
|
||||
</style>
|
||||
|
||||
<!-- Widget TextView that mimics the main Auxio Secondary TextView -->
|
||||
<style name="Widget.Auxio.TextView.Secondary.AppWidget" parent="Widget.Auxio.TextView.AppWidget">
|
||||
<item name="android:textStyle">normal</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:textColor">?android:attr/textColorSecondary</item>
|
||||
<item name="android:textAppearance">@style/TextAppearance.Auxio.TitleMedium</item>
|
||||
</style>
|
||||
|
|
|
@ -163,7 +163,7 @@
|
|||
|
||||
<!--
|
||||
Abuse this floating action button to act more like an old-school auxio button.
|
||||
This is only done because the elevation show acts weird with the panel layout.
|
||||
This is only done because elevation acts weird with the panel layout.
|
||||
-->
|
||||
<item name="android:elevation">0dp</item>
|
||||
<item name="elevation">0dp</item>
|
||||
|
|
Loading…
Reference in a new issue