From 7d04aad9b70de6910790e27f7138424919aaecff Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Thu, 4 Aug 2022 20:11:25 -0600 Subject: [PATCH] util: clean up context utils Clean up the context utils to be more appropriately designed and efficient. --- .../java/org/oxycblt/auxio/MainFragment.kt | 22 ++-- .../auxio/detail/AlbumDetailFragment.kt | 9 +- .../auxio/detail/ArtistDetailFragment.kt | 8 +- .../auxio/detail/DetailAppBarLayout.kt | 12 +- .../auxio/detail/GenreDetailFragment.kt | 7 +- .../oxycblt/auxio/detail/SongDetailDialog.kt | 10 +- .../detail/recycler/AlbumDetailAdapter.kt | 4 +- .../detail/recycler/ArtistDetailAdapter.kt | 6 +- .../detail/recycler/GenreDetailAdapter.kt | 4 +- .../org/oxycblt/auxio/home/HomeFragment.kt | 33 +++-- .../home/fastscroll/FastScrollPopupView.kt | 20 ++- .../home/fastscroll/FastScrollRecyclerView.kt | 18 ++- .../auxio/home/list/HomeListFragment.kt | 2 - .../org/oxycblt/auxio/image/ImageGroup.kt | 8 +- .../oxycblt/auxio/image/StyledImageView.kt | 12 +- .../auxio/music/dirs/MusicDirsDialog.kt | 4 +- .../auxio/music/system/IndexerService.kt | 4 +- .../auxio/music/system/MediaStoreBackend.kt | 4 +- .../auxio/playback/PlaybackBarFragment.kt | 4 +- .../auxio/playback/PlaybackPanelFragment.kt | 27 +++-- .../auxio/playback/queue/QueueAdapter.kt | 8 +- .../auxio/playback/queue/QueueDragCallback.kt | 6 +- .../playback/queue/QueueSheetBehavior.kt | 4 +- .../oxycblt/auxio/search/SearchFragment.kt | 7 +- .../oxycblt/auxio/settings/AboutFragment.kt | 11 +- .../auxio/settings/SettingsListFragment.kt | 53 ++++---- .../settings/ui/IntListPreferenceDialog.kt | 25 ++-- .../org/oxycblt/auxio/ui/AuxioAppBarLayout.kt | 5 +- .../oxycblt/auxio/ui/AuxioSheetBehavior.kt | 7 +- .../auxio/ui/BottomSheetContentBehavior.kt | 6 +- .../main/java/org/oxycblt/auxio/ui/Sort.kt | 2 + .../oxycblt/auxio/ui/accent/AccentAdapter.kt | 11 +- .../ui/accent/AccentGridLayoutManager.kt | 11 +- ...geRecyclerView.kt => AuxioRecyclerView.kt} | 12 +- ...rRecyclerView.kt => DialogRecyclerView.kt} | 6 +- .../auxio/ui/recycler/RecyclerFramework.kt | 2 +- .../oxycblt/auxio/ui/recycler/ViewHolders.kt | 8 +- .../auxio/ui/system/ServiceNotification.kt | 4 +- .../org/oxycblt/auxio/util/ContextUtil.kt | 114 +++--------------- .../org/oxycblt/auxio/util/FrameworkUtil.kt | 43 ++----- .../oxycblt/auxio/widgets/WidgetComponent.kt | 6 +- app/src/main/res/layout/dialog_accent.xml | 2 +- app/src/main/res/layout/dialog_tabs.xml | 2 +- app/src/main/res/layout/fragment_detail.xml | 5 +- .../main/res/layout/fragment_home_list.xml | 3 +- app/src/main/res/layout/fragment_queue.xml | 2 +- app/src/main/res/layout/fragment_search.xml | 5 +- app/src/main/res/values/dimens.xml | 1 + 48 files changed, 246 insertions(+), 343 deletions(-) rename app/src/main/java/org/oxycblt/auxio/ui/recycler/{EdgeRecyclerView.kt => AuxioRecyclerView.kt} (81%) rename app/src/main/java/org/oxycblt/auxio/ui/recycler/{ScrollIndicatorRecyclerView.kt => DialogRecyclerView.kt} (95%) diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 04052b8c7..4ecc42f99 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -55,6 +55,7 @@ class MainFragment : private val navModel: NavigationViewModel by activityViewModels() private var callback: DynamicBackPressedCallback? = null private var lastInsets: WindowInsets? = null + private var elevationNormal = -1f override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -66,9 +67,12 @@ class MainFragment : override fun onBindingCreated(binding: FragmentMainBinding, savedInstanceState: Bundle?) { // --- UI SETUP --- - requireActivity() - .onBackPressedDispatcher.addCallback( - viewLifecycleOwner, DynamicBackPressedCallback().also { callback = it }) + val context = requireActivity() + + context.onBackPressedDispatcher.addCallback( + viewLifecycleOwner, DynamicBackPressedCallback().also { callback = it }) + + elevationNormal = requireContext().getDimen(R.dimen.elevation_normal) binding.root.setOnApplyWindowInsetsListener { _, insets -> lastInsets = insets @@ -77,8 +81,9 @@ class MainFragment : // Send meaningful accessibility events for bottom sheets ViewCompat.setAccessibilityPaneTitle( - binding.playbackSheet, getString(R.string.lbl_playback)) - ViewCompat.setAccessibilityPaneTitle(binding.queueSheet, getString(R.string.lbl_queue)) + binding.playbackSheet, context.getString(R.string.lbl_playback)) + ViewCompat.setAccessibilityPaneTitle( + binding.queueSheet, context.getString(R.string.lbl_queue)) val queueSheetBehavior = binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior? if (queueSheetBehavior != null) { @@ -97,8 +102,8 @@ class MainFragment : binding.queueSheet.apply { background = MaterialShapeDrawable.createWithElevationOverlay(context).apply { - fillColor = context.getAttrColorSafe(R.attr.colorSurface).stateList - elevation = context.getDimenSafe(R.dimen.elevation_normal) + fillColor = context.getAttrColorCompat(R.attr.colorSurface) + elevation = context.getDimen(R.dimen.elevation_normal) } setOnApplyWindowInsetsListener { v, insets -> @@ -182,8 +187,7 @@ class MainFragment : isInvisible = alpha == 0f } - binding.playbackSheet.translationZ = - requireContext().getDimenSafe(R.dimen.elevation_normal) * outPlaybackRatio + binding.playbackSheet.translationZ = elevationNormal * outPlaybackRatio playbackSheetBehavior.sheetBackgroundDrawable.alpha = (outPlaybackRatio * 255).toInt() binding.playbackBarFragment.apply { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 34646af39..ade43f324 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -43,7 +43,6 @@ import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.recycler.Header import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.canScroll import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collectImmediately @@ -88,9 +87,9 @@ class AlbumDetailFragment : binding.detailRecycler.apply { adapter = detailAdapter - applySpans { pos -> - val item = detailAdapter.data.currentList[pos] - item is Header || item is SortHeader || item is Album + setSpanSizeLookup { pos -> + val item = detailAdapter.data.getItem(pos) + 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 // correctly center the playing item, so make sure that the Toolbar is lifted in // that case. - binding.detailAppbar.isLifted = binding.detailRecycler.canScroll + binding.detailAppbar.isLifted = binding.detailRecycler.canScroll() } } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index fcd8459ba..4cb2f0424 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -41,7 +41,6 @@ import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.recycler.Header import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.context @@ -83,10 +82,9 @@ class ArtistDetailFragment : binding.detailRecycler.apply { adapter = detailAdapter - applySpans { pos -> - // If the item is an ActionHeader we need to also make the item full-width - val item = detailAdapter.data.currentList[pos] - item is Header || item is SortHeader || item is Artist + setSpanSizeLookup { pos -> + val item = detailAdapter.data.getItem(pos) + item is Artist || item is Header || item is SortHeader } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt index d208a4df0..91d1436ed 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt @@ -29,13 +29,10 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.appbar.AppBarLayout -import java.lang.Exception import java.lang.reflect.Field import org.oxycblt.auxio.R import org.oxycblt.auxio.ui.AuxioAppBarLayout 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 @@ -68,14 +65,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr val toolbar = findViewById(R.id.detail_toolbar) // Reflect to get the actual title view to do transformations on - val newTitleView = - 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 - } + val newTitleView = TOOLBAR_TITLE_TEXT_FIELD.get(toolbar) as AppCompatTextView newTitleView.alpha = 0f this.titleView = newTitleView diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index d26344988..5342ef72f 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -42,7 +42,6 @@ import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.recycler.Header import org.oxycblt.auxio.ui.recycler.Item -import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.context @@ -84,9 +83,9 @@ class GenreDetailFragment : binding.detailRecycler.apply { adapter = detailAdapter - applySpans { pos -> - val item = detailAdapter.data.currentList[pos] - item is Header || item is SortHeader || item is Genre + setSpanSizeLookup { pos -> + val item = detailAdapter.data.getItem(pos) + item is Genre || item is Header || item is SortHeader } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt index 1e949366d..dfccbb22d 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt @@ -64,14 +64,12 @@ class SongDetailDialog : ViewBindingDialogFragment() { if (song != null) { if (song.info != null) { + val context = requireContext() binding.detailContainer.isGone = false binding.detailFileName.setText(song.song.path.name) - binding.detailRelativeDir.setText( - song.song.path.parent.resolveName(requireContext())) - binding.detailFormat.setText( - song.info.resolvedMimeType.resolveName(requireContext())) - binding.detailSize.setText( - Formatter.formatFileSize(requireContext(), song.song.size)) + binding.detailRelativeDir.setText(song.song.path.parent.resolveName(context)) + binding.detailFormat.setText(song.info.resolvedMimeType.resolveName(context)) + binding.detailSize.setText(Formatter.formatFileSize(context, song.song.size)) binding.detailDuration.setText(song.song.durationSecs.formatDuration(true)) if (song.info.bitrateKbps != null) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt index 8dffbfc06..f602aa2aa 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt @@ -34,7 +34,7 @@ import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.formatDuration -import org.oxycblt.auxio.util.getPluralSafe +import org.oxycblt.auxio.util.getPlural 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) } ?: 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) diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt index 8995cc312..903d9b830 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt @@ -34,7 +34,7 @@ import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.util.context -import org.oxycblt.auxio.util.getPluralSafe +import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.inflater /** @@ -138,8 +138,8 @@ private class ArtistDetailViewHolder private constructor(private val binding: It binding.detailInfo.text = binding.context.getString( R.string.fmt_two, - binding.context.getPluralSafe(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_album_count, item.albums.size), + binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)) binding.detailPlayButton.setOnClickListener { listener.onPlayParent() } binding.detailShuffleButton.setOnClickListener { listener.onShuffleParent() } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt index 9726cd334..9a570e4fa 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt @@ -32,7 +32,7 @@ import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.ui.recycler.SongViewHolder import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.formatDuration -import org.oxycblt.auxio.util.getPluralSafe +import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.inflater /** @@ -106,7 +106,7 @@ private class GenreDetailViewHolder private constructor(private val binding: Ite binding.detailCover.bind(item) binding.detailName.text = item.resolveName(binding.context) 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.detailPlayButton.setOnClickListener { listener.onPlayParent() } binding.detailShuffleButton.setOnClickListener { listener.onShuffleParent() } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 5cf70f509..82226f2c6 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -106,7 +106,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI // Load the track color in manually as it's unclear whether the track actually supports // using a ColorStateList in the resources binding.homeIndexingProgress.trackColor = - requireContext().getColorStateListSafe(R.color.sel_track).defaultColor + requireContext().getColorCompat(R.color.sel_track).defaultColor binding.homePager.apply { adapter = HomePagerAdapter() @@ -279,6 +279,8 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI binding.homeFab.show() binding.homeIndexingContainer.visibility = View.INVISIBLE } else { + val context = requireContext() + binding.homeIndexingContainer.visibility = View.VISIBLE logD("Received non-ok response $response") @@ -286,28 +288,28 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI when (response) { is Indexer.Response.Err -> { 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 { visibility = View.VISIBLE - text = getString(R.string.lbl_retry) + text = context.getString(R.string.lbl_retry) setOnClickListener { musicModel.reindex() } } } is Indexer.Response.NoMusic -> { 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 { visibility = View.VISIBLE - text = getString(R.string.lbl_retry) + text = context.getString(R.string.lbl_retry) setOnClickListener { musicModel.reindex() } } } is Indexer.Response.NoPerms -> { 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 { visibility = View.VISIBLE - text = getString(R.string.lbl_grant) + text = context.getString(R.string.lbl_grant) setOnClickListener { storagePermissionLauncher.launch( Manifest.permission.READ_EXTERNAL_STORAGE) @@ -324,14 +326,16 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI binding.homeIndexingProgress.visibility = View.VISIBLE binding.homeIndexingAction.visibility = View.INVISIBLE + val context = requireContext() + when (indexing) { 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 } is Indexer.Indexing.Songs -> { 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 { isIndeterminate = false max = indexing.total @@ -370,14 +374,9 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI * https://al-e-shevelev.medium.com/how-to-reduce-scroll-sensitivity-of-viewpager2-widget-87797ad02414 */ private fun ViewPager2.reduceSensitivity(by: Int) { - try { - val recycler = VIEW_PAGER_RECYCLER_FIELD.get(this@reduceSensitivity) - val slop = VIEW_PAGER_TOUCH_SLOP_FIELD.get(recycler) as Int - VIEW_PAGER_TOUCH_SLOP_FIELD.set(recycler, slop * by) - } catch (e: Exception) { - logE("Unable to reduce ViewPager sensitivity (likely an internal code change)") - e.logTraceOrThrow() - } + val recycler = VIEW_PAGER_RECYCLER_FIELD.get(this@reduceSensitivity) + val slop = VIEW_PAGER_TOUCH_SLOP_FIELD.get(recycler) as Int + VIEW_PAGER_TOUCH_SLOP_FIELD.set(recycler, slop * by) } /** Forces the view to recreate all fragments contained within it. */ diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupView.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupView.kt index 9ff51cee0..7680c4c47 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupView.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupView.kt @@ -34,9 +34,8 @@ import android.view.Gravity import androidx.core.widget.TextViewCompat import com.google.android.material.textview.MaterialTextView import org.oxycblt.auxio.R -import org.oxycblt.auxio.util.getAttrColorSafe -import org.oxycblt.auxio.util.getDimenOffsetSafe -import org.oxycblt.auxio.util.getDimenSizeSafe +import org.oxycblt.auxio.util.getAttrColorCompat +import org.oxycblt.auxio.util.getDimenSize import org.oxycblt.auxio.util.isRtl class FastScrollPopupView @@ -44,17 +43,17 @@ class FastScrollPopupView constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0) : MaterialTextView(context, attrs, defStyleRes) { init { - minimumWidth = context.getDimenSizeSafe(R.dimen.fast_scroll_popup_min_width) - minimumHeight = context.getDimenSizeSafe(R.dimen.fast_scroll_popup_min_height) + minimumWidth = context.getDimenSize(R.dimen.fast_scroll_popup_min_width) + minimumHeight = context.getDimenSize(R.dimen.fast_scroll_popup_min_height) TextViewCompat.setTextAppearance(this, R.style.TextAppearance_Auxio_HeadlineLarge) - setTextColor(context.getAttrColorSafe(R.attr.colorOnSecondary)) + setTextColor(context.getAttrColorCompat(R.attr.colorOnSecondary)) ellipsize = TextUtils.TruncateAt.MIDDLE gravity = Gravity.CENTER includeFontPadding = false alpha = 0f - elevation = context.getDimenSizeSafe(R.dimen.elevation_normal).toFloat() + elevation = context.getDimenSize(R.dimen.elevation_normal).toFloat() background = FastScrollPopupDrawable(context) } @@ -62,16 +61,15 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0) private val paint: Paint = Paint().apply { isAntiAlias = true - color = context.getAttrColorSafe(R.attr.colorSecondary) + color = context.getAttrColorCompat(R.attr.colorSecondary).defaultColor style = Paint.Style.FILL } private val path = Path() private val matrix = Matrix() - private val paddingStart = - context.getDimenOffsetSafe(R.dimen.fast_scroll_popup_padding_start) - private val paddingEnd = context.getDimenOffsetSafe(R.dimen.fast_scroll_popup_padding_end) + private val paddingStart = context.getDimenSize(R.dimen.fast_scroll_popup_padding_start) + private val paddingEnd = context.getDimenSize(R.dimen.fast_scroll_popup_padding_end) override fun draw(canvas: Canvas) { canvas.drawPath(path, paint) diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt index ce22496e1..0b3e4c77c 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt @@ -34,11 +34,9 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlin.math.abs import org.oxycblt.auxio.R -import org.oxycblt.auxio.ui.recycler.EdgeRecyclerView -import org.oxycblt.auxio.util.canScroll -import org.oxycblt.auxio.util.getDimenOffsetSafe -import org.oxycblt.auxio.util.getDimenSizeSafe -import org.oxycblt.auxio.util.getDrawableSafe +import org.oxycblt.auxio.ui.recycler.AuxioRecyclerView +import org.oxycblt.auxio.util.getDimenSize +import org.oxycblt.auxio.util.getDrawableCompat import org.oxycblt.auxio.util.isRtl import org.oxycblt.auxio.util.isUnder import org.oxycblt.auxio.util.systemBarInsetsCompat @@ -71,12 +69,12 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat class FastScrollRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) : - EdgeRecyclerView(context, attrs, defStyleAttr) { + AuxioRecyclerView(context, attrs, defStyleAttr) { // Thumb private val thumbView = View(context).apply { alpha = 0f - background = context.getDrawableSafe(R.drawable.ui_scroll_thumb) + background = context.getDrawableCompat(R.drawable.ui_scroll_thumb) } 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) .apply { 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 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 var downX = 0f @@ -289,7 +287,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr } private fun updateScrollbarState() { - if (!canScroll || childCount == 0) { + if (computeVerticalScrollRange() <= height || childCount == 0) { return } diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt index 35dceb2df..c51cbaac6 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt @@ -27,7 +27,6 @@ import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.MenuItemListener 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. @@ -46,7 +45,6 @@ abstract class HomeListFragment : override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) { binding.homeRecycler.popupProvider = this binding.homeRecycler.listener = this - binding.homeRecycler.applySpans() } override fun onDestroyBinding(binding: FragmentHomeListBinding) { diff --git a/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt b/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt index b1a99b46f..702057f75 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt @@ -29,8 +29,8 @@ import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.util.getColorStateListSafe -import org.oxycblt.auxio.util.getDrawableSafe +import org.oxycblt.auxio.util.getColorCompat +import org.oxycblt.auxio.util.getDrawableCompat /** * Effectively a super-charged [StyledImageView]. @@ -66,7 +66,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr indicator = StyledImageView(context).apply { cornerRadius = this@ImageGroup.cornerRadius - staticIcon = context.getDrawableSafe(R.drawable.ic_currently_playing_24) + staticIcon = context.getDrawableCompat(R.drawable.ic_currently_playing_24) } addView(inner) @@ -83,7 +83,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr getChildAt(1)?.apply { background = MaterialShapeDrawable().apply { - fillColor = context.getColorStateListSafe(R.color.sel_cover_bg) + fillColor = context.getColorCompat(R.color.sel_cover_bg) setCornerSize(cornerRadius) } } diff --git a/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt b/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt index b4efcaf3c..ed3783104 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt @@ -39,8 +39,8 @@ import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.util.getColorStateListSafe -import org.oxycblt.auxio.util.getDrawableSafe +import org.oxycblt.auxio.util.getColorCompat +import org.oxycblt.auxio.util.getDrawableCompat /** * 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 background = MaterialShapeDrawable().apply { - fillColor = context.getColorStateListSafe(R.color.sel_cover_bg) + fillColor = context.getColorCompat(R.color.sel_cover_bg) setCornerSize(cornerRadius) } @@ -96,7 +96,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr styledAttrs.getResourceId( R.styleable.StyledImageView_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) @@ -126,14 +126,14 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr dispose() load(music) { - error(StyledDrawable(context, context.getDrawableSafe(error))) + error(StyledDrawable(context, context.getDrawableCompat(error))) transformations(SquareFrameTransform.INSTANCE) } } private class StyledDrawable(context: Context, private val src: Drawable) : Drawable() { 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) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirsDialog.kt index d6052e563..652db6ee6 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirsDialog.kt @@ -32,7 +32,7 @@ import org.oxycblt.auxio.music.Directory import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment 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.showToast @@ -45,7 +45,7 @@ class MusicDirsDialog : private val dirAdapter = MusicDirAdapter(this) private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) } private val storageManager: StorageManager by lifecycleObject { binding -> - binding.context.getSystemServiceSafe(StorageManager::class) + binding.context.getSystemServiceCompat(StorageManager::class) } override fun onCreateBinding(inflater: LayoutInflater) = diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt index df1e4c22b..c116e5df5 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt @@ -34,7 +34,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.system.ForegroundManager import org.oxycblt.auxio.util.contentResolverSafe -import org.oxycblt.auxio.util.getSystemServiceSafe +import org.oxycblt.auxio.util.getSystemServiceCompat import org.oxycblt.auxio.util.logD /** @@ -73,7 +73,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback { observingNotification = ObservingNotification(this) wakeLock = - getSystemServiceSafe(PowerManager::class) + getSystemServiceCompat(PowerManager::class) .newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":IndexerService") diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/MediaStoreBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/system/MediaStoreBackend.kt index 4d59d0482..ea1820285 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/MediaStoreBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/MediaStoreBackend.kt @@ -30,7 +30,7 @@ import java.io.File import org.oxycblt.auxio.music.* import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.contentResolverSafe -import org.oxycblt.auxio.util.getSystemServiceSafe +import org.oxycblt.auxio.util.getSystemServiceCompat import org.oxycblt.auxio.util.logD /* @@ -114,7 +114,7 @@ abstract class MediaStoreBackend : Indexer.Backend { override fun query(context: Context): Cursor { val settings = Settings(context) - val storageManager = context.getSystemServiceSafe(StorageManager::class) + val storageManager = context.getSystemServiceCompat(StorageManager::class) volumes.addAll(storageManager.storageVolumesCompat) val dirs = settings.getMusicDirs(storageManager) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt index 9a3e7f059..83e51b60c 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt @@ -28,7 +28,7 @@ import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.fragment.ViewBindingFragment import org.oxycblt.auxio.util.androidActivityViewModels 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 @@ -58,7 +58,7 @@ class PlaybackBarFragment : ViewBindingFragment() { // Load the track color in manually as it's unclear whether the track actually supports // using a ColorStateList in the resources binding.playbackProgressBar.trackColor = - requireContext().getColorStateListSafe(R.color.sel_track).defaultColor + requireContext().getColorCompat(R.color.sel_track).defaultColor binding.playbackPlayPause.setOnClickListener { playbackModel.invertPlaying() } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt index 7880602f2..602c3cf57 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -34,7 +34,7 @@ import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.fragment.ViewBindingFragment import org.oxycblt.auxio.util.androidActivityViewModels 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.systemBarInsetsCompat @@ -148,8 +148,11 @@ class PlaybackPanelFragment : } private fun updateParent(parent: MusicParent?) { - requireBinding().playbackToolbar.subtitle = - parent?.resolveName(requireContext()) ?: getString(R.string.lbl_all_songs) + val binding = requireBinding() + val context = requireContext() + + binding.playbackToolbar.subtitle = + parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs) } private fun updatePosition(positionSecs: Long) { @@ -157,16 +160,16 @@ class PlaybackPanelFragment : } private fun updateRepeat(repeatMode: RepeatMode) { - requireBinding().playbackRepeat.apply { - isActivated = repeatMode != RepeatMode.NONE - val iconRes = - when (repeatMode) { - RepeatMode.NONE -> R.drawable.ic_repeat_off_24 - RepeatMode.ALL -> R.drawable.ic_repeat_on_24 - RepeatMode.TRACK -> R.drawable.ic_repeat_one_24 - } + val iconRes = + when (repeatMode) { + RepeatMode.NONE -> R.drawable.ic_repeat_off_24 + RepeatMode.ALL -> R.drawable.ic_repeat_on_24 + RepeatMode.TRACK -> R.drawable.ic_repeat_one_24 + } - icon = requireContext().getDrawableSafe(iconRes) + requireBinding().playbackRepeat.apply { + icon = requireContext().getDrawableCompat(iconRes) + isActivated = repeatMode != RepeatMode.NONE } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt index cdc265bca..b96cbc5d2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -87,8 +87,8 @@ private constructor( val backgroundDrawable = MaterialShapeDrawable.createWithElevationOverlay(binding.root.context).apply { - fillColor = binding.context.getAttrColorSafe(R.attr.colorSurface).stateList - elevation = binding.context.getDimenSafe(R.dimen.elevation_normal) * 5 + fillColor = binding.context.getAttrColorCompat(R.attr.colorSurface) + elevation = binding.context.getDimen(R.dimen.elevation_normal) * 5 alpha = 0 } @@ -114,8 +114,8 @@ private constructor( LayerDrawable( arrayOf( MaterialShapeDrawable.createWithElevationOverlay(binding.context).apply { - fillColor = binding.context.getAttrColorSafe(R.attr.colorSurface).stateList - elevation = binding.context.getDimenSafe(R.dimen.elevation_normal) + fillColor = binding.context.getAttrColorCompat(R.attr.colorSurface) + elevation = binding.context.getDimen(R.dimen.elevation_normal) }, backgroundDrawable)) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt index 4b09b91ff..01f940341 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt @@ -23,7 +23,7 @@ import androidx.core.view.isInvisible import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R -import org.oxycblt.auxio.util.getDimenSafe +import org.oxycblt.auxio.util.getDimen import org.oxycblt.auxio.util.logD /** @@ -68,7 +68,7 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe logD("Lifting queue item") val bg = holder.backgroundDrawable - val elevation = recyclerView.context.getDimenSafe(R.dimen.elevation_normal) + val elevation = recyclerView.context.getDimen(R.dimen.elevation_normal) holder.itemView .animate() .translationZ(elevation) @@ -108,7 +108,7 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe logD("Dropping queue item") val bg = holder.backgroundDrawable - val elevation = recyclerView.context.getDimenSafe(R.dimen.elevation_normal) + val elevation = recyclerView.context.getDimen(R.dimen.elevation_normal) holder.itemView .animate() .translationZ(0f) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueSheetBehavior.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueSheetBehavior.kt index 4fc9e1001..0ec75162a 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueSheetBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueSheetBehavior.kt @@ -33,11 +33,11 @@ import org.oxycblt.auxio.util.* class QueueSheetBehavior(context: Context, attributeSet: AttributeSet?) : AuxioSheetBehavior(context, attributeSet) { private var barHeight = 0 - private var barSpacing = context.getDimenSizeSafe(R.dimen.spacing_small) + private var barSpacing = context.getDimenSize(R.dimen.spacing_small) init { 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) = diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index c1856b1b3..80f7f1a8a 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -44,11 +44,10 @@ import org.oxycblt.auxio.ui.recycler.Header import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.util.androidViewModels -import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.context -import org.oxycblt.auxio.util.getSystemServiceSafe +import org.oxycblt.auxio.util.getSystemServiceCompat import org.oxycblt.auxio.util.logW /** @@ -64,7 +63,7 @@ class SearchFragment : private val searchAdapter = SearchAdapter(this) private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) } private val imm: InputMethodManager by lifecycleObject { binding -> - binding.context.getSystemServiceSafe(InputMethodManager::class) + binding.context.getSystemServiceCompat(InputMethodManager::class) } private var launchedKeyboard = false @@ -108,7 +107,7 @@ class SearchFragment : binding.searchRecycler.apply { adapter = searchAdapter - applySpans { pos -> searchAdapter.data.currentList[pos] is Header } + setSpanSizeLookup { pos -> searchAdapter.data.getItem(pos) is Header } } // --- VIEWMODEL SETUP --- diff --git a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt index 106fe43af..eafa08110 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt @@ -104,6 +104,8 @@ class AboutFragment : ViewBindingFragment() { private fun openLinkInBrowser(link: String) { logD("Opening $link") + val context = requireContext() + val browserIntent = Intent(Intent.ACTION_VIEW, link.toUri()).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) @@ -112,10 +114,10 @@ class AboutFragment : ViewBindingFragment() { // [along with adding a new permission that breaks the old manual code], so // we just do a typical activity launch. try { - requireContext().startActivity(browserIntent) + context.startActivity(browserIntent) } catch (e: ActivityNotFoundException) { // 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) { // On older versions of android, opening links from an ACTION_VIEW intent might @@ -123,8 +125,7 @@ class AboutFragment : ViewBindingFragment() { // case, we will try to manually handle these cases before we try to launch the // browser. val pkgName = - requireContext() - .packageManager + context.packageManager .resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY) ?.run { activityInfo.packageName } @@ -144,7 +145,7 @@ class AboutFragment : ViewBindingFragment() { } } else { // No app installed to open the link - requireContext().showToast(R.string.err_no_app) + context.showToast(R.string.err_no_app) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt index 67d4db54e..6c1f1f475 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt @@ -95,41 +95,49 @@ class SettingsListFragment : PreferenceFragmentCompat() { dialog.setTargetFragment(this, 0) dialog.show(parentFragmentManager, IntListPreferenceDialog.TAG) } - is WrappedDialogPreference -> + is WrappedDialogPreference -> { + val context = requireContext() when (preference.key) { - getString(R.string.set_key_accent) -> + context.getString(R.string.set_key_accent) -> AccentCustomizeDialog() .show(childFragmentManager, AccentCustomizeDialog.TAG) - getString(R.string.set_key_lib_tabs) -> + context.getString(R.string.set_key_lib_tabs) -> TabCustomizeDialog().show(childFragmentManager, TabCustomizeDialog.TAG) - getString(R.string.set_key_pre_amp) -> + context.getString(R.string.set_key_pre_amp) -> PreAmpCustomizeDialog() .show(childFragmentManager, PreAmpCustomizeDialog.TAG) - getString(R.string.set_key_music_dirs) -> + context.getString(R.string.set_key_music_dirs) -> MusicDirsDialog().show(childFragmentManager, MusicDirsDialog.TAG) else -> logEOrThrow("Unexpected dialog key ${preference.key}") } + } else -> super.onDisplayPreferenceDialog(preference) } } override fun onPreferenceTreeClick(preference: Preference): Boolean { + val context = requireContext() + when (preference.key) { - getString(R.string.set_key_save_state) -> { - playbackModel.savePlaybackState { context?.showToast(R.string.lng_state_saved) } + context.getString(R.string.set_key_save_state) -> { + 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) } + context.getString(R.string.set_key_wipe_state) -> { + playbackModel.wipePlaybackState { + this.context?.showToast(R.string.lng_state_wiped) + } } - getString(R.string.set_key_restore_state) -> + context.getString(R.string.set_key_restore_state) -> playbackModel.tryRestorePlaybackState { restored -> if (restored) { - context?.showToast(R.string.lng_state_restored) + this.context?.showToast(R.string.lng_state_restored) } 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() } else -> return super.onPreferenceTreeClick(preference) @@ -139,7 +147,8 @@ class SettingsListFragment : PreferenceFragmentCompat() { } private fun setupPreference(preference: Preference) { - val settings = Settings(requireContext()) + val context = requireActivity() + val settings = Settings(context) if (!preference.isVisible) return @@ -151,31 +160,31 @@ class SettingsListFragment : PreferenceFragmentCompat() { preference.apply { when (key) { - getString(R.string.set_key_theme) -> { + context.getString(R.string.set_key_theme) -> { onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value -> AppCompatDelegate.setDefaultNightMode(value as Int) true } } - getString(R.string.set_key_accent) -> { + context.getString(R.string.set_key_accent) -> { summary = context.getString(settings.accent.name) } - getString(R.string.set_key_black_theme) -> { + context.getString(R.string.set_key_black_theme) -> { onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> - if (requireContext().isNight) { - requireActivity().recreate() + if (context.isNight) { + context.recreate() } true } } - getString(R.string.set_key_show_covers), - getString(R.string.set_key_quality_covers) -> { + context.getString(R.string.set_key_show_covers), + context.getString(R.string.set_key_quality_covers) -> { onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> - Coil.imageLoader(requireContext()).apply { this.memoryCache?.clear() } + Coil.imageLoader(context).apply { this.memoryCache?.clear() } true } } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreferenceDialog.kt b/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreferenceDialog.kt index 0c1e85309..1550bce2b 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreferenceDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreferenceDialog.kt @@ -17,7 +17,6 @@ package org.oxycblt.auxio.settings.ui -import android.app.Dialog import android.os.Bundle import androidx.preference.PreferenceDialogFragmentCompat import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -29,22 +28,20 @@ class IntListPreferenceDialog : PreferenceDialogFragmentCompat() { get() = (preference as IntListPreference) 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 // of the alert dialog, so we have to manually override onCreateDialog and customize it // ourselves. - val builder = MaterialAlertDialogBuilder(requireContext(), theme) - builder.setTitle(listPreference.title) - builder.setPositiveButton(null, null) - builder.setNegativeButton(R.string.lbl_cancel, null) - builder.setSingleChoiceItems(listPreference.entries, listPreference.getValueIndex()) { - _, - index -> - pendingValueIndex = index - dismiss() - } - return builder.create() - } + MaterialAlertDialogBuilder(requireContext(), theme) + .setTitle(listPreference.title) + .setPositiveButton(null, null) + .setNegativeButton(R.string.lbl_cancel, null) + .setSingleChoiceItems(listPreference.entries, listPreference.getValueIndex()) { _, index + -> + pendingValueIndex = index + dismiss() + } + .create() override fun onDialogClosed(positiveResult: Boolean) { if (pendingValueIndex > -1) { diff --git a/app/src/main/java/org/oxycblt/auxio/ui/AuxioAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/AuxioAppBarLayout.kt index 4494ec98c..9916197c1 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/AuxioAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/AuxioAppBarLayout.kt @@ -26,6 +26,7 @@ import androidx.annotation.AttrRes import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.res.ResourcesCompat 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 @@ -47,8 +48,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr if (child != null) { val coordinator = parent as CoordinatorLayout - (layoutParams as CoordinatorLayout.LayoutParams) - .behavior?.onNestedPreScroll(coordinator, this, coordinator, 0, 0, tConsumed, 0) + coordinatorLayoutBehavior?.onNestedPreScroll( + coordinator, this, coordinator, 0, 0, tConsumed, 0) } true diff --git a/app/src/main/java/org/oxycblt/auxio/ui/AuxioSheetBehavior.kt b/app/src/main/java/org/oxycblt/auxio/ui/AuxioSheetBehavior.kt index 79e2c6a56..7b197f385 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/AuxioSheetBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/AuxioSheetBehavior.kt @@ -40,8 +40,8 @@ abstract class AuxioSheetBehavior(context: Context, attributeSet: Attr private var setup = false val sheetBackgroundDrawable = MaterialShapeDrawable.createWithElevationOverlay(context).apply { - fillColor = context.getAttrColorSafe(R.attr.colorSurface).stateList - elevation = context.getDimenSafe(R.dimen.elevation_normal) + fillColor = context.getAttrColorCompat(R.attr.colorSurface) + elevation = context.getDimen(R.dimen.elevation_normal) } init { @@ -75,7 +75,8 @@ abstract class AuxioSheetBehavior(context: Context, attributeSet: Attr background = LayerDrawable( arrayOf( - ColorDrawable(context.getAttrColorSafe(R.attr.colorSurface)), + ColorDrawable( + context.getAttrColorCompat(R.attr.colorSurface).defaultColor), sheetBackgroundDrawable)) // Try to disable drop shadows if possible. diff --git a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetContentBehavior.kt b/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetContentBehavior.kt index a2a118584..bac1ae091 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetContentBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetContentBehavior.kt @@ -37,8 +37,8 @@ class BottomSheetContentBehavior(context: Context, attributeSet: Attri CoordinatorLayout.Behavior(context, attributeSet) { private var dep: View? = null private var lastInsets: WindowInsets? = null - private var lastConsumed: Int? = null - private var setup: Boolean = false + private var lastConsumed = -1 + private var setup = false override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean { if (dependency.coordinatorLayoutBehavior is NeoBottomSheetBehavior) { @@ -56,7 +56,7 @@ class BottomSheetContentBehavior(context: Context, attributeSet: Attri ): Boolean { val behavior = dependency.coordinatorLayoutBehavior as NeoBottomSheetBehavior val consumed = behavior.calculateConsumedByBar() - if (consumed < Int.MIN_VALUE) { + if (consumed == Int.MIN_VALUE) { return false } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt index 01e066ebb..39c274646 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt @@ -350,6 +350,8 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { } private class BasicComparator private constructor() : Comparator { + // TODO: Use Collator for sorting? + override fun compare(a: T, b: T): Int { val aSortName = a.sortName val bSortName = b.sortName diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentAdapter.kt b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentAdapter.kt index 671dd4b07..a58600062 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentAdapter.kt @@ -24,10 +24,9 @@ import org.oxycblt.auxio.databinding.ItemAccentBinding import org.oxycblt.auxio.ui.recycler.BackingData import org.oxycblt.auxio.ui.recycler.BindingViewHolder import org.oxycblt.auxio.ui.recycler.MonoAdapter -import org.oxycblt.auxio.util.getAttrColorSafe -import org.oxycblt.auxio.util.getColorSafe +import org.oxycblt.auxio.util.getAttrColorCompat +import org.oxycblt.auxio.util.getColorCompat import org.oxycblt.auxio.util.inflater -import org.oxycblt.auxio.util.stateList /** * An adapter that displays the accent palette. @@ -82,7 +81,7 @@ class AccentViewHolder private constructor(private val binding: ItemAccentBindin setSelected(false) binding.accent.apply { - backgroundTintList = context.getColorSafe(item.primary).stateList + backgroundTintList = context.getColorCompat(item.primary) contentDescription = context.getString(item.name) TooltipCompat.setTooltipText(this, contentDescription) setOnClickListener { listener.onAccentSelected(item) } @@ -94,9 +93,9 @@ class AccentViewHolder private constructor(private val binding: ItemAccentBindin isEnabled = !isSelected iconTint = if (isSelected) { - context.getAttrColorSafe(R.attr.colorSurface).stateList + context.getAttrColorCompat(R.attr.colorSurface) } else { - context.getColorSafe(android.R.color.transparent).stateList + context.getColorCompat(android.R.color.transparent) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt index 8ce95604e..a8b16070c 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt @@ -22,7 +22,8 @@ import android.util.AttributeSet import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView 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 @@ -35,9 +36,9 @@ class AccentGridLayoutManager( defStyleAttr: Int, defStyleRes: Int ) : 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. - private var columnWidth = context.pxOfDp(56f) + private var columnWidth = context.getDimenSize(R.dimen.size_accent_item) private var lastWidth = -1 private var lastHeight = -1 @@ -45,9 +46,7 @@ class AccentGridLayoutManager( override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) { if (width > 0 && height > 0 && (lastWidth != width || lastHeight != height)) { val totalSpace = width - paddingRight - paddingLeft - val spanCount = max(1, totalSpace / columnWidth) - - setSpanCount(spanCount) + spanCount = max(1, totalSpace / columnWidth) } lastWidth = width diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/EdgeRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/AuxioRecyclerView.kt similarity index 81% rename from app/src/main/java/org/oxycblt/auxio/ui/recycler/EdgeRecyclerView.kt rename to app/src/main/java/org/oxycblt/auxio/ui/recycler/AuxioRecyclerView.kt index a38f044f3..84b8b8e6a 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/EdgeRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/recycler/AuxioRecyclerView.kt @@ -23,11 +23,12 @@ import android.util.AttributeSet import android.view.WindowInsets import androidx.annotation.AttrRes import androidx.core.view.updatePadding +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.util.systemBarInsetsCompat /** A [RecyclerView] that automatically applies insets to itself. */ -open class EdgeRecyclerView +open class AuxioRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) : RecyclerView(context, attrs, defStyleAttr) { @@ -52,4 +53,13 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr 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 + } + } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/ScrollIndicatorRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/DialogRecyclerView.kt similarity index 95% rename from app/src/main/java/org/oxycblt/auxio/ui/recycler/ScrollIndicatorRecyclerView.kt rename to app/src/main/java/org/oxycblt/auxio/ui/recycler/DialogRecyclerView.kt index 86cd02269..b15ff9e54 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/ScrollIndicatorRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/recycler/DialogRecyclerView.kt @@ -27,21 +27,21 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.divider.MaterialDivider 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 * used in dialogs. * @author OxygenCobalt */ -class ScrollIndicatorRecyclerView +class DialogRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) : RecyclerView(context, attrs, defStyleAttr) { private val topDivider = 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 { updatePadding(top = spacingMedium) diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/RecyclerFramework.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/RecyclerFramework.kt index 4385772df..d2a86bc18 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/RecyclerFramework.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/recycler/RecyclerFramework.kt @@ -26,7 +26,7 @@ import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.DiffUtil 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 diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt index f89de54b1..b74c961bb 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt @@ -28,7 +28,7 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.util.context -import org.oxycblt.auxio.util.getPluralSafe +import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.inflater /** @@ -124,8 +124,8 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin binding.parentInfo.text = binding.context.getString( R.string.fmt_two, - binding.context.getPluralSafe(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_album_count, item.albums.size), + binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)) binding.root.apply { setOnClickListener { listener.onItemClick(item) } setOnLongClickListener { view -> @@ -168,7 +168,7 @@ private constructor( binding.parentImage.bind(item) binding.parentName.text = item.resolveName(binding.context) 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 { setOnClickListener { listener.onItemClick(item) } setOnLongClickListener { view -> diff --git a/app/src/main/java/org/oxycblt/auxio/ui/system/ServiceNotification.kt b/app/src/main/java/org/oxycblt/auxio/ui/system/ServiceNotification.kt index 8955b246e..87572c8aa 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/system/ServiceNotification.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/system/ServiceNotification.kt @@ -23,7 +23,7 @@ import android.content.Context import android.os.Build import androidx.annotation.StringRes 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 @@ -32,7 +32,7 @@ import org.oxycblt.auxio.util.getSystemServiceSafe */ abstract class ServiceNotification(context: Context, info: ChannelInfo) : NotificationCompat.Builder(context, info.id) { - private val notificationManager = context.getSystemServiceSafe(NotificationManager::class) + private val notificationManager = context.getSystemServiceCompat(NotificationManager::class) init { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { diff --git a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt index 2b514358c..4ff681e6f 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt @@ -23,14 +23,12 @@ import android.content.Context import android.content.Intent import android.content.res.ColorStateList import android.content.res.Configuration -import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.os.Build import android.util.TypedValue import android.view.LayoutInflater import android.widget.Toast import androidx.annotation.AttrRes -import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.annotation.DimenRes import androidx.annotation.Dimension @@ -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 * as well. */ -val Context.isNight: Boolean +val Context.isNight get() = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES @@ -73,48 +71,25 @@ val Context.contentResolverSafe: ContentResolver * @param value Int value for the plural. * @return The formatted string requested */ -fun Context.getPluralSafe(@PluralsRes pluralsRes: Int, value: Int): String { - return try { - resources.getQuantityString(pluralsRes, value, value) - } catch (e: Exception) { - handleResourceFailure(e, "plural", "") - } -} - -/** - * 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)) - } -} +fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int) = + resources.getQuantityString(pluralsRes, value, value) /** * Convenience method for getting a [ColorStateList] resource safely. * @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 { - return try { - unlikelyToBeNull(ContextCompat.getColorStateList(this, color)) - } catch (e: Exception) { - handleResourceFailure(e, "color state list", getColorSafe(android.R.color.black).stateList) +fun Context.getColorCompat(@ColorRes color: Int) = + requireNotNull(ContextCompat.getColorStateList(this, color)) { + "Invalid resource: State list was null" } -} /** * Convenience method for getting a color attribute safely. * @param attr The color attribute - * @return The attribute requested, or black if an error occurred. + * @return The attribute requested */ -@ColorInt -fun Context.getAttrColorSafe(@AttrRes attr: Int): Int { +fun Context.getAttrColorCompat(@AttrRes attr: Int): ColorStateList { // First resolve the attribute into its ID val resolvedAttr = TypedValue() theme.resolveAttribute(attr, resolvedAttr, true) @@ -127,80 +102,32 @@ fun Context.getAttrColorSafe(@AttrRes attr: Int): Int { resolvedAttr.data } - return getColorSafe(color) + return getColorCompat(color) } /** * Convenience method for getting a [Drawable] safely. * @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 { - return try { - requireNotNull(ContextCompat.getDrawable(this, drawable)) - } catch (e: Exception) { - handleResourceFailure(e, "drawable", ColorDrawable(getColorSafe(android.R.color.black))) +fun Context.getDrawableCompat(@DrawableRes drawable: Int) = + requireNotNull(ContextCompat.getDrawable(this, drawable)) { + "Invalid resource: Drawable was null" } -} /** * Convenience method for getting a dimension safely. * @param dimen The dimension resource - * @return The dimension requested, or 0 if an error occurred. + * @return The dimension requested */ -@Dimension -fun Context.getDimenSafe(@DimenRes dimen: Int): Float { - return try { - resources.getDimension(dimen) - } catch (e: Exception) { - handleResourceFailure(e, "dimen", 0f) - } -} +@Dimension fun Context.getDimen(@DimenRes dimen: Int) = resources.getDimension(dimen) /** * Convenience method for getting a dimension pixel size safely. * @param dimen The dimension resource - * @return The dimension requested, in pixels, or 0 if an error occurred. + * @return The dimension requested, in pixels */ -@Px -fun Context.getDimenSizeSafe(@DimenRes dimen: Int): Int { - return try { - resources.getDimensionPixelSize(dimen) - } catch (e: Exception) { - handleResourceFailure(e, "dimen", 0) - } -} - -/** - * Convenience method for getting a dimension pixel offset safely. - * @param dimen The dimension resource - * @return The dimension requested, in pixels, or 0 if an error occurred. - */ -@Px -fun Context.getDimenOffsetSafe(@DimenRes dimen: Int): Int { - return try { - resources.getDimensionPixelOffset(dimen) - } catch (e: Exception) { - handleResourceFailure(e, "dimen", 0) - } -} - -/** - * 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 Context.handleResourceFailure(e: Exception, what: String, default: T): T { - logE("$what load failed") - e.logTraceOrThrow() - return default -} +@Px fun Context.getDimenSize(@DimenRes dimen: Int) = resources.getDimensionPixelSize(dimen) /** * Convenience method for getting a system service without nullability issues. @@ -209,11 +136,10 @@ private fun Context.handleResourceFailure(e: Exception, what: String, defaul * @return The system service * @throws IllegalArgumentException If the system service cannot be retrieved. */ -fun Context.getSystemServiceSafe(serviceClass: KClass): T { - return requireNotNull(ContextCompat.getSystemService(this, serviceClass.java)) { +fun Context.getSystemServiceCompat(serviceClass: KClass) = + requireNotNull(ContextCompat.getSystemService(this, serviceClass.java)) { "System service ${serviceClass.simpleName} could not be instantiated" } -} /** Create a toast using the provided string resource. */ fun Context.showToast(@StringRes str: Int) { diff --git a/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt index 9ff10ef44..0eba10018 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt @@ -19,7 +19,6 @@ package org.oxycblt.auxio.util import android.app.Application import android.content.Context -import android.content.res.ColorStateList import android.database.Cursor import android.database.sqlite.SQLiteDatabase import android.graphics.drawable.Drawable @@ -27,7 +26,6 @@ import android.os.Build import android.view.View import android.view.WindowInsets import androidx.activity.viewModels -import androidx.annotation.ColorRes import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.coordinatorlayout.widget.CoordinatorLayout @@ -41,7 +39,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import kotlinx.coroutines.CoroutineScope @@ -56,7 +53,7 @@ import org.oxycblt.auxio.R */ fun View.disableDropShadowCompat() { 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 outlineSpotShadowColor = transparent } @@ -113,42 +110,16 @@ val Drawable.isRtl: Boolean val ViewBinding.context: 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. */ -val RecyclerView.canScroll: Boolean - get() = computeVerticalScrollRange() > height +fun RecyclerView.canScroll() = 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? 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. * diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt index 76aada3d0..16cfbd1ba 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt @@ -31,7 +31,7 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.util.getDimenSizeSafe +import org.oxycblt.auxio.util.getDimenSize import org.oxycblt.auxio.util.logD /** @@ -85,10 +85,10 @@ class WidgetComponent(private val context: Context) : val cornerRadius = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // 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) { // < Android 12, but the user still enabled round mode. - context.getDimenSizeSafe(R.dimen.size_corners_medium) + context.getDimenSize(R.dimen.size_corners_medium) } else { // User did not enable round mode. 0 diff --git a/app/src/main/res/layout/dialog_accent.xml b/app/src/main/res/layout/dialog_accent.xml index 2e8fe4336..50287b87a 100644 --- a/app/src/main/res/layout/dialog_accent.xml +++ b/app/src/main/res/layout/dialog_accent.xml @@ -1,5 +1,5 @@ - - - diff --git a/app/src/main/res/layout/fragment_home_list.xml b/app/src/main/res/layout/fragment_home_list.xml index da74600e2..15bd17a2a 100644 --- a/app/src/main/res/layout/fragment_home_list.xml +++ b/app/src/main/res/layout/fragment_home_list.xml @@ -6,6 +6,7 @@ style="@style/Widget.Auxio.RecyclerView.WithAdaptiveFab" android:layout_width="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" tools:listitem="@layout/item_parent" /> diff --git a/app/src/main/res/layout/fragment_queue.xml b/app/src/main/res/layout/fragment_queue.xml index dcf527955..a9dd9dfe7 100644 --- a/app/src/main/res/layout/fragment_queue.xml +++ b/app/src/main/res/layout/fragment_queue.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index da1cf5847..61c79cf8a 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -21,6 +21,7 @@ 24dp 48dp + 56dp 64dp 72dp