util: clean up context utils
Clean up the context utils to be more appropriately designed and efficient.
This commit is contained in:
parent
96be8cb6b7
commit
7d04aad9b7
48 changed files with 246 additions and 343 deletions
|
@ -55,6 +55,7 @@ class MainFragment :
|
||||||
private val navModel: NavigationViewModel by activityViewModels()
|
private val navModel: NavigationViewModel by activityViewModels()
|
||||||
private var callback: DynamicBackPressedCallback? = null
|
private var callback: DynamicBackPressedCallback? = null
|
||||||
private var lastInsets: WindowInsets? = null
|
private var lastInsets: WindowInsets? = null
|
||||||
|
private var elevationNormal = -1f
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -66,10 +67,13 @@ class MainFragment :
|
||||||
|
|
||||||
override fun onBindingCreated(binding: FragmentMainBinding, savedInstanceState: Bundle?) {
|
override fun onBindingCreated(binding: FragmentMainBinding, savedInstanceState: Bundle?) {
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
requireActivity()
|
val context = requireActivity()
|
||||||
.onBackPressedDispatcher.addCallback(
|
|
||||||
|
context.onBackPressedDispatcher.addCallback(
|
||||||
viewLifecycleOwner, DynamicBackPressedCallback().also { callback = it })
|
viewLifecycleOwner, DynamicBackPressedCallback().also { callback = it })
|
||||||
|
|
||||||
|
elevationNormal = requireContext().getDimen(R.dimen.elevation_normal)
|
||||||
|
|
||||||
binding.root.setOnApplyWindowInsetsListener { _, insets ->
|
binding.root.setOnApplyWindowInsetsListener { _, insets ->
|
||||||
lastInsets = insets
|
lastInsets = insets
|
||||||
insets
|
insets
|
||||||
|
@ -77,8 +81,9 @@ class MainFragment :
|
||||||
|
|
||||||
// Send meaningful accessibility events for bottom sheets
|
// Send meaningful accessibility events for bottom sheets
|
||||||
ViewCompat.setAccessibilityPaneTitle(
|
ViewCompat.setAccessibilityPaneTitle(
|
||||||
binding.playbackSheet, getString(R.string.lbl_playback))
|
binding.playbackSheet, context.getString(R.string.lbl_playback))
|
||||||
ViewCompat.setAccessibilityPaneTitle(binding.queueSheet, getString(R.string.lbl_queue))
|
ViewCompat.setAccessibilityPaneTitle(
|
||||||
|
binding.queueSheet, context.getString(R.string.lbl_queue))
|
||||||
|
|
||||||
val queueSheetBehavior = binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior?
|
val queueSheetBehavior = binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior?
|
||||||
if (queueSheetBehavior != null) {
|
if (queueSheetBehavior != null) {
|
||||||
|
@ -97,8 +102,8 @@ class MainFragment :
|
||||||
binding.queueSheet.apply {
|
binding.queueSheet.apply {
|
||||||
background =
|
background =
|
||||||
MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
||||||
fillColor = context.getAttrColorSafe(R.attr.colorSurface).stateList
|
fillColor = context.getAttrColorCompat(R.attr.colorSurface)
|
||||||
elevation = context.getDimenSafe(R.dimen.elevation_normal)
|
elevation = context.getDimen(R.dimen.elevation_normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnApplyWindowInsetsListener { v, insets ->
|
setOnApplyWindowInsetsListener { v, insets ->
|
||||||
|
@ -182,8 +187,7 @@ class MainFragment :
|
||||||
isInvisible = alpha == 0f
|
isInvisible = alpha == 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.playbackSheet.translationZ =
|
binding.playbackSheet.translationZ = elevationNormal * outPlaybackRatio
|
||||||
requireContext().getDimenSafe(R.dimen.elevation_normal) * outPlaybackRatio
|
|
||||||
playbackSheetBehavior.sheetBackgroundDrawable.alpha = (outPlaybackRatio * 255).toInt()
|
playbackSheetBehavior.sheetBackgroundDrawable.alpha = (outPlaybackRatio * 255).toInt()
|
||||||
|
|
||||||
binding.playbackBarFragment.apply {
|
binding.playbackBarFragment.apply {
|
||||||
|
|
|
@ -43,7 +43,6 @@ import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
||||||
import org.oxycblt.auxio.ui.recycler.Header
|
import org.oxycblt.auxio.ui.recycler.Header
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.util.applySpans
|
|
||||||
import org.oxycblt.auxio.util.canScroll
|
import org.oxycblt.auxio.util.canScroll
|
||||||
import org.oxycblt.auxio.util.collect
|
import org.oxycblt.auxio.util.collect
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
|
@ -88,9 +87,9 @@ class AlbumDetailFragment :
|
||||||
|
|
||||||
binding.detailRecycler.apply {
|
binding.detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
applySpans { pos ->
|
setSpanSizeLookup { pos ->
|
||||||
val item = detailAdapter.data.currentList[pos]
|
val item = detailAdapter.data.getItem(pos)
|
||||||
item is Header || item is SortHeader || item is Album
|
item is Album || item is Header || item is SortHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +239,7 @@ class AlbumDetailFragment :
|
||||||
// If the recyclerview can scroll, its certain that it will have to scroll to
|
// If the recyclerview can scroll, its certain that it will have to scroll to
|
||||||
// correctly center the playing item, so make sure that the Toolbar is lifted in
|
// correctly center the playing item, so make sure that the Toolbar is lifted in
|
||||||
// that case.
|
// that case.
|
||||||
binding.detailAppbar.isLifted = binding.detailRecycler.canScroll
|
binding.detailAppbar.isLifted = binding.detailRecycler.canScroll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,6 @@ import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
||||||
import org.oxycblt.auxio.ui.recycler.Header
|
import org.oxycblt.auxio.ui.recycler.Header
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.util.applySpans
|
|
||||||
import org.oxycblt.auxio.util.collect
|
import org.oxycblt.auxio.util.collect
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
|
@ -83,10 +82,9 @@ class ArtistDetailFragment :
|
||||||
|
|
||||||
binding.detailRecycler.apply {
|
binding.detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
applySpans { pos ->
|
setSpanSizeLookup { pos ->
|
||||||
// If the item is an ActionHeader we need to also make the item full-width
|
val item = detailAdapter.data.getItem(pos)
|
||||||
val item = detailAdapter.data.currentList[pos]
|
item is Artist || item is Header || item is SortHeader
|
||||||
item is Header || item is SortHeader || item is Artist
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,13 +29,10 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import java.lang.Exception
|
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.ui.AuxioAppBarLayout
|
import org.oxycblt.auxio.ui.AuxioAppBarLayout
|
||||||
import org.oxycblt.auxio.util.lazyReflectedField
|
import org.oxycblt.auxio.util.lazyReflectedField
|
||||||
import org.oxycblt.auxio.util.logE
|
|
||||||
import org.oxycblt.auxio.util.logTraceOrThrow
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An [AuxioAppBarLayout] variant that also shows the name of the toolbar whenever the detail
|
* An [AuxioAppBarLayout] variant that also shows the name of the toolbar whenever the detail
|
||||||
|
@ -68,14 +65,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
val toolbar = findViewById<Toolbar>(R.id.detail_toolbar)
|
val toolbar = findViewById<Toolbar>(R.id.detail_toolbar)
|
||||||
|
|
||||||
// Reflect to get the actual title view to do transformations on
|
// Reflect to get the actual title view to do transformations on
|
||||||
val newTitleView =
|
val newTitleView = TOOLBAR_TITLE_TEXT_FIELD.get(toolbar) as AppCompatTextView
|
||||||
try {
|
|
||||||
TOOLBAR_TITLE_TEXT_FIELD.get(toolbar) as AppCompatTextView
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logE("Could not get toolbar title view (likely an internal code change)")
|
|
||||||
e.logTraceOrThrow()
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
newTitleView.alpha = 0f
|
newTitleView.alpha = 0f
|
||||||
this.titleView = newTitleView
|
this.titleView = newTitleView
|
||||||
|
|
|
@ -42,7 +42,6 @@ import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
||||||
import org.oxycblt.auxio.ui.recycler.Header
|
import org.oxycblt.auxio.ui.recycler.Header
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.util.applySpans
|
|
||||||
import org.oxycblt.auxio.util.collect
|
import org.oxycblt.auxio.util.collect
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
|
@ -84,9 +83,9 @@ class GenreDetailFragment :
|
||||||
|
|
||||||
binding.detailRecycler.apply {
|
binding.detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
applySpans { pos ->
|
setSpanSizeLookup { pos ->
|
||||||
val item = detailAdapter.data.currentList[pos]
|
val item = detailAdapter.data.getItem(pos)
|
||||||
item is Header || item is SortHeader || item is Genre
|
item is Genre || item is Header || item is SortHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,14 +64,12 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
|
||||||
|
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
if (song.info != null) {
|
if (song.info != null) {
|
||||||
|
val context = requireContext()
|
||||||
binding.detailContainer.isGone = false
|
binding.detailContainer.isGone = false
|
||||||
binding.detailFileName.setText(song.song.path.name)
|
binding.detailFileName.setText(song.song.path.name)
|
||||||
binding.detailRelativeDir.setText(
|
binding.detailRelativeDir.setText(song.song.path.parent.resolveName(context))
|
||||||
song.song.path.parent.resolveName(requireContext()))
|
binding.detailFormat.setText(song.info.resolvedMimeType.resolveName(context))
|
||||||
binding.detailFormat.setText(
|
binding.detailSize.setText(Formatter.formatFileSize(context, song.song.size))
|
||||||
song.info.resolvedMimeType.resolveName(requireContext()))
|
|
||||||
binding.detailSize.setText(
|
|
||||||
Formatter.formatFileSize(requireContext(), song.song.size))
|
|
||||||
binding.detailDuration.setText(song.song.durationSecs.formatDuration(true))
|
binding.detailDuration.setText(song.song.durationSecs.formatDuration(true))
|
||||||
|
|
||||||
if (song.info.bitrateKbps != null) {
|
if (song.info.bitrateKbps != null) {
|
||||||
|
|
|
@ -34,7 +34,7 @@ import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.formatDuration
|
import org.oxycblt.auxio.util.formatDuration
|
||||||
import org.oxycblt.auxio.util.getPluralSafe
|
import org.oxycblt.auxio.util.getPlural
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,7 +127,7 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite
|
||||||
item.date?.let { context.getString(R.string.fmt_number, it.year) }
|
item.date?.let { context.getString(R.string.fmt_number, it.year) }
|
||||||
?: context.getString(R.string.def_date)
|
?: context.getString(R.string.def_date)
|
||||||
|
|
||||||
val songCount = context.getPluralSafe(R.plurals.fmt_song_count, item.songs.size)
|
val songCount = context.getPlural(R.plurals.fmt_song_count, item.songs.size)
|
||||||
|
|
||||||
val duration = item.durationSecs.formatDuration(true)
|
val duration = item.durationSecs.formatDuration(true)
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.getPluralSafe
|
import org.oxycblt.auxio.util.getPlural
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,8 +138,8 @@ private class ArtistDetailViewHolder private constructor(private val binding: It
|
||||||
binding.detailInfo.text =
|
binding.detailInfo.text =
|
||||||
binding.context.getString(
|
binding.context.getString(
|
||||||
R.string.fmt_two,
|
R.string.fmt_two,
|
||||||
binding.context.getPluralSafe(R.plurals.fmt_album_count, item.albums.size),
|
binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size),
|
||||||
binding.context.getPluralSafe(R.plurals.fmt_song_count, item.songs.size))
|
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size))
|
||||||
|
|
||||||
binding.detailPlayButton.setOnClickListener { listener.onPlayParent() }
|
binding.detailPlayButton.setOnClickListener { listener.onPlayParent() }
|
||||||
binding.detailShuffleButton.setOnClickListener { listener.onShuffleParent() }
|
binding.detailShuffleButton.setOnClickListener { listener.onShuffleParent() }
|
||||||
|
|
|
@ -32,7 +32,7 @@ import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.formatDuration
|
import org.oxycblt.auxio.util.formatDuration
|
||||||
import org.oxycblt.auxio.util.getPluralSafe
|
import org.oxycblt.auxio.util.getPlural
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,7 +106,7 @@ private class GenreDetailViewHolder private constructor(private val binding: Ite
|
||||||
binding.detailCover.bind(item)
|
binding.detailCover.bind(item)
|
||||||
binding.detailName.text = item.resolveName(binding.context)
|
binding.detailName.text = item.resolveName(binding.context)
|
||||||
binding.detailSubhead.text =
|
binding.detailSubhead.text =
|
||||||
binding.context.getPluralSafe(R.plurals.fmt_song_count, item.songs.size)
|
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)
|
||||||
binding.detailInfo.text = item.durationSecs.formatDuration(false)
|
binding.detailInfo.text = item.durationSecs.formatDuration(false)
|
||||||
binding.detailPlayButton.setOnClickListener { listener.onPlayParent() }
|
binding.detailPlayButton.setOnClickListener { listener.onPlayParent() }
|
||||||
binding.detailShuffleButton.setOnClickListener { listener.onShuffleParent() }
|
binding.detailShuffleButton.setOnClickListener { listener.onShuffleParent() }
|
||||||
|
|
|
@ -106,7 +106,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
// Load the track color in manually as it's unclear whether the track actually supports
|
// Load the track color in manually as it's unclear whether the track actually supports
|
||||||
// using a ColorStateList in the resources
|
// using a ColorStateList in the resources
|
||||||
binding.homeIndexingProgress.trackColor =
|
binding.homeIndexingProgress.trackColor =
|
||||||
requireContext().getColorStateListSafe(R.color.sel_track).defaultColor
|
requireContext().getColorCompat(R.color.sel_track).defaultColor
|
||||||
|
|
||||||
binding.homePager.apply {
|
binding.homePager.apply {
|
||||||
adapter = HomePagerAdapter()
|
adapter = HomePagerAdapter()
|
||||||
|
@ -279,6 +279,8 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
binding.homeFab.show()
|
binding.homeFab.show()
|
||||||
binding.homeIndexingContainer.visibility = View.INVISIBLE
|
binding.homeIndexingContainer.visibility = View.INVISIBLE
|
||||||
} else {
|
} else {
|
||||||
|
val context = requireContext()
|
||||||
|
|
||||||
binding.homeIndexingContainer.visibility = View.VISIBLE
|
binding.homeIndexingContainer.visibility = View.VISIBLE
|
||||||
|
|
||||||
logD("Received non-ok response $response")
|
logD("Received non-ok response $response")
|
||||||
|
@ -286,28 +288,28 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
when (response) {
|
when (response) {
|
||||||
is Indexer.Response.Err -> {
|
is Indexer.Response.Err -> {
|
||||||
binding.homeIndexingProgress.visibility = View.INVISIBLE
|
binding.homeIndexingProgress.visibility = View.INVISIBLE
|
||||||
binding.homeIndexingStatus.text = getString(R.string.err_index_failed)
|
binding.homeIndexingStatus.text = context.getString(R.string.err_index_failed)
|
||||||
binding.homeIndexingAction.apply {
|
binding.homeIndexingAction.apply {
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
text = getString(R.string.lbl_retry)
|
text = context.getString(R.string.lbl_retry)
|
||||||
setOnClickListener { musicModel.reindex() }
|
setOnClickListener { musicModel.reindex() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Indexer.Response.NoMusic -> {
|
is Indexer.Response.NoMusic -> {
|
||||||
binding.homeIndexingProgress.visibility = View.INVISIBLE
|
binding.homeIndexingProgress.visibility = View.INVISIBLE
|
||||||
binding.homeIndexingStatus.text = getString(R.string.err_no_music)
|
binding.homeIndexingStatus.text = context.getString(R.string.err_no_music)
|
||||||
binding.homeIndexingAction.apply {
|
binding.homeIndexingAction.apply {
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
text = getString(R.string.lbl_retry)
|
text = context.getString(R.string.lbl_retry)
|
||||||
setOnClickListener { musicModel.reindex() }
|
setOnClickListener { musicModel.reindex() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Indexer.Response.NoPerms -> {
|
is Indexer.Response.NoPerms -> {
|
||||||
binding.homeIndexingProgress.visibility = View.INVISIBLE
|
binding.homeIndexingProgress.visibility = View.INVISIBLE
|
||||||
binding.homeIndexingStatus.text = getString(R.string.err_no_perms)
|
binding.homeIndexingStatus.text = context.getString(R.string.err_no_perms)
|
||||||
binding.homeIndexingAction.apply {
|
binding.homeIndexingAction.apply {
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
text = getString(R.string.lbl_grant)
|
text = context.getString(R.string.lbl_grant)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
storagePermissionLauncher.launch(
|
storagePermissionLauncher.launch(
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE)
|
Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
@ -324,14 +326,16 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
binding.homeIndexingProgress.visibility = View.VISIBLE
|
binding.homeIndexingProgress.visibility = View.VISIBLE
|
||||||
binding.homeIndexingAction.visibility = View.INVISIBLE
|
binding.homeIndexingAction.visibility = View.INVISIBLE
|
||||||
|
|
||||||
|
val context = requireContext()
|
||||||
|
|
||||||
when (indexing) {
|
when (indexing) {
|
||||||
is Indexer.Indexing.Indeterminate -> {
|
is Indexer.Indexing.Indeterminate -> {
|
||||||
binding.homeIndexingStatus.text = getString(R.string.lng_indexing_desc)
|
binding.homeIndexingStatus.text = context.getString(R.string.lng_indexing_desc)
|
||||||
binding.homeIndexingProgress.isIndeterminate = true
|
binding.homeIndexingProgress.isIndeterminate = true
|
||||||
}
|
}
|
||||||
is Indexer.Indexing.Songs -> {
|
is Indexer.Indexing.Songs -> {
|
||||||
binding.homeIndexingStatus.text =
|
binding.homeIndexingStatus.text =
|
||||||
getString(R.string.fmt_indexing, indexing.current, indexing.total)
|
context.getString(R.string.fmt_indexing, indexing.current, indexing.total)
|
||||||
binding.homeIndexingProgress.apply {
|
binding.homeIndexingProgress.apply {
|
||||||
isIndeterminate = false
|
isIndeterminate = false
|
||||||
max = indexing.total
|
max = indexing.total
|
||||||
|
@ -370,14 +374,9 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
* https://al-e-shevelev.medium.com/how-to-reduce-scroll-sensitivity-of-viewpager2-widget-87797ad02414
|
* https://al-e-shevelev.medium.com/how-to-reduce-scroll-sensitivity-of-viewpager2-widget-87797ad02414
|
||||||
*/
|
*/
|
||||||
private fun ViewPager2.reduceSensitivity(by: Int) {
|
private fun ViewPager2.reduceSensitivity(by: Int) {
|
||||||
try {
|
|
||||||
val recycler = VIEW_PAGER_RECYCLER_FIELD.get(this@reduceSensitivity)
|
val recycler = VIEW_PAGER_RECYCLER_FIELD.get(this@reduceSensitivity)
|
||||||
val slop = VIEW_PAGER_TOUCH_SLOP_FIELD.get(recycler) as Int
|
val slop = VIEW_PAGER_TOUCH_SLOP_FIELD.get(recycler) as Int
|
||||||
VIEW_PAGER_TOUCH_SLOP_FIELD.set(recycler, slop * by)
|
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Forces the view to recreate all fragments contained within it. */
|
/** Forces the view to recreate all fragments contained within it. */
|
||||||
|
|
|
@ -34,9 +34,8 @@ import android.view.Gravity
|
||||||
import androidx.core.widget.TextViewCompat
|
import androidx.core.widget.TextViewCompat
|
||||||
import com.google.android.material.textview.MaterialTextView
|
import com.google.android.material.textview.MaterialTextView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.util.getAttrColorSafe
|
import org.oxycblt.auxio.util.getAttrColorCompat
|
||||||
import org.oxycblt.auxio.util.getDimenOffsetSafe
|
import org.oxycblt.auxio.util.getDimenSize
|
||||||
import org.oxycblt.auxio.util.getDimenSizeSafe
|
|
||||||
import org.oxycblt.auxio.util.isRtl
|
import org.oxycblt.auxio.util.isRtl
|
||||||
|
|
||||||
class FastScrollPopupView
|
class FastScrollPopupView
|
||||||
|
@ -44,17 +43,17 @@ class FastScrollPopupView
|
||||||
constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0) :
|
constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0) :
|
||||||
MaterialTextView(context, attrs, defStyleRes) {
|
MaterialTextView(context, attrs, defStyleRes) {
|
||||||
init {
|
init {
|
||||||
minimumWidth = context.getDimenSizeSafe(R.dimen.fast_scroll_popup_min_width)
|
minimumWidth = context.getDimenSize(R.dimen.fast_scroll_popup_min_width)
|
||||||
minimumHeight = context.getDimenSizeSafe(R.dimen.fast_scroll_popup_min_height)
|
minimumHeight = context.getDimenSize(R.dimen.fast_scroll_popup_min_height)
|
||||||
|
|
||||||
TextViewCompat.setTextAppearance(this, R.style.TextAppearance_Auxio_HeadlineLarge)
|
TextViewCompat.setTextAppearance(this, R.style.TextAppearance_Auxio_HeadlineLarge)
|
||||||
setTextColor(context.getAttrColorSafe(R.attr.colorOnSecondary))
|
setTextColor(context.getAttrColorCompat(R.attr.colorOnSecondary))
|
||||||
ellipsize = TextUtils.TruncateAt.MIDDLE
|
ellipsize = TextUtils.TruncateAt.MIDDLE
|
||||||
gravity = Gravity.CENTER
|
gravity = Gravity.CENTER
|
||||||
includeFontPadding = false
|
includeFontPadding = false
|
||||||
|
|
||||||
alpha = 0f
|
alpha = 0f
|
||||||
elevation = context.getDimenSizeSafe(R.dimen.elevation_normal).toFloat()
|
elevation = context.getDimenSize(R.dimen.elevation_normal).toFloat()
|
||||||
background = FastScrollPopupDrawable(context)
|
background = FastScrollPopupDrawable(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,16 +61,15 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0)
|
||||||
private val paint: Paint =
|
private val paint: Paint =
|
||||||
Paint().apply {
|
Paint().apply {
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
color = context.getAttrColorSafe(R.attr.colorSecondary)
|
color = context.getAttrColorCompat(R.attr.colorSecondary).defaultColor
|
||||||
style = Paint.Style.FILL
|
style = Paint.Style.FILL
|
||||||
}
|
}
|
||||||
|
|
||||||
private val path = Path()
|
private val path = Path()
|
||||||
private val matrix = Matrix()
|
private val matrix = Matrix()
|
||||||
|
|
||||||
private val paddingStart =
|
private val paddingStart = context.getDimenSize(R.dimen.fast_scroll_popup_padding_start)
|
||||||
context.getDimenOffsetSafe(R.dimen.fast_scroll_popup_padding_start)
|
private val paddingEnd = context.getDimenSize(R.dimen.fast_scroll_popup_padding_end)
|
||||||
private val paddingEnd = context.getDimenOffsetSafe(R.dimen.fast_scroll_popup_padding_end)
|
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
canvas.drawPath(path, paint)
|
canvas.drawPath(path, paint)
|
||||||
|
|
|
@ -34,11 +34,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.ui.recycler.EdgeRecyclerView
|
import org.oxycblt.auxio.ui.recycler.AuxioRecyclerView
|
||||||
import org.oxycblt.auxio.util.canScroll
|
import org.oxycblt.auxio.util.getDimenSize
|
||||||
import org.oxycblt.auxio.util.getDimenOffsetSafe
|
import org.oxycblt.auxio.util.getDrawableCompat
|
||||||
import org.oxycblt.auxio.util.getDimenSizeSafe
|
|
||||||
import org.oxycblt.auxio.util.getDrawableSafe
|
|
||||||
import org.oxycblt.auxio.util.isRtl
|
import org.oxycblt.auxio.util.isRtl
|
||||||
import org.oxycblt.auxio.util.isUnder
|
import org.oxycblt.auxio.util.isUnder
|
||||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
|
@ -71,12 +69,12 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
class FastScrollRecyclerView
|
class FastScrollRecyclerView
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||||
EdgeRecyclerView(context, attrs, defStyleAttr) {
|
AuxioRecyclerView(context, attrs, defStyleAttr) {
|
||||||
// Thumb
|
// Thumb
|
||||||
private val thumbView =
|
private val thumbView =
|
||||||
View(context).apply {
|
View(context).apply {
|
||||||
alpha = 0f
|
alpha = 0f
|
||||||
background = context.getDrawableSafe(R.drawable.ui_scroll_thumb)
|
background = context.getDrawableCompat(R.drawable.ui_scroll_thumb)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val thumbWidth = thumbView.background.intrinsicWidth
|
private val thumbWidth = thumbView.background.intrinsicWidth
|
||||||
|
@ -99,7 +97,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
.apply {
|
.apply {
|
||||||
gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
|
gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
|
||||||
marginEnd = context.getDimenOffsetSafe(R.dimen.spacing_small)
|
marginEnd = context.getDimenSize(R.dimen.spacing_small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +105,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
|
|
||||||
// Touch
|
// Touch
|
||||||
private val minTouchTargetSize =
|
private val minTouchTargetSize =
|
||||||
context.getDimenSizeSafe(R.dimen.fast_scroll_thumb_touch_target_size)
|
context.getDimenSize(R.dimen.fast_scroll_thumb_touch_target_size)
|
||||||
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||||
|
|
||||||
private var downX = 0f
|
private var downX = 0f
|
||||||
|
@ -289,7 +287,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateScrollbarState() {
|
private fun updateScrollbarState() {
|
||||||
if (!canScroll || childCount == 0) {
|
if (computeVerticalScrollRange() <= height || childCount == 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ import org.oxycblt.auxio.ui.fragment.MenuFragment
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||||
import org.oxycblt.auxio.util.applySpans
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Base [Fragment] implementing the base features shared across all list fragments in the home UI.
|
* A Base [Fragment] implementing the base features shared across all list fragments in the home UI.
|
||||||
|
@ -46,7 +45,6 @@ abstract class HomeListFragment<T : Item> :
|
||||||
override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) {
|
override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) {
|
||||||
binding.homeRecycler.popupProvider = this
|
binding.homeRecycler.popupProvider = this
|
||||||
binding.homeRecycler.listener = this
|
binding.homeRecycler.listener = this
|
||||||
binding.homeRecycler.applySpans()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyBinding(binding: FragmentHomeListBinding) {
|
override fun onDestroyBinding(binding: FragmentHomeListBinding) {
|
||||||
|
|
|
@ -29,8 +29,8 @@ import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.util.getColorStateListSafe
|
import org.oxycblt.auxio.util.getColorCompat
|
||||||
import org.oxycblt.auxio.util.getDrawableSafe
|
import org.oxycblt.auxio.util.getDrawableCompat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Effectively a super-charged [StyledImageView].
|
* Effectively a super-charged [StyledImageView].
|
||||||
|
@ -66,7 +66,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
indicator =
|
indicator =
|
||||||
StyledImageView(context).apply {
|
StyledImageView(context).apply {
|
||||||
cornerRadius = this@ImageGroup.cornerRadius
|
cornerRadius = this@ImageGroup.cornerRadius
|
||||||
staticIcon = context.getDrawableSafe(R.drawable.ic_currently_playing_24)
|
staticIcon = context.getDrawableCompat(R.drawable.ic_currently_playing_24)
|
||||||
}
|
}
|
||||||
|
|
||||||
addView(inner)
|
addView(inner)
|
||||||
|
@ -83,7 +83,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
getChildAt(1)?.apply {
|
getChildAt(1)?.apply {
|
||||||
background =
|
background =
|
||||||
MaterialShapeDrawable().apply {
|
MaterialShapeDrawable().apply {
|
||||||
fillColor = context.getColorStateListSafe(R.color.sel_cover_bg)
|
fillColor = context.getColorCompat(R.color.sel_cover_bg)
|
||||||
setCornerSize(cornerRadius)
|
setCornerSize(cornerRadius)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,8 +39,8 @@ import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Music
|
import org.oxycblt.auxio.music.Music
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.util.getColorStateListSafe
|
import org.oxycblt.auxio.util.getColorCompat
|
||||||
import org.oxycblt.auxio.util.getDrawableSafe
|
import org.oxycblt.auxio.util.getDrawableCompat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An [AppCompatImageView] that applies many of the stylistic choices that Auxio uses regarding
|
* An [AppCompatImageView] that applies many of the stylistic choices that Auxio uses regarding
|
||||||
|
@ -87,7 +87,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
clipToOutline = true
|
clipToOutline = true
|
||||||
background =
|
background =
|
||||||
MaterialShapeDrawable().apply {
|
MaterialShapeDrawable().apply {
|
||||||
fillColor = context.getColorStateListSafe(R.color.sel_cover_bg)
|
fillColor = context.getColorCompat(R.color.sel_cover_bg)
|
||||||
setCornerSize(cornerRadius)
|
setCornerSize(cornerRadius)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
styledAttrs.getResourceId(
|
styledAttrs.getResourceId(
|
||||||
R.styleable.StyledImageView_staticIcon, ResourcesCompat.ID_NULL)
|
R.styleable.StyledImageView_staticIcon, ResourcesCompat.ID_NULL)
|
||||||
if (staticIcon != ResourcesCompat.ID_NULL) {
|
if (staticIcon != ResourcesCompat.ID_NULL) {
|
||||||
this.staticIcon = context.getDrawableSafe(staticIcon)
|
this.staticIcon = context.getDrawableCompat(staticIcon)
|
||||||
}
|
}
|
||||||
|
|
||||||
useLargeIcon = styledAttrs.getBoolean(R.styleable.StyledImageView_useLargeIcon, false)
|
useLargeIcon = styledAttrs.getBoolean(R.styleable.StyledImageView_useLargeIcon, false)
|
||||||
|
@ -126,14 +126,14 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
|
|
||||||
dispose()
|
dispose()
|
||||||
load(music) {
|
load(music) {
|
||||||
error(StyledDrawable(context, context.getDrawableSafe(error)))
|
error(StyledDrawable(context, context.getDrawableCompat(error)))
|
||||||
transformations(SquareFrameTransform.INSTANCE)
|
transformations(SquareFrameTransform.INSTANCE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StyledDrawable(context: Context, private val src: Drawable) : Drawable() {
|
private class StyledDrawable(context: Context, private val src: Drawable) : Drawable() {
|
||||||
init {
|
init {
|
||||||
DrawableCompat.setTintList(src, context.getColorStateListSafe(R.color.sel_on_cover_bg))
|
DrawableCompat.setTintList(src, context.getColorCompat(R.color.sel_on_cover_bg))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
|
|
|
@ -32,7 +32,7 @@ import org.oxycblt.auxio.music.Directory
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
|
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.showToast
|
import org.oxycblt.auxio.util.showToast
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ class MusicDirsDialog :
|
||||||
private val dirAdapter = MusicDirAdapter(this)
|
private val dirAdapter = MusicDirAdapter(this)
|
||||||
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
||||||
private val storageManager: StorageManager by lifecycleObject { binding ->
|
private val storageManager: StorageManager by lifecycleObject { binding ->
|
||||||
binding.context.getSystemServiceSafe(StorageManager::class)
|
binding.context.getSystemServiceCompat(StorageManager::class)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||||
|
|
|
@ -34,7 +34,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.ui.system.ForegroundManager
|
import org.oxycblt.auxio.ui.system.ForegroundManager
|
||||||
import org.oxycblt.auxio.util.contentResolverSafe
|
import org.oxycblt.auxio.util.contentResolverSafe
|
||||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,7 +73,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
|
||||||
observingNotification = ObservingNotification(this)
|
observingNotification = ObservingNotification(this)
|
||||||
|
|
||||||
wakeLock =
|
wakeLock =
|
||||||
getSystemServiceSafe(PowerManager::class)
|
getSystemServiceCompat(PowerManager::class)
|
||||||
.newWakeLock(
|
.newWakeLock(
|
||||||
PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":IndexerService")
|
PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":IndexerService")
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ import java.io.File
|
||||||
import org.oxycblt.auxio.music.*
|
import org.oxycblt.auxio.music.*
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.util.contentResolverSafe
|
import org.oxycblt.auxio.util.contentResolverSafe
|
||||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -114,7 +114,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
|
||||||
|
|
||||||
override fun query(context: Context): Cursor {
|
override fun query(context: Context): Cursor {
|
||||||
val settings = Settings(context)
|
val settings = Settings(context)
|
||||||
val storageManager = context.getSystemServiceSafe(StorageManager::class)
|
val storageManager = context.getSystemServiceCompat(StorageManager::class)
|
||||||
volumes.addAll(storageManager.storageVolumesCompat)
|
volumes.addAll(storageManager.storageVolumesCompat)
|
||||||
val dirs = settings.getMusicDirs(storageManager)
|
val dirs = settings.getMusicDirs(storageManager)
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ import org.oxycblt.auxio.ui.NavigationViewModel
|
||||||
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
|
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
|
||||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.getColorStateListSafe
|
import org.oxycblt.auxio.util.getColorCompat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment showing the current playback state in a compact manner. Used as the bar for the
|
* A fragment showing the current playback state in a compact manner. Used as the bar for the
|
||||||
|
@ -58,7 +58,7 @@ class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
|
||||||
// Load the track color in manually as it's unclear whether the track actually supports
|
// Load the track color in manually as it's unclear whether the track actually supports
|
||||||
// using a ColorStateList in the resources
|
// using a ColorStateList in the resources
|
||||||
binding.playbackProgressBar.trackColor =
|
binding.playbackProgressBar.trackColor =
|
||||||
requireContext().getColorStateListSafe(R.color.sel_track).defaultColor
|
requireContext().getColorCompat(R.color.sel_track).defaultColor
|
||||||
|
|
||||||
binding.playbackPlayPause.setOnClickListener { playbackModel.invertPlaying() }
|
binding.playbackPlayPause.setOnClickListener { playbackModel.invertPlaying() }
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ import org.oxycblt.auxio.ui.NavigationViewModel
|
||||||
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
|
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
|
||||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.getDrawableSafe
|
import org.oxycblt.auxio.util.getDrawableCompat
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
|
|
||||||
|
@ -148,8 +148,11 @@ class PlaybackPanelFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateParent(parent: MusicParent?) {
|
private fun updateParent(parent: MusicParent?) {
|
||||||
requireBinding().playbackToolbar.subtitle =
|
val binding = requireBinding()
|
||||||
parent?.resolveName(requireContext()) ?: getString(R.string.lbl_all_songs)
|
val context = requireContext()
|
||||||
|
|
||||||
|
binding.playbackToolbar.subtitle =
|
||||||
|
parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePosition(positionSecs: Long) {
|
private fun updatePosition(positionSecs: Long) {
|
||||||
|
@ -157,8 +160,6 @@ class PlaybackPanelFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateRepeat(repeatMode: RepeatMode) {
|
private fun updateRepeat(repeatMode: RepeatMode) {
|
||||||
requireBinding().playbackRepeat.apply {
|
|
||||||
isActivated = repeatMode != RepeatMode.NONE
|
|
||||||
val iconRes =
|
val iconRes =
|
||||||
when (repeatMode) {
|
when (repeatMode) {
|
||||||
RepeatMode.NONE -> R.drawable.ic_repeat_off_24
|
RepeatMode.NONE -> R.drawable.ic_repeat_off_24
|
||||||
|
@ -166,7 +167,9 @@ class PlaybackPanelFragment :
|
||||||
RepeatMode.TRACK -> R.drawable.ic_repeat_one_24
|
RepeatMode.TRACK -> R.drawable.ic_repeat_one_24
|
||||||
}
|
}
|
||||||
|
|
||||||
icon = requireContext().getDrawableSafe(iconRes)
|
requireBinding().playbackRepeat.apply {
|
||||||
|
icon = requireContext().getDrawableCompat(iconRes)
|
||||||
|
isActivated = repeatMode != RepeatMode.NONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,8 +87,8 @@ private constructor(
|
||||||
|
|
||||||
val backgroundDrawable =
|
val backgroundDrawable =
|
||||||
MaterialShapeDrawable.createWithElevationOverlay(binding.root.context).apply {
|
MaterialShapeDrawable.createWithElevationOverlay(binding.root.context).apply {
|
||||||
fillColor = binding.context.getAttrColorSafe(R.attr.colorSurface).stateList
|
fillColor = binding.context.getAttrColorCompat(R.attr.colorSurface)
|
||||||
elevation = binding.context.getDimenSafe(R.dimen.elevation_normal) * 5
|
elevation = binding.context.getDimen(R.dimen.elevation_normal) * 5
|
||||||
alpha = 0
|
alpha = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,8 +114,8 @@ private constructor(
|
||||||
LayerDrawable(
|
LayerDrawable(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
MaterialShapeDrawable.createWithElevationOverlay(binding.context).apply {
|
MaterialShapeDrawable.createWithElevationOverlay(binding.context).apply {
|
||||||
fillColor = binding.context.getAttrColorSafe(R.attr.colorSurface).stateList
|
fillColor = binding.context.getAttrColorCompat(R.attr.colorSurface)
|
||||||
elevation = binding.context.getDimenSafe(R.dimen.elevation_normal)
|
elevation = binding.context.getDimen(R.dimen.elevation_normal)
|
||||||
},
|
},
|
||||||
backgroundDrawable))
|
backgroundDrawable))
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import androidx.core.view.isInvisible
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.util.getDimenSafe
|
import org.oxycblt.auxio.util.getDimen
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,7 +68,7 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
||||||
logD("Lifting queue item")
|
logD("Lifting queue item")
|
||||||
|
|
||||||
val bg = holder.backgroundDrawable
|
val bg = holder.backgroundDrawable
|
||||||
val elevation = recyclerView.context.getDimenSafe(R.dimen.elevation_normal)
|
val elevation = recyclerView.context.getDimen(R.dimen.elevation_normal)
|
||||||
holder.itemView
|
holder.itemView
|
||||||
.animate()
|
.animate()
|
||||||
.translationZ(elevation)
|
.translationZ(elevation)
|
||||||
|
@ -108,7 +108,7 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
||||||
logD("Dropping queue item")
|
logD("Dropping queue item")
|
||||||
|
|
||||||
val bg = holder.backgroundDrawable
|
val bg = holder.backgroundDrawable
|
||||||
val elevation = recyclerView.context.getDimenSafe(R.dimen.elevation_normal)
|
val elevation = recyclerView.context.getDimen(R.dimen.elevation_normal)
|
||||||
holder.itemView
|
holder.itemView
|
||||||
.animate()
|
.animate()
|
||||||
.translationZ(0f)
|
.translationZ(0f)
|
||||||
|
|
|
@ -33,11 +33,11 @@ import org.oxycblt.auxio.util.*
|
||||||
class QueueSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) :
|
class QueueSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) :
|
||||||
AuxioSheetBehavior<V>(context, attributeSet) {
|
AuxioSheetBehavior<V>(context, attributeSet) {
|
||||||
private var barHeight = 0
|
private var barHeight = 0
|
||||||
private var barSpacing = context.getDimenSizeSafe(R.dimen.spacing_small)
|
private var barSpacing = context.getDimenSize(R.dimen.spacing_small)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
isHideable = false
|
isHideable = false
|
||||||
sheetBackgroundDrawable.setCornerSize(context.getDimenSafe(R.dimen.size_corners_medium))
|
sheetBackgroundDrawable.setCornerSize(context.getDimen(R.dimen.size_corners_medium))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View) =
|
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View) =
|
||||||
|
|
|
@ -44,11 +44,10 @@ import org.oxycblt.auxio.ui.recycler.Header
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
import org.oxycblt.auxio.util.androidViewModels
|
import org.oxycblt.auxio.util.androidViewModels
|
||||||
import org.oxycblt.auxio.util.applySpans
|
|
||||||
import org.oxycblt.auxio.util.collect
|
import org.oxycblt.auxio.util.collect
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,7 +63,7 @@ class SearchFragment :
|
||||||
private val searchAdapter = SearchAdapter(this)
|
private val searchAdapter = SearchAdapter(this)
|
||||||
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
||||||
private val imm: InputMethodManager by lifecycleObject { binding ->
|
private val imm: InputMethodManager by lifecycleObject { binding ->
|
||||||
binding.context.getSystemServiceSafe(InputMethodManager::class)
|
binding.context.getSystemServiceCompat(InputMethodManager::class)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var launchedKeyboard = false
|
private var launchedKeyboard = false
|
||||||
|
@ -108,7 +107,7 @@ class SearchFragment :
|
||||||
|
|
||||||
binding.searchRecycler.apply {
|
binding.searchRecycler.apply {
|
||||||
adapter = searchAdapter
|
adapter = searchAdapter
|
||||||
applySpans { pos -> searchAdapter.data.currentList[pos] is Header }
|
setSpanSizeLookup { pos -> searchAdapter.data.getItem(pos) is Header }
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
|
@ -104,6 +104,8 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
||||||
private fun openLinkInBrowser(link: String) {
|
private fun openLinkInBrowser(link: String) {
|
||||||
logD("Opening $link")
|
logD("Opening $link")
|
||||||
|
|
||||||
|
val context = requireContext()
|
||||||
|
|
||||||
val browserIntent =
|
val browserIntent =
|
||||||
Intent(Intent.ACTION_VIEW, link.toUri()).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
Intent(Intent.ACTION_VIEW, link.toUri()).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
|
||||||
|
@ -112,10 +114,10 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
||||||
// [along with adding a new permission that breaks the old manual code], so
|
// [along with adding a new permission that breaks the old manual code], so
|
||||||
// we just do a typical activity launch.
|
// we just do a typical activity launch.
|
||||||
try {
|
try {
|
||||||
requireContext().startActivity(browserIntent)
|
context.startActivity(browserIntent)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
// No app installed to open the link
|
// No app installed to open the link
|
||||||
requireContext().showToast(R.string.err_no_app)
|
context.showToast(R.string.err_no_app)
|
||||||
}
|
}
|
||||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
// On older versions of android, opening links from an ACTION_VIEW intent might
|
// On older versions of android, opening links from an ACTION_VIEW intent might
|
||||||
|
@ -123,8 +125,7 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
||||||
// case, we will try to manually handle these cases before we try to launch the
|
// case, we will try to manually handle these cases before we try to launch the
|
||||||
// browser.
|
// browser.
|
||||||
val pkgName =
|
val pkgName =
|
||||||
requireContext()
|
context.packageManager
|
||||||
.packageManager
|
|
||||||
.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
|
.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||||
?.run { activityInfo.packageName }
|
?.run { activityInfo.packageName }
|
||||||
|
|
||||||
|
@ -144,7 +145,7 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No app installed to open the link
|
// No app installed to open the link
|
||||||
requireContext().showToast(R.string.err_no_app)
|
context.showToast(R.string.err_no_app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,41 +95,49 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
dialog.setTargetFragment(this, 0)
|
dialog.setTargetFragment(this, 0)
|
||||||
dialog.show(parentFragmentManager, IntListPreferenceDialog.TAG)
|
dialog.show(parentFragmentManager, IntListPreferenceDialog.TAG)
|
||||||
}
|
}
|
||||||
is WrappedDialogPreference ->
|
is WrappedDialogPreference -> {
|
||||||
|
val context = requireContext()
|
||||||
when (preference.key) {
|
when (preference.key) {
|
||||||
getString(R.string.set_key_accent) ->
|
context.getString(R.string.set_key_accent) ->
|
||||||
AccentCustomizeDialog()
|
AccentCustomizeDialog()
|
||||||
.show(childFragmentManager, AccentCustomizeDialog.TAG)
|
.show(childFragmentManager, AccentCustomizeDialog.TAG)
|
||||||
getString(R.string.set_key_lib_tabs) ->
|
context.getString(R.string.set_key_lib_tabs) ->
|
||||||
TabCustomizeDialog().show(childFragmentManager, TabCustomizeDialog.TAG)
|
TabCustomizeDialog().show(childFragmentManager, TabCustomizeDialog.TAG)
|
||||||
getString(R.string.set_key_pre_amp) ->
|
context.getString(R.string.set_key_pre_amp) ->
|
||||||
PreAmpCustomizeDialog()
|
PreAmpCustomizeDialog()
|
||||||
.show(childFragmentManager, PreAmpCustomizeDialog.TAG)
|
.show(childFragmentManager, PreAmpCustomizeDialog.TAG)
|
||||||
getString(R.string.set_key_music_dirs) ->
|
context.getString(R.string.set_key_music_dirs) ->
|
||||||
MusicDirsDialog().show(childFragmentManager, MusicDirsDialog.TAG)
|
MusicDirsDialog().show(childFragmentManager, MusicDirsDialog.TAG)
|
||||||
else -> logEOrThrow("Unexpected dialog key ${preference.key}")
|
else -> logEOrThrow("Unexpected dialog key ${preference.key}")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else -> super.onDisplayPreferenceDialog(preference)
|
else -> super.onDisplayPreferenceDialog(preference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||||
|
val context = requireContext()
|
||||||
|
|
||||||
when (preference.key) {
|
when (preference.key) {
|
||||||
getString(R.string.set_key_save_state) -> {
|
context.getString(R.string.set_key_save_state) -> {
|
||||||
playbackModel.savePlaybackState { context?.showToast(R.string.lng_state_saved) }
|
playbackModel.savePlaybackState {
|
||||||
|
this.context?.showToast(R.string.lng_state_saved)
|
||||||
}
|
}
|
||||||
getString(R.string.set_key_wipe_state) -> {
|
|
||||||
playbackModel.wipePlaybackState { context?.showToast(R.string.lng_state_wiped) }
|
|
||||||
}
|
}
|
||||||
getString(R.string.set_key_restore_state) ->
|
context.getString(R.string.set_key_wipe_state) -> {
|
||||||
|
playbackModel.wipePlaybackState {
|
||||||
|
this.context?.showToast(R.string.lng_state_wiped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.getString(R.string.set_key_restore_state) ->
|
||||||
playbackModel.tryRestorePlaybackState { restored ->
|
playbackModel.tryRestorePlaybackState { restored ->
|
||||||
if (restored) {
|
if (restored) {
|
||||||
context?.showToast(R.string.lng_state_restored)
|
this.context?.showToast(R.string.lng_state_restored)
|
||||||
} else {
|
} else {
|
||||||
context?.showToast(R.string.err_did_not_restore)
|
this.context?.showToast(R.string.err_did_not_restore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getString(R.string.set_key_reindex) -> {
|
context.getString(R.string.set_key_reindex) -> {
|
||||||
musicModel.reindex()
|
musicModel.reindex()
|
||||||
}
|
}
|
||||||
else -> return super.onPreferenceTreeClick(preference)
|
else -> return super.onPreferenceTreeClick(preference)
|
||||||
|
@ -139,7 +147,8 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPreference(preference: Preference) {
|
private fun setupPreference(preference: Preference) {
|
||||||
val settings = Settings(requireContext())
|
val context = requireActivity()
|
||||||
|
val settings = Settings(context)
|
||||||
|
|
||||||
if (!preference.isVisible) return
|
if (!preference.isVisible) return
|
||||||
|
|
||||||
|
@ -151,31 +160,31 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
preference.apply {
|
preference.apply {
|
||||||
when (key) {
|
when (key) {
|
||||||
getString(R.string.set_key_theme) -> {
|
context.getString(R.string.set_key_theme) -> {
|
||||||
onPreferenceChangeListener =
|
onPreferenceChangeListener =
|
||||||
Preference.OnPreferenceChangeListener { _, value ->
|
Preference.OnPreferenceChangeListener { _, value ->
|
||||||
AppCompatDelegate.setDefaultNightMode(value as Int)
|
AppCompatDelegate.setDefaultNightMode(value as Int)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getString(R.string.set_key_accent) -> {
|
context.getString(R.string.set_key_accent) -> {
|
||||||
summary = context.getString(settings.accent.name)
|
summary = context.getString(settings.accent.name)
|
||||||
}
|
}
|
||||||
getString(R.string.set_key_black_theme) -> {
|
context.getString(R.string.set_key_black_theme) -> {
|
||||||
onPreferenceChangeListener =
|
onPreferenceChangeListener =
|
||||||
Preference.OnPreferenceChangeListener { _, _ ->
|
Preference.OnPreferenceChangeListener { _, _ ->
|
||||||
if (requireContext().isNight) {
|
if (context.isNight) {
|
||||||
requireActivity().recreate()
|
context.recreate()
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getString(R.string.set_key_show_covers),
|
context.getString(R.string.set_key_show_covers),
|
||||||
getString(R.string.set_key_quality_covers) -> {
|
context.getString(R.string.set_key_quality_covers) -> {
|
||||||
onPreferenceChangeListener =
|
onPreferenceChangeListener =
|
||||||
Preference.OnPreferenceChangeListener { _, _ ->
|
Preference.OnPreferenceChangeListener { _, _ ->
|
||||||
Coil.imageLoader(requireContext()).apply { this.memoryCache?.clear() }
|
Coil.imageLoader(context).apply { this.memoryCache?.clear() }
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.settings.ui
|
package org.oxycblt.auxio.settings.ui
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.preference.PreferenceDialogFragmentCompat
|
import androidx.preference.PreferenceDialogFragmentCompat
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
@ -29,22 +28,20 @@ class IntListPreferenceDialog : PreferenceDialogFragmentCompat() {
|
||||||
get() = (preference as IntListPreference)
|
get() = (preference as IntListPreference)
|
||||||
private var pendingValueIndex = -1
|
private var pendingValueIndex = -1
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?) =
|
||||||
// PreferenceDialogFragmentCompat does not allow us to customize the actual creation
|
// PreferenceDialogFragmentCompat does not allow us to customize the actual creation
|
||||||
// of the alert dialog, so we have to manually override onCreateDialog and customize it
|
// of the alert dialog, so we have to manually override onCreateDialog and customize it
|
||||||
// ourselves.
|
// ourselves.
|
||||||
val builder = MaterialAlertDialogBuilder(requireContext(), theme)
|
MaterialAlertDialogBuilder(requireContext(), theme)
|
||||||
builder.setTitle(listPreference.title)
|
.setTitle(listPreference.title)
|
||||||
builder.setPositiveButton(null, null)
|
.setPositiveButton(null, null)
|
||||||
builder.setNegativeButton(R.string.lbl_cancel, null)
|
.setNegativeButton(R.string.lbl_cancel, null)
|
||||||
builder.setSingleChoiceItems(listPreference.entries, listPreference.getValueIndex()) {
|
.setSingleChoiceItems(listPreference.entries, listPreference.getValueIndex()) { _, index
|
||||||
_,
|
->
|
||||||
index ->
|
|
||||||
pendingValueIndex = index
|
pendingValueIndex = index
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
return builder.create()
|
.create()
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDialogClosed(positiveResult: Boolean) {
|
override fun onDialogClosed(positiveResult: Boolean) {
|
||||||
if (pendingValueIndex > -1) {
|
if (pendingValueIndex > -1) {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import androidx.annotation.AttrRes
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import org.oxycblt.auxio.util.coordinatorLayoutBehavior
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An [AppBarLayout] that fixes a bug with the default implementation where the lifted state will
|
* An [AppBarLayout] that fixes a bug with the default implementation where the lifted state will
|
||||||
|
@ -47,8 +48,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
|
|
||||||
if (child != null) {
|
if (child != null) {
|
||||||
val coordinator = parent as CoordinatorLayout
|
val coordinator = parent as CoordinatorLayout
|
||||||
(layoutParams as CoordinatorLayout.LayoutParams)
|
coordinatorLayoutBehavior?.onNestedPreScroll(
|
||||||
.behavior?.onNestedPreScroll(coordinator, this, coordinator, 0, 0, tConsumed, 0)
|
coordinator, this, coordinator, 0, 0, tConsumed, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
|
@ -40,8 +40,8 @@ abstract class AuxioSheetBehavior<V : View>(context: Context, attributeSet: Attr
|
||||||
private var setup = false
|
private var setup = false
|
||||||
val sheetBackgroundDrawable =
|
val sheetBackgroundDrawable =
|
||||||
MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
||||||
fillColor = context.getAttrColorSafe(R.attr.colorSurface).stateList
|
fillColor = context.getAttrColorCompat(R.attr.colorSurface)
|
||||||
elevation = context.getDimenSafe(R.dimen.elevation_normal)
|
elevation = context.getDimen(R.dimen.elevation_normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -75,7 +75,8 @@ abstract class AuxioSheetBehavior<V : View>(context: Context, attributeSet: Attr
|
||||||
background =
|
background =
|
||||||
LayerDrawable(
|
LayerDrawable(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
ColorDrawable(context.getAttrColorSafe(R.attr.colorSurface)),
|
ColorDrawable(
|
||||||
|
context.getAttrColorCompat(R.attr.colorSurface).defaultColor),
|
||||||
sheetBackgroundDrawable))
|
sheetBackgroundDrawable))
|
||||||
|
|
||||||
// Try to disable drop shadows if possible.
|
// Try to disable drop shadows if possible.
|
||||||
|
|
|
@ -37,8 +37,8 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
|
||||||
CoordinatorLayout.Behavior<V>(context, attributeSet) {
|
CoordinatorLayout.Behavior<V>(context, attributeSet) {
|
||||||
private var dep: View? = null
|
private var dep: View? = null
|
||||||
private var lastInsets: WindowInsets? = null
|
private var lastInsets: WindowInsets? = null
|
||||||
private var lastConsumed: Int? = null
|
private var lastConsumed = -1
|
||||||
private var setup: Boolean = false
|
private var setup = false
|
||||||
|
|
||||||
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean {
|
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean {
|
||||||
if (dependency.coordinatorLayoutBehavior is NeoBottomSheetBehavior) {
|
if (dependency.coordinatorLayoutBehavior is NeoBottomSheetBehavior) {
|
||||||
|
@ -56,7 +56,7 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val behavior = dependency.coordinatorLayoutBehavior as NeoBottomSheetBehavior
|
val behavior = dependency.coordinatorLayoutBehavior as NeoBottomSheetBehavior
|
||||||
val consumed = behavior.calculateConsumedByBar()
|
val consumed = behavior.calculateConsumedByBar()
|
||||||
if (consumed < Int.MIN_VALUE) {
|
if (consumed == Int.MIN_VALUE) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -350,6 +350,8 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BasicComparator<T : Music> private constructor() : Comparator<T> {
|
private class BasicComparator<T : Music> private constructor() : Comparator<T> {
|
||||||
|
// TODO: Use Collator for sorting?
|
||||||
|
|
||||||
override fun compare(a: T, b: T): Int {
|
override fun compare(a: T, b: T): Int {
|
||||||
val aSortName = a.sortName
|
val aSortName = a.sortName
|
||||||
val bSortName = b.sortName
|
val bSortName = b.sortName
|
||||||
|
|
|
@ -24,10 +24,9 @@ import org.oxycblt.auxio.databinding.ItemAccentBinding
|
||||||
import org.oxycblt.auxio.ui.recycler.BackingData
|
import org.oxycblt.auxio.ui.recycler.BackingData
|
||||||
import org.oxycblt.auxio.ui.recycler.BindingViewHolder
|
import org.oxycblt.auxio.ui.recycler.BindingViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.MonoAdapter
|
import org.oxycblt.auxio.ui.recycler.MonoAdapter
|
||||||
import org.oxycblt.auxio.util.getAttrColorSafe
|
import org.oxycblt.auxio.util.getAttrColorCompat
|
||||||
import org.oxycblt.auxio.util.getColorSafe
|
import org.oxycblt.auxio.util.getColorCompat
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
import org.oxycblt.auxio.util.stateList
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter that displays the accent palette.
|
* An adapter that displays the accent palette.
|
||||||
|
@ -82,7 +81,7 @@ class AccentViewHolder private constructor(private val binding: ItemAccentBindin
|
||||||
setSelected(false)
|
setSelected(false)
|
||||||
|
|
||||||
binding.accent.apply {
|
binding.accent.apply {
|
||||||
backgroundTintList = context.getColorSafe(item.primary).stateList
|
backgroundTintList = context.getColorCompat(item.primary)
|
||||||
contentDescription = context.getString(item.name)
|
contentDescription = context.getString(item.name)
|
||||||
TooltipCompat.setTooltipText(this, contentDescription)
|
TooltipCompat.setTooltipText(this, contentDescription)
|
||||||
setOnClickListener { listener.onAccentSelected(item) }
|
setOnClickListener { listener.onAccentSelected(item) }
|
||||||
|
@ -94,9 +93,9 @@ class AccentViewHolder private constructor(private val binding: ItemAccentBindin
|
||||||
isEnabled = !isSelected
|
isEnabled = !isSelected
|
||||||
iconTint =
|
iconTint =
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
context.getAttrColorSafe(R.attr.colorSurface).stateList
|
context.getAttrColorCompat(R.attr.colorSurface)
|
||||||
} else {
|
} else {
|
||||||
context.getColorSafe(android.R.color.transparent).stateList
|
context.getColorCompat(android.R.color.transparent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,8 @@ import android.util.AttributeSet
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import org.oxycblt.auxio.util.pxOfDp
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.util.getDimenSize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A sub-class of [GridLayoutManager] that automatically sets the spans so that they fit the width
|
* A sub-class of [GridLayoutManager] that automatically sets the spans so that they fit the width
|
||||||
|
@ -35,9 +36,9 @@ class AccentGridLayoutManager(
|
||||||
defStyleAttr: Int,
|
defStyleAttr: Int,
|
||||||
defStyleRes: Int
|
defStyleRes: Int
|
||||||
) : GridLayoutManager(context, attrs, defStyleAttr, defStyleRes) {
|
) : GridLayoutManager(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
// We use 72dp here since that's the rough size of the accent item.
|
// We use 56dp 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.
|
// This will need to be modified if this is used beyond the accent dialog.
|
||||||
private var columnWidth = context.pxOfDp(56f)
|
private var columnWidth = context.getDimenSize(R.dimen.size_accent_item)
|
||||||
|
|
||||||
private var lastWidth = -1
|
private var lastWidth = -1
|
||||||
private var lastHeight = -1
|
private var lastHeight = -1
|
||||||
|
@ -45,9 +46,7 @@ class AccentGridLayoutManager(
|
||||||
override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
|
override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
|
||||||
if (width > 0 && height > 0 && (lastWidth != width || lastHeight != height)) {
|
if (width > 0 && height > 0 && (lastWidth != width || lastHeight != height)) {
|
||||||
val totalSpace = width - paddingRight - paddingLeft
|
val totalSpace = width - paddingRight - paddingLeft
|
||||||
val spanCount = max(1, totalSpace / columnWidth)
|
spanCount = max(1, totalSpace / columnWidth)
|
||||||
|
|
||||||
setSpanCount(spanCount)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastWidth = width
|
lastWidth = width
|
||||||
|
|
|
@ -23,11 +23,12 @@ import android.util.AttributeSet
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
|
|
||||||
/** A [RecyclerView] that automatically applies insets to itself. */
|
/** A [RecyclerView] that automatically applies insets to itself. */
|
||||||
open class EdgeRecyclerView
|
open class AuxioRecyclerView
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||||
RecyclerView(context, attrs, defStyleAttr) {
|
RecyclerView(context, attrs, defStyleAttr) {
|
||||||
|
@ -52,4 +53,13 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
|
|
||||||
return insets
|
return insets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setSpanSizeLookup(lookup: (Int) -> Boolean) {
|
||||||
|
val glm = layoutManager as GridLayoutManager
|
||||||
|
val spanCount = glm.spanCount
|
||||||
|
glm.spanSizeLookup =
|
||||||
|
object : GridLayoutManager.SpanSizeLookup() {
|
||||||
|
override fun getSpanSize(position: Int) = if (lookup(position)) spanCount else 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -27,21 +27,21 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.divider.MaterialDivider
|
import com.google.android.material.divider.MaterialDivider
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.util.getDimenSizeSafe
|
import org.oxycblt.auxio.util.getDimenSize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A RecyclerView that enables something resembling the android:scrollIndicators attribute. Only
|
* A RecyclerView that enables something resembling the android:scrollIndicators attribute. Only
|
||||||
* used in dialogs.
|
* used in dialogs.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class ScrollIndicatorRecyclerView
|
class DialogRecyclerView
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||||
RecyclerView(context, attrs, defStyleAttr) {
|
RecyclerView(context, attrs, defStyleAttr) {
|
||||||
private val topDivider = MaterialDivider(context)
|
private val topDivider = MaterialDivider(context)
|
||||||
private val bottomDivider = MaterialDivider(context)
|
private val bottomDivider = MaterialDivider(context)
|
||||||
|
|
||||||
private val spacingMedium = context.getDimenSizeSafe(R.dimen.spacing_medium)
|
private val spacingMedium = context.getDimenSize(R.dimen.spacing_medium)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
updatePadding(top = spacingMedium)
|
updatePadding(top = spacingMedium)
|
|
@ -26,7 +26,7 @@ import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
// TODO: Reunify music updates and sorts under replace
|
// TODO: Unify music updates and sorts under replace
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter for one viewholder tied to one type of data. All functionality is derived from the
|
* An adapter for one viewholder tied to one type of data. All functionality is derived from the
|
||||||
|
|
|
@ -28,7 +28,7 @@ import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.getPluralSafe
|
import org.oxycblt.auxio.util.getPlural
|
||||||
import org.oxycblt.auxio.util.inflater
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -124,8 +124,8 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
|
||||||
binding.parentInfo.text =
|
binding.parentInfo.text =
|
||||||
binding.context.getString(
|
binding.context.getString(
|
||||||
R.string.fmt_two,
|
R.string.fmt_two,
|
||||||
binding.context.getPluralSafe(R.plurals.fmt_album_count, item.albums.size),
|
binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size),
|
||||||
binding.context.getPluralSafe(R.plurals.fmt_song_count, item.songs.size))
|
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size))
|
||||||
binding.root.apply {
|
binding.root.apply {
|
||||||
setOnClickListener { listener.onItemClick(item) }
|
setOnClickListener { listener.onItemClick(item) }
|
||||||
setOnLongClickListener { view ->
|
setOnLongClickListener { view ->
|
||||||
|
@ -168,7 +168,7 @@ private constructor(
|
||||||
binding.parentImage.bind(item)
|
binding.parentImage.bind(item)
|
||||||
binding.parentName.text = item.resolveName(binding.context)
|
binding.parentName.text = item.resolveName(binding.context)
|
||||||
binding.parentInfo.text =
|
binding.parentInfo.text =
|
||||||
binding.context.getPluralSafe(R.plurals.fmt_song_count, item.songs.size)
|
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)
|
||||||
binding.root.apply {
|
binding.root.apply {
|
||||||
setOnClickListener { listener.onItemClick(item) }
|
setOnClickListener { listener.onItemClick(item) }
|
||||||
setOnLongClickListener { view ->
|
setOnLongClickListener { view ->
|
||||||
|
|
|
@ -23,7 +23,7 @@ import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around [NotificationCompat.Builder] that automates parts of the notification setup, under
|
* Wrapper around [NotificationCompat.Builder] that automates parts of the notification setup, under
|
||||||
|
@ -32,7 +32,7 @@ import org.oxycblt.auxio.util.getSystemServiceSafe
|
||||||
*/
|
*/
|
||||||
abstract class ServiceNotification(context: Context, info: ChannelInfo) :
|
abstract class ServiceNotification(context: Context, info: ChannelInfo) :
|
||||||
NotificationCompat.Builder(context, info.id) {
|
NotificationCompat.Builder(context, info.id) {
|
||||||
private val notificationManager = context.getSystemServiceSafe(NotificationManager::class)
|
private val notificationManager = context.getSystemServiceCompat(NotificationManager::class)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
|
|
@ -23,14 +23,12 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.drawable.ColorDrawable
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.annotation.ColorRes
|
import androidx.annotation.ColorRes
|
||||||
import androidx.annotation.DimenRes
|
import androidx.annotation.DimenRes
|
||||||
import androidx.annotation.Dimension
|
import androidx.annotation.Dimension
|
||||||
|
@ -51,7 +49,7 @@ val Context.inflater: LayoutInflater
|
||||||
* Returns whether the current UI is in night mode or not. This will work if the theme is automatic
|
* Returns whether the current UI is in night mode or not. This will work if the theme is automatic
|
||||||
* as well.
|
* as well.
|
||||||
*/
|
*/
|
||||||
val Context.isNight: Boolean
|
val Context.isNight
|
||||||
get() =
|
get() =
|
||||||
resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
|
resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
|
||||||
Configuration.UI_MODE_NIGHT_YES
|
Configuration.UI_MODE_NIGHT_YES
|
||||||
|
@ -73,48 +71,25 @@ val Context.contentResolverSafe: ContentResolver
|
||||||
* @param value Int value for the plural.
|
* @param value Int value for the plural.
|
||||||
* @return The formatted string requested
|
* @return The formatted string requested
|
||||||
*/
|
*/
|
||||||
fun Context.getPluralSafe(@PluralsRes pluralsRes: Int, value: Int): String {
|
fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int) =
|
||||||
return try {
|
|
||||||
resources.getQuantityString(pluralsRes, value, value)
|
resources.getQuantityString(pluralsRes, value, value)
|
||||||
} catch (e: Exception) {
|
|
||||||
handleResourceFailure(e, "plural", "<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.
|
* Convenience method for getting a [ColorStateList] resource safely.
|
||||||
* @param color The color resource
|
* @param color The color resource
|
||||||
* @return The [ColorStateList] requested, or black if an error occurred.
|
* @return The [ColorStateList] requested
|
||||||
*/
|
*/
|
||||||
fun Context.getColorStateListSafe(@ColorRes color: Int): ColorStateList {
|
fun Context.getColorCompat(@ColorRes color: Int) =
|
||||||
return try {
|
requireNotNull(ContextCompat.getColorStateList(this, color)) {
|
||||||
unlikelyToBeNull(ContextCompat.getColorStateList(this, color))
|
"Invalid resource: State list was null"
|
||||||
} catch (e: Exception) {
|
|
||||||
handleResourceFailure(e, "color state list", getColorSafe(android.R.color.black).stateList)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a color attribute safely.
|
* Convenience method for getting a color attribute safely.
|
||||||
* @param attr The color attribute
|
* @param attr The color attribute
|
||||||
* @return The attribute requested, or black if an error occurred.
|
* @return The attribute requested
|
||||||
*/
|
*/
|
||||||
@ColorInt
|
fun Context.getAttrColorCompat(@AttrRes attr: Int): ColorStateList {
|
||||||
fun Context.getAttrColorSafe(@AttrRes attr: Int): Int {
|
|
||||||
// First resolve the attribute into its ID
|
// First resolve the attribute into its ID
|
||||||
val resolvedAttr = TypedValue()
|
val resolvedAttr = TypedValue()
|
||||||
theme.resolveAttribute(attr, resolvedAttr, true)
|
theme.resolveAttribute(attr, resolvedAttr, true)
|
||||||
|
@ -127,80 +102,32 @@ fun Context.getAttrColorSafe(@AttrRes attr: Int): Int {
|
||||||
resolvedAttr.data
|
resolvedAttr.data
|
||||||
}
|
}
|
||||||
|
|
||||||
return getColorSafe(color)
|
return getColorCompat(color)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a [Drawable] safely.
|
* Convenience method for getting a [Drawable] safely.
|
||||||
* @param drawable The drawable resource
|
* @param drawable The drawable resource
|
||||||
* @return The drawable requested, or black if an error occurred.
|
* @return The drawable requested
|
||||||
*/
|
*/
|
||||||
fun Context.getDrawableSafe(@DrawableRes drawable: Int): Drawable {
|
fun Context.getDrawableCompat(@DrawableRes drawable: Int) =
|
||||||
return try {
|
requireNotNull(ContextCompat.getDrawable(this, drawable)) {
|
||||||
requireNotNull(ContextCompat.getDrawable(this, drawable))
|
"Invalid resource: Drawable was null"
|
||||||
} catch (e: Exception) {
|
|
||||||
handleResourceFailure(e, "drawable", ColorDrawable(getColorSafe(android.R.color.black)))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a dimension safely.
|
* Convenience method for getting a dimension safely.
|
||||||
* @param dimen The dimension resource
|
* @param dimen The dimension resource
|
||||||
* @return The dimension requested, or 0 if an error occurred.
|
* @return The dimension requested
|
||||||
*/
|
*/
|
||||||
@Dimension
|
@Dimension fun Context.getDimen(@DimenRes dimen: Int) = resources.getDimension(dimen)
|
||||||
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.
|
* Convenience method for getting a dimension pixel size safely.
|
||||||
* @param dimen The dimension resource
|
* @param dimen The dimension resource
|
||||||
* @return The dimension requested, in pixels, or 0 if an error occurred.
|
* @return The dimension requested, in pixels
|
||||||
*/
|
*/
|
||||||
@Px
|
@Px fun Context.getDimenSize(@DimenRes dimen: Int) = resources.getDimensionPixelSize(dimen)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the pixels of the given dimension [dp].
|
|
||||||
* @param dp the dimension value
|
|
||||||
* @return The equivalent amount of pixels for [dp].
|
|
||||||
*/
|
|
||||||
@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")
|
|
||||||
e.logTraceOrThrow()
|
|
||||||
return default
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a system service without nullability issues.
|
* Convenience method for getting a system service without nullability issues.
|
||||||
|
@ -209,11 +136,10 @@ private fun <T> Context.handleResourceFailure(e: Exception, what: String, defaul
|
||||||
* @return The system service
|
* @return The system service
|
||||||
* @throws IllegalArgumentException If the system service cannot be retrieved.
|
* @throws IllegalArgumentException If the system service cannot be retrieved.
|
||||||
*/
|
*/
|
||||||
fun <T : Any> Context.getSystemServiceSafe(serviceClass: KClass<T>): T {
|
fun <T : Any> Context.getSystemServiceCompat(serviceClass: KClass<T>) =
|
||||||
return requireNotNull(ContextCompat.getSystemService(this, serviceClass.java)) {
|
requireNotNull(ContextCompat.getSystemService(this, serviceClass.java)) {
|
||||||
"System service ${serviceClass.simpleName} could not be instantiated"
|
"System service ${serviceClass.simpleName} could not be instantiated"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a toast using the provided string resource. */
|
/** Create a toast using the provided string resource. */
|
||||||
fun Context.showToast(@StringRes str: Int) {
|
fun Context.showToast(@StringRes str: Int) {
|
||||||
|
|
|
@ -19,7 +19,6 @@ package org.oxycblt.auxio.util
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
@ -27,7 +26,6 @@ import android.os.Build
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.ColorRes
|
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
@ -41,7 +39,6 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
@ -56,7 +53,7 @@ import org.oxycblt.auxio.R
|
||||||
*/
|
*/
|
||||||
fun View.disableDropShadowCompat() {
|
fun View.disableDropShadowCompat() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
val transparent = context.getColorSafe(android.R.color.transparent)
|
val transparent = context.getColorCompat(android.R.color.transparent).defaultColor
|
||||||
outlineAmbientShadowColor = transparent
|
outlineAmbientShadowColor = transparent
|
||||||
outlineSpotShadowColor = transparent
|
outlineSpotShadowColor = transparent
|
||||||
}
|
}
|
||||||
|
@ -113,42 +110,16 @@ val Drawable.isRtl: Boolean
|
||||||
val ViewBinding.context: Context
|
val ViewBinding.context: Context
|
||||||
get() = root.context
|
get() = root.context
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply the recommended spans for a [RecyclerView].
|
|
||||||
*
|
|
||||||
* @param shouldBeFullWidth Optional callback for determining whether an item should be full-width,
|
|
||||||
* regardless of spans
|
|
||||||
*/
|
|
||||||
fun RecyclerView.applySpans(shouldBeFullWidth: ((Int) -> Boolean)? = null) {
|
|
||||||
val spans = resources.getInteger(R.integer.recycler_spans)
|
|
||||||
|
|
||||||
if (spans > 1) {
|
|
||||||
val mgr = GridLayoutManager(context, spans)
|
|
||||||
|
|
||||||
if (shouldBeFullWidth != null) {
|
|
||||||
mgr.spanSizeLookup =
|
|
||||||
object : GridLayoutManager.SpanSizeLookup() {
|
|
||||||
override fun getSpanSize(position: Int): Int {
|
|
||||||
return if (shouldBeFullWidth(position)) spans else 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layoutManager = mgr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns whether a recyclerview can scroll. */
|
/** Returns whether a recyclerview can scroll. */
|
||||||
val RecyclerView.canScroll: Boolean
|
fun RecyclerView.canScroll() = computeVerticalScrollRange() > height
|
||||||
get() = computeVerticalScrollRange() > height
|
|
||||||
|
|
||||||
val View.coordinatorLayoutBehavior: CoordinatorLayout.Behavior<*>?
|
/**
|
||||||
|
* Shortcut to obtain the CoordinatorLayout behavior of a view. Null if not from a coordinator
|
||||||
|
* layout or if no behavior is present.
|
||||||
|
*/
|
||||||
|
val View.coordinatorLayoutBehavior: CoordinatorLayout.Behavior<View>?
|
||||||
get() = (layoutParams as? CoordinatorLayout.LayoutParams)?.behavior
|
get() = (layoutParams as? CoordinatorLayout.LayoutParams)?.behavior
|
||||||
|
|
||||||
/** Converts this color to a single-color [ColorStateList]. */
|
|
||||||
val @receiver:ColorRes Int.stateList
|
|
||||||
get() = ColorStateList.valueOf(this)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect a [stateFlow] into [block] eventually.
|
* Collect a [stateFlow] into [block] eventually.
|
||||||
*
|
*
|
||||||
|
|
|
@ -31,7 +31,7 @@ import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.util.getDimenSizeSafe
|
import org.oxycblt.auxio.util.getDimenSize
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,10 +85,10 @@ class WidgetComponent(private val context: Context) :
|
||||||
val cornerRadius =
|
val cornerRadius =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
// Android 12, always round the cover with the app widget's inner radius
|
// Android 12, always round the cover with the app widget's inner radius
|
||||||
context.getDimenSizeSafe(android.R.dimen.system_app_widget_inner_radius)
|
context.getDimenSize(android.R.dimen.system_app_widget_inner_radius)
|
||||||
} else if (settings.roundMode) {
|
} else if (settings.roundMode) {
|
||||||
// < Android 12, but the user still enabled round mode.
|
// < Android 12, but the user still enabled round mode.
|
||||||
context.getDimenSizeSafe(R.dimen.size_corners_medium)
|
context.getDimenSize(R.dimen.size_corners_medium)
|
||||||
} else {
|
} else {
|
||||||
// User did not enable round mode.
|
// User did not enable round mode.
|
||||||
0
|
0
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.oxycblt.auxio.ui.recycler.ScrollIndicatorRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
<org.oxycblt.auxio.ui.recycler.DialogRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/accent_recycler"
|
android:id="@+id/accent_recycler"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.oxycblt.auxio.ui.recycler.ScrollIndicatorRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
<org.oxycblt.auxio.ui.recycler.DialogRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/tab_recycler"
|
android:id="@+id/tab_recycler"
|
||||||
|
|
|
@ -20,11 +20,12 @@
|
||||||
|
|
||||||
</org.oxycblt.auxio.detail.DetailAppBarLayout>
|
</org.oxycblt.auxio.detail.DetailAppBarLayout>
|
||||||
|
|
||||||
<org.oxycblt.auxio.ui.recycler.EdgeRecyclerView
|
<org.oxycblt.auxio.ui.recycler.AuxioRecyclerView
|
||||||
android:id="@+id/detail_recycler"
|
android:id="@+id/detail_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
|
app:spanCount="@integer/recycler_spans"
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||||
tools:listitem="@layout/item_detail" />
|
tools:listitem="@layout/item_detail" />
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
style="@style/Widget.Auxio.RecyclerView.WithAdaptiveFab"
|
style="@style/Widget.Auxio.RecyclerView.WithAdaptiveFab"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
|
app:spanCount="@integer/recycler_spans"
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||||
tools:listitem="@layout/item_parent" />
|
tools:listitem="@layout/item_parent" />
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<org.oxycblt.auxio.ui.recycler.EdgeRecyclerView
|
<org.oxycblt.auxio.ui.recycler.AuxioRecyclerView
|
||||||
android:id="@+id/queue_recycler"
|
android:id="@+id/queue_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
@ -45,11 +45,12 @@
|
||||||
|
|
||||||
</org.oxycblt.auxio.ui.AuxioAppBarLayout>
|
</org.oxycblt.auxio.ui.AuxioAppBarLayout>
|
||||||
|
|
||||||
<org.oxycblt.auxio.ui.recycler.EdgeRecyclerView
|
<org.oxycblt.auxio.ui.recycler.AuxioRecyclerView
|
||||||
android:id="@+id/search_recycler"
|
android:id="@+id/search_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
|
app:spanCount="@integer/recycler_spans"
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||||
tools:listitem="@layout/item_song" />
|
tools:listitem="@layout/item_song" />
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
<dimen name="size_corners_mid_large">24dp</dimen>
|
<dimen name="size_corners_mid_large">24dp</dimen>
|
||||||
|
|
||||||
<dimen name="size_btn">48dp</dimen>
|
<dimen name="size_btn">48dp</dimen>
|
||||||
|
<dimen name="size_accent_item">56dp</dimen>
|
||||||
<dimen name="size_bottom_sheet_bar">64dp</dimen>
|
<dimen name="size_bottom_sheet_bar">64dp</dimen>
|
||||||
<dimen name="size_play_pause_button">72dp</dimen>
|
<dimen name="size_play_pause_button">72dp</dimen>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue