util: redocument
Redocument the util module. This should make the purpose of the utilities used in this app clearer.
This commit is contained in:
parent
e92b69e399
commit
7415c28e2d
27 changed files with 346 additions and 210 deletions
3
app/proguard-rules.pro
vendored
3
app/proguard-rules.pro
vendored
|
@ -20,5 +20,6 @@
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
# Free software does not obsfucate. Also it's easier to debug stack traces.
|
# Obsfucation is what proprietary software does to keep the user unaware of it's abuses.
|
||||||
|
# Also it's easier to debug if the class names remain unmangled.
|
||||||
-dontobfuscate
|
-dontobfuscate
|
|
@ -33,15 +33,14 @@ import org.oxycblt.auxio.image.extractor.MusicKeyer
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auxio.
|
* Auxio: A simple, rational music player for android.
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
class AuxioApp : Application(), ImageLoaderFactory {
|
class AuxioApp : Application(), ImageLoaderFactory {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
// Migrate any settings that may have changed in an app update.
|
||||||
Settings(this).migrate()
|
Settings(this).migrate()
|
||||||
|
|
||||||
// Adding static shortcuts in a dynamic manner is better than declaring them
|
// Adding static shortcuts in a dynamic manner is better than declaring them
|
||||||
// manually, as it will properly handle the difference between debug and release
|
// manually, as it will properly handle the difference between debug and release
|
||||||
// Auxio instances.
|
// Auxio instances.
|
||||||
|
@ -75,7 +74,14 @@ class AuxioApp : Application(), ImageLoaderFactory {
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* The ID of the "Shuffle All" shortcut.
|
||||||
|
*/
|
||||||
const val SHORTCUT_SHUFFLE_ID = "shortcut_shuffle"
|
const val SHORTCUT_SHUFFLE_ID = "shortcut_shuffle"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [Intent] name for the "Shuffle All" shortcut.
|
||||||
|
*/
|
||||||
const val INTENT_KEY_SHORTCUT_SHUFFLE = BuildConfig.APPLICATION_ID + ".action.SHUFFLE_ALL"
|
const val INTENT_KEY_SHORTCUT_SHUFFLE = BuildConfig.APPLICATION_ID + ".action.SHUFFLE_ALL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,6 @@ class MainFragment :
|
||||||
private val navModel: NavigationViewModel by activityViewModels()
|
private val navModel: NavigationViewModel by activityViewModels()
|
||||||
private val callback = DynamicBackPressedCallback()
|
private val callback = DynamicBackPressedCallback()
|
||||||
private var lastInsets: WindowInsets? = null
|
private var lastInsets: WindowInsets? = null
|
||||||
|
|
||||||
private val elevationNormal: Float by lifecycleObject { binding ->
|
private val elevationNormal: Float by lifecycleObject { binding ->
|
||||||
binding.context.getDimen(R.dimen.elevation_normal)
|
binding.context.getDimen(R.dimen.elevation_normal)
|
||||||
}
|
}
|
||||||
|
@ -72,7 +71,8 @@ class MainFragment :
|
||||||
override fun onBindingCreated(binding: FragmentMainBinding, savedInstanceState: Bundle?) {
|
override fun onBindingCreated(binding: FragmentMainBinding, savedInstanceState: Bundle?) {
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
val context = requireActivity()
|
val context = requireActivity()
|
||||||
|
// Override the back pressed callback so we can map back navigation to collapsing
|
||||||
|
// navigation, navigation out of detail views, etc.
|
||||||
context.onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
|
context.onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
|
||||||
|
|
||||||
binding.root.setOnApplyWindowInsetsListener { _, insets ->
|
binding.root.setOnApplyWindowInsetsListener { _, insets ->
|
||||||
|
@ -89,24 +89,26 @@ class MainFragment :
|
||||||
val queueSheetBehavior =
|
val queueSheetBehavior =
|
||||||
binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior?
|
binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior?
|
||||||
if (queueSheetBehavior != null) {
|
if (queueSheetBehavior != null) {
|
||||||
|
// Bottom sheet mode, set up click listeners.
|
||||||
val playbackSheetBehavior =
|
val playbackSheetBehavior =
|
||||||
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior
|
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior
|
||||||
|
|
||||||
unlikelyToBeNull(binding.handleWrapper).setOnClickListener {
|
unlikelyToBeNull(binding.handleWrapper).setOnClickListener {
|
||||||
if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED &&
|
if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED &&
|
||||||
queueSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED) {
|
queueSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED) {
|
||||||
|
// Playback sheet is expanded and queue sheet is collapsed, we can expand it.
|
||||||
queueSheetBehavior.state = NeoBottomSheetBehavior.STATE_EXPANDED
|
queueSheetBehavior.state = NeoBottomSheetBehavior.STATE_EXPANDED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Dual-pane mode, color/pad the queue sheet manually.
|
// Dual-pane mode, manually style the static queue sheet.
|
||||||
binding.queueSheet.apply {
|
binding.queueSheet.apply {
|
||||||
|
// Emulate the elevated bottom sheet style.
|
||||||
background =
|
background =
|
||||||
MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
MaterialShapeDrawable.createWithElevationOverlay(context).apply {
|
||||||
fillColor = context.getAttrColorCompat(R.attr.colorSurface)
|
fillColor = context.getAttrColorCompat(R.attr.colorSurface)
|
||||||
elevation = context.getDimen(R.dimen.elevation_normal)
|
elevation = context.getDimen(R.dimen.elevation_normal)
|
||||||
}
|
}
|
||||||
|
// Apply bar insets for the queue's RecyclerView to usee.
|
||||||
setOnApplyWindowInsetsListener { v, insets ->
|
setOnApplyWindowInsetsListener { v, insets ->
|
||||||
v.updatePadding(top = insets.systemBarInsetsCompat.top)
|
v.updatePadding(top = insets.systemBarInsetsCompat.top)
|
||||||
insets
|
insets
|
||||||
|
@ -115,7 +117,6 @@ class MainFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
collect(navModel.mainNavigationAction, ::handleMainNavigation)
|
collect(navModel.mainNavigationAction, ::handleMainNavigation)
|
||||||
collect(navModel.exploreNavigationItem, ::handleExploreNavigation)
|
collect(navModel.exploreNavigationItem, ::handleExploreNavigation)
|
||||||
collect(navModel.exploreNavigationArtists, ::handleExplorePicker)
|
collect(navModel.exploreNavigationArtists, ::handleExplorePicker)
|
||||||
|
@ -125,7 +126,6 @@ class MainFragment :
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
|
|
||||||
// Callback could still reasonably fire even if we clear the binding, attach/detach
|
// Callback could still reasonably fire even if we clear the binding, attach/detach
|
||||||
// our pre-draw listener our listener in onStart/onStop respectively.
|
// our pre-draw listener our listener in onStart/onStop respectively.
|
||||||
requireBinding().playbackSheet.viewTreeObserver.addOnPreDrawListener(this)
|
requireBinding().playbackSheet.viewTreeObserver.addOnPreDrawListener(this)
|
||||||
|
@ -139,33 +139,33 @@ class MainFragment :
|
||||||
override fun onPreDraw(): Boolean {
|
override fun onPreDraw(): Boolean {
|
||||||
// We overload CoordinatorLayout far too much to rely on any of it's typical
|
// We overload CoordinatorLayout far too much to rely on any of it's typical
|
||||||
// callback functionality. Just update all transitions before every draw. Should
|
// callback functionality. Just update all transitions before every draw. Should
|
||||||
// probably be cheap *enough.*
|
// probably be cheap enough.
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
|
|
||||||
val playbackSheetBehavior =
|
val playbackSheetBehavior =
|
||||||
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior
|
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackBottomSheetBehavior
|
||||||
|
val queueSheetBehavior =
|
||||||
|
binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior?
|
||||||
|
|
||||||
val playbackRatio = max(playbackSheetBehavior.calculateSlideOffset(), 0f)
|
val playbackRatio = max(playbackSheetBehavior.calculateSlideOffset(), 0f)
|
||||||
|
|
||||||
val outPlaybackRatio = 1 - playbackRatio
|
val outPlaybackRatio = 1 - playbackRatio
|
||||||
val halfOutRatio = min(playbackRatio * 2, 1f)
|
val halfOutRatio = min(playbackRatio * 2, 1f)
|
||||||
val halfInPlaybackRatio = max(playbackRatio - 0.5f, 0f) * 2
|
val halfInPlaybackRatio = max(playbackRatio - 0.5f, 0f) * 2
|
||||||
|
|
||||||
val queueSheetBehavior =
|
|
||||||
binding.queueSheet.coordinatorLayoutBehavior as QueueBottomSheetBehavior?
|
|
||||||
|
|
||||||
if (queueSheetBehavior != null) {
|
if (queueSheetBehavior != null) {
|
||||||
// Queue sheet, take queue into account so the playback bar is shown and the playback
|
// Queue sheet available, the normal transition applies, but it now much be combined
|
||||||
// panel is hidden when the queue sheet is expanded.
|
// with another transition where the playback panel disappears and the playback bar
|
||||||
|
// appears as the queue sheet expands.
|
||||||
val queueRatio = max(queueSheetBehavior.calculateSlideOffset(), 0f)
|
val queueRatio = max(queueSheetBehavior.calculateSlideOffset(), 0f)
|
||||||
val halfOutQueueRatio = min(queueRatio * 2, 1f)
|
val halfOutQueueRatio = min(queueRatio * 2, 1f)
|
||||||
val halfInQueueRatio = max(queueRatio - 0.5f, 0f) * 2
|
val halfInQueueRatio = max(queueRatio - 0.5f, 0f) * 2
|
||||||
|
|
||||||
binding.playbackBarFragment.alpha = max(1 - halfOutRatio, halfInQueueRatio)
|
binding.playbackBarFragment.alpha = max(1 - halfOutRatio, halfInQueueRatio)
|
||||||
binding.playbackPanelFragment.alpha = min(halfInPlaybackRatio, 1 - halfOutQueueRatio)
|
binding.playbackPanelFragment.alpha = min(halfInPlaybackRatio, 1 - halfOutQueueRatio)
|
||||||
binding.queueFragment.alpha = queueRatio
|
binding.queueFragment.alpha = queueRatio
|
||||||
|
|
||||||
if (playbackModel.song.value != null) {
|
if (playbackModel.song.value != null) {
|
||||||
// Hack around the playback sheet intercepting swipe events on the queue bar
|
// Playback sheet intercepts queue sheet touch events, prevent that from
|
||||||
|
// occurring by disabling dragging whenever the queue sheet is expanded.
|
||||||
playbackSheetBehavior.isDraggable =
|
playbackSheetBehavior.isDraggable =
|
||||||
queueSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED
|
queueSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED
|
||||||
}
|
}
|
||||||
|
@ -175,26 +175,42 @@ class MainFragment :
|
||||||
binding.playbackPanelFragment.alpha = halfInPlaybackRatio
|
binding.playbackPanelFragment.alpha = halfInPlaybackRatio
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fade out the content as the playback panel expands.
|
||||||
|
// TODO: Replace with shadow?
|
||||||
binding.exploreNavHost.apply {
|
binding.exploreNavHost.apply {
|
||||||
alpha = outPlaybackRatio
|
alpha = outPlaybackRatio
|
||||||
|
// Prevent interactions when the content fully fades out.
|
||||||
isInvisible = alpha == 0f
|
isInvisible = alpha == 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reduce playback sheet elevation as it expands. This involves both updating the
|
||||||
|
// shadow elevation for older versions, and fading out the background drawable
|
||||||
|
// containing the elevation overlay.
|
||||||
binding.playbackSheet.translationZ = elevationNormal * outPlaybackRatio
|
binding.playbackSheet.translationZ = elevationNormal * outPlaybackRatio
|
||||||
playbackSheetBehavior.sheetBackgroundDrawable.alpha = (outPlaybackRatio * 255).toInt()
|
playbackSheetBehavior.sheetBackgroundDrawable.alpha = (outPlaybackRatio * 255).toInt()
|
||||||
|
|
||||||
|
// Fade out the playback bar as the panel expands.
|
||||||
binding.playbackBarFragment.apply {
|
binding.playbackBarFragment.apply {
|
||||||
|
// Prevent interactions when the playback bar fully fades out.
|
||||||
isInvisible = alpha == 0f
|
isInvisible = alpha == 0f
|
||||||
|
// As the playback bar expands, we also want to subtly translate the bar to
|
||||||
|
// align with the top inset. This results in both a smooth transition from the bar
|
||||||
|
// to the playback panel's toolbar, but also a correctly positioned playback bar
|
||||||
|
// for when the queue sheet expands.
|
||||||
lastInsets?.let { translationY = it.systemBarInsetsCompat.top * halfOutRatio }
|
lastInsets?.let { translationY = it.systemBarInsetsCompat.top * halfOutRatio }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent interactions when the playback panell fully fades out.
|
||||||
binding.playbackPanelFragment.isInvisible = binding.playbackPanelFragment.alpha == 0f
|
binding.playbackPanelFragment.isInvisible = binding.playbackPanelFragment.alpha == 0f
|
||||||
|
|
||||||
binding.queueSheet.apply {
|
binding.queueSheet.apply {
|
||||||
|
// Queue sheet (not queue content) should fade out with the playback panel.
|
||||||
alpha = halfInPlaybackRatio
|
alpha = halfInPlaybackRatio
|
||||||
|
// Prevent interactions when the queue sheet fully fades out.
|
||||||
binding.queueSheet.isInvisible = alpha == 0f
|
binding.queueSheet.isInvisible = alpha == 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent interactions when the queue content fully fades out.
|
||||||
binding.queueFragment.isInvisible = binding.queueFragment.alpha == 0f
|
binding.queueFragment.isInvisible = binding.queueFragment.alpha == 0f
|
||||||
|
|
||||||
if (playbackModel.song.value == null) {
|
if (playbackModel.song.value == null) {
|
||||||
|
@ -216,8 +232,7 @@ class MainFragment :
|
||||||
when (action) {
|
when (action) {
|
||||||
is MainNavigationAction.Expand -> tryExpandAll()
|
is MainNavigationAction.Expand -> tryExpandAll()
|
||||||
is MainNavigationAction.Collapse -> tryCollapseAll()
|
is MainNavigationAction.Collapse -> tryCollapseAll()
|
||||||
// No need to reset selection despite navigating to another screen, as the
|
// TODO: Figure out how to clear out the selections as one moves between screens.
|
||||||
// main fragment destinations don't have selections
|
|
||||||
is MainNavigationAction.Directions -> findNavController().navigate(action.directions)
|
is MainNavigationAction.Directions -> findNavController().navigate(action.directions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ import com.google.android.material.appbar.AppBarLayout
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.shared.AuxioAppBarLayout
|
import org.oxycblt.auxio.shared.AuxioAppBarLayout
|
||||||
|
import org.oxycblt.auxio.util.getInteger
|
||||||
import org.oxycblt.auxio.util.lazyReflectedField
|
import org.oxycblt.auxio.util.lazyReflectedField
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,9 +132,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
addUpdateListener { titleView.alpha = it.animatedValue as Float }
|
addUpdateListener { titleView.alpha = it.animatedValue as Float }
|
||||||
duration =
|
duration =
|
||||||
if (titleShown == true) {
|
if (titleShown == true) {
|
||||||
context.resources.getInteger(R.integer.anim_fade_enter_duration).toLong()
|
context.getInteger(R.integer.anim_fade_enter_duration).toLong()
|
||||||
} else {
|
} else {
|
||||||
context.resources.getInteger(R.integer.anim_fade_exit_duration).toLong()
|
context.getInteger(R.integer.anim_fade_exit_duration).toLong()
|
||||||
}
|
}
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
|
@ -295,7 +295,7 @@ class DetailViewModel(application: Application) :
|
||||||
val extractor = MediaExtractor()
|
val extractor = MediaExtractor()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
extractor.setDataSource(application, song.uri, emptyMap())
|
extractor.setDataSource(context, song.uri, emptyMap())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Can feasibly fail with invalid file formats. Note that this isn't considered
|
// Can feasibly fail with invalid file formats. Note that this isn't considered
|
||||||
// an error condition in the UI, as there is still plenty of other song information
|
// an error condition in the UI, as there is still plenty of other song information
|
||||||
|
|
|
@ -31,7 +31,7 @@ import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.Sort
|
import org.oxycblt.auxio.music.Sort
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.util.application
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,13 +131,13 @@ class HomeViewModel(application: Application) :
|
||||||
|
|
||||||
override fun onSettingChanged(key: String) {
|
override fun onSettingChanged(key: String) {
|
||||||
when (key) {
|
when (key) {
|
||||||
application.getString(R.string.set_key_lib_tabs) -> {
|
context.getString(R.string.set_key_lib_tabs) -> {
|
||||||
// Tabs changed, update the current tabs and set up a re-create event.
|
// Tabs changed, update the current tabs and set up a re-create event.
|
||||||
currentTabModes = getVisibleTabModes()
|
currentTabModes = getVisibleTabModes()
|
||||||
_shouldRecreate.value = true
|
_shouldRecreate.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
application.getString(R.string.set_key_hide_collaborators) -> {
|
context.getString(R.string.set_key_hide_collaborators) -> {
|
||||||
// Changes in the hide collaborator setting will change the artist contents
|
// Changes in the hide collaborator setting will change the artist contents
|
||||||
// of the library, consider it a library update.
|
// of the library, consider it a library update.
|
||||||
onLibraryChanged(musicStore.library)
|
onLibraryChanged(musicStore.library)
|
||||||
|
|
|
@ -35,7 +35,7 @@ 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.getAttrColorCompat
|
import org.oxycblt.auxio.util.getAttrColorCompat
|
||||||
import org.oxycblt.auxio.util.getDimenSize
|
import org.oxycblt.auxio.util.getDimenPixels
|
||||||
import org.oxycblt.auxio.util.isRtl
|
import org.oxycblt.auxio.util.isRtl
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,8 +47,8 @@ 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.getDimenSize(R.dimen.fast_scroll_popup_min_width)
|
minimumWidth = context.getDimenPixels(R.dimen.fast_scroll_popup_min_width)
|
||||||
minimumHeight = context.getDimenSize(R.dimen.fast_scroll_popup_min_height)
|
minimumHeight = context.getDimenPixels(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.getAttrColorCompat(R.attr.colorOnSecondary))
|
setTextColor(context.getAttrColorCompat(R.attr.colorOnSecondary))
|
||||||
|
@ -57,7 +57,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0)
|
||||||
includeFontPadding = false
|
includeFontPadding = false
|
||||||
|
|
||||||
alpha = 0f
|
alpha = 0f
|
||||||
elevation = context.getDimenSize(R.dimen.elevation_normal).toFloat()
|
elevation = context.getDimenPixels(R.dimen.elevation_normal).toFloat()
|
||||||
background = FastScrollPopupDrawable(context)
|
background = FastScrollPopupDrawable(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,8 +72,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0)
|
||||||
private val path = Path()
|
private val path = Path()
|
||||||
private val matrix = Matrix()
|
private val matrix = Matrix()
|
||||||
|
|
||||||
private val paddingStart = context.getDimenSize(R.dimen.fast_scroll_popup_padding_start)
|
private val paddingStart = context.getDimenPixels(R.dimen.fast_scroll_popup_padding_start)
|
||||||
private val paddingEnd = context.getDimenSize(R.dimen.fast_scroll_popup_padding_end)
|
private val paddingEnd = context.getDimenPixels(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)
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.oxycblt.auxio.home.fastscroll
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.PointF
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
|
@ -36,11 +37,7 @@ 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.list.recycler.AuxioRecyclerView
|
import org.oxycblt.auxio.list.recycler.AuxioRecyclerView
|
||||||
import org.oxycblt.auxio.util.getDimenSize
|
import org.oxycblt.auxio.util.*
|
||||||
import org.oxycblt.auxio.util.getDrawableCompat
|
|
||||||
import org.oxycblt.auxio.util.isRtl
|
|
||||||
import org.oxycblt.auxio.util.isUnder
|
|
||||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [RecyclerView] that enables better fast-scrolling. This is fundamentally a implementation of
|
* A [RecyclerView] that enables better fast-scrolling. This is fundamentally a implementation of
|
||||||
|
@ -126,7 +123,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.getDimenSize(R.dimen.spacing_small)
|
marginEnd = context.getDimenPixels(R.dimen.spacing_small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +131,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
|
|
||||||
// Touch
|
// Touch
|
||||||
private val minTouchTargetSize =
|
private val minTouchTargetSize =
|
||||||
context.getDimenSize(R.dimen.fast_scroll_thumb_touch_target_size)
|
context.getDimenPixels(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
|
||||||
|
@ -474,7 +471,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
view
|
view
|
||||||
.animate()
|
.animate()
|
||||||
.alpha(1f)
|
.alpha(1f)
|
||||||
.setDuration(context.resources.getInteger(R.integer.anim_fade_enter_duration).toLong())
|
.setDuration(context.getInteger(R.integer.anim_fade_enter_duration).toLong())
|
||||||
.start()
|
.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,7 +479,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
view
|
view
|
||||||
.animate()
|
.animate()
|
||||||
.alpha(0f)
|
.alpha(0f)
|
||||||
.setDuration(context.resources.getInteger(R.integer.anim_fade_exit_duration).toLong())
|
.setDuration(context.getInteger(R.integer.anim_fade_exit_duration).toLong())
|
||||||
.start()
|
.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,8 @@ import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.util.getAttrColorCompat
|
import org.oxycblt.auxio.util.getAttrColorCompat
|
||||||
import org.oxycblt.auxio.util.getColorCompat
|
import org.oxycblt.auxio.util.getColorCompat
|
||||||
import org.oxycblt.auxio.util.getDimenSize
|
import org.oxycblt.auxio.util.getDimenPixels
|
||||||
|
import org.oxycblt.auxio.util.getInteger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A super-charged [StyledImageView]. This class enables the following features in addition
|
* A super-charged [StyledImageView]. This class enables the following features in addition
|
||||||
|
@ -114,7 +115,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
// Override the layout params of the indicator so that it's in the
|
// Override the layout params of the indicator so that it's in the
|
||||||
// bottom left corner.
|
// bottom left corner.
|
||||||
gravity = Gravity.BOTTOM or Gravity.END
|
gravity = Gravity.BOTTOM or Gravity.END
|
||||||
val spacing = context.getDimenSize(R.dimen.spacing_tiny)
|
val spacing = context.getDimenPixels(R.dimen.spacing_tiny)
|
||||||
updateMarginsRelative(bottom = spacing, end = spacing)
|
updateMarginsRelative(bottom = spacing, end = spacing)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -225,12 +226,12 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
// Activated -> Show selection indicator
|
// Activated -> Show selection indicator
|
||||||
targetAlpha = 1f
|
targetAlpha = 1f
|
||||||
targetDuration =
|
targetDuration =
|
||||||
context.resources.getInteger(R.integer.anim_fade_enter_duration).toLong()
|
context.getInteger(R.integer.anim_fade_enter_duration).toLong()
|
||||||
} else {
|
} else {
|
||||||
// Activated -> Hide selection indicator.
|
// Activated -> Hide selection indicator.
|
||||||
targetAlpha = 0f
|
targetAlpha = 0f
|
||||||
targetDuration =
|
targetDuration =
|
||||||
context.resources.getInteger(R.integer.anim_fade_exit_duration).toLong()
|
context.getInteger(R.integer.anim_fade_exit_duration).toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectionIndicatorView.alpha == targetAlpha) {
|
if (selectionIndicatorView.alpha == targetAlpha) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ 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.getDimenSize
|
import org.oxycblt.auxio.util.getDimenPixels
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [RecyclerView] intended for use in Dialogs, adding features such as:
|
* A [RecyclerView] intended for use in Dialogs, adding features such as:
|
||||||
|
@ -55,7 +55,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes 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.getDimenSize(R.dimen.spacing_medium)
|
private val spacingMedium = context.getDimenPixels(R.dimen.spacing_medium)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Apply top padding to give enough room to the dialog title, assuming that this view
|
// Apply top padding to give enough room to the dialog title, assuming that this view
|
||||||
|
|
|
@ -26,6 +26,7 @@ import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.util.getInteger
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,12 +117,12 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
targetInnerAlpha = 0f
|
targetInnerAlpha = 0f
|
||||||
targetSelectionAlpha = 1f
|
targetSelectionAlpha = 1f
|
||||||
targetDuration =
|
targetDuration =
|
||||||
context.resources.getInteger(R.integer.anim_fade_enter_duration).toLong()
|
context.getInteger(R.integer.anim_fade_enter_duration).toLong()
|
||||||
} else {
|
} else {
|
||||||
targetInnerAlpha = 1f
|
targetInnerAlpha = 1f
|
||||||
targetSelectionAlpha = 0f
|
targetSelectionAlpha = 0f
|
||||||
targetDuration =
|
targetDuration =
|
||||||
context.resources.getInteger(R.integer.anim_fade_exit_duration).toLong()
|
context.getInteger(R.integer.anim_fade_exit_duration).toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (innerToolbar.alpha == targetInnerAlpha &&
|
if (innerToolbar.alpha == targetInnerAlpha &&
|
||||||
|
|
|
@ -23,7 +23,7 @@ import android.provider.OpenableColumns
|
||||||
import org.oxycblt.auxio.music.MusicStore.Callback
|
import org.oxycblt.auxio.music.MusicStore.Callback
|
||||||
import org.oxycblt.auxio.music.MusicStore.Library
|
import org.oxycblt.auxio.music.MusicStore.Library
|
||||||
import org.oxycblt.auxio.music.storage.useQuery
|
import org.oxycblt.auxio.music.storage.useQuery
|
||||||
import org.oxycblt.auxio.util.contentResolverSafe
|
import org.oxycblt.auxio.music.storage.contentResolverSafe
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A repository granting access to the music library..
|
* A repository granting access to the music library..
|
||||||
|
|
|
@ -35,7 +35,7 @@ import org.oxycblt.auxio.music.storage.safeQuery
|
||||||
import org.oxycblt.auxio.music.storage.storageVolumesCompat
|
import org.oxycblt.auxio.music.storage.storageVolumesCompat
|
||||||
import org.oxycblt.auxio.music.storage.useQuery
|
import org.oxycblt.auxio.music.storage.useQuery
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.util.contentResolverSafe
|
import org.oxycblt.auxio.music.storage.contentResolverSafe
|
||||||
import org.oxycblt.auxio.util.getSystemServiceCompat
|
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,13 @@ import org.oxycblt.auxio.util.lazyReflectedMethod
|
||||||
|
|
||||||
// --- MEDIASTORE UTILITIES ---
|
// --- MEDIASTORE UTILITIES ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a content resolver that will not mangle MediaStore queries on certain devices.
|
||||||
|
* See https://github.com/OxygenCobalt/Auxio/issues/50 for more info.
|
||||||
|
*/
|
||||||
|
val Context.contentResolverSafe: ContentResolver
|
||||||
|
get() = applicationContext.contentResolver
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A shortcut for querying the [ContentResolver] database.
|
* A shortcut for querying the [ContentResolver] database.
|
||||||
* @param uri The [Uri] of content to retrieve.
|
* @param uri The [Uri] of content to retrieve.
|
||||||
|
|
|
@ -33,10 +33,10 @@ import kotlinx.coroutines.launch
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
|
import org.oxycblt.auxio.music.storage.contentResolverSafe
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.shared.ForegroundManager
|
import org.oxycblt.auxio.shared.ForegroundManager
|
||||||
import org.oxycblt.auxio.util.contentResolverSafe
|
|
||||||
import org.oxycblt.auxio.util.getSystemServiceCompat
|
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
|
||||||
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.application
|
import org.oxycblt.auxio.util.context
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ViewModel that provides a UI frontend for [PlaybackStateManager].
|
* The ViewModel that provides a UI frontend for [PlaybackStateManager].
|
||||||
|
@ -281,7 +281,7 @@ class PlaybackViewModel(application: Application) :
|
||||||
*/
|
*/
|
||||||
fun savePlaybackState(onDone: (Boolean) -> Unit) {
|
fun savePlaybackState(onDone: (Boolean) -> Unit) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val saved = playbackManager.saveState(PlaybackStateDatabase.getInstance(application))
|
val saved = playbackManager.saveState(PlaybackStateDatabase.getInstance(context))
|
||||||
onDone(saved)
|
onDone(saved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,7 +292,7 @@ class PlaybackViewModel(application: Application) :
|
||||||
*/
|
*/
|
||||||
fun wipePlaybackState(onDone: (Boolean) -> Unit) {
|
fun wipePlaybackState(onDone: (Boolean) -> Unit) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val wiped = playbackManager.wipeState(PlaybackStateDatabase.getInstance(application))
|
val wiped = playbackManager.wipeState(PlaybackStateDatabase.getInstance(context))
|
||||||
onDone(wiped)
|
onDone(wiped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -305,7 +305,7 @@ class PlaybackViewModel(application: Application) :
|
||||||
fun tryRestorePlaybackState(onDone: (Boolean) -> Unit) {
|
fun tryRestorePlaybackState(onDone: (Boolean) -> Unit) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val restored =
|
val restored =
|
||||||
playbackManager.restoreState(PlaybackStateDatabase.getInstance(application), true)
|
playbackManager.restoreState(PlaybackStateDatabase.getInstance(context), true)
|
||||||
onDone(restored)
|
onDone(restored)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.shared.AuxioBottomSheetBehavior
|
import org.oxycblt.auxio.shared.AuxioBottomSheetBehavior
|
||||||
import org.oxycblt.auxio.util.getAttrColorCompat
|
import org.oxycblt.auxio.util.getAttrColorCompat
|
||||||
import org.oxycblt.auxio.util.getDimen
|
import org.oxycblt.auxio.util.getDimen
|
||||||
import org.oxycblt.auxio.util.getDimenSize
|
import org.oxycblt.auxio.util.getDimenPixels
|
||||||
import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
|
import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
|
||||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
class QueueBottomSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) :
|
class QueueBottomSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) :
|
||||||
AuxioBottomSheetBehavior<V>(context, attributeSet) {
|
AuxioBottomSheetBehavior<V>(context, attributeSet) {
|
||||||
private var barHeight = 0
|
private var barHeight = 0
|
||||||
private var barSpacing = context.getDimenSize(R.dimen.spacing_small)
|
private var barSpacing = context.getDimenPixels(R.dimen.spacing_small)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
isHideable = false
|
isHideable = false
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.util.getInteger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [MaterialButton] that automatically morphs from a circle to a squircle shape appearance when it
|
* A [MaterialButton] that automatically morphs from a circle to a squircle shape appearance when it
|
||||||
|
@ -47,7 +48,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||||
animator?.cancel()
|
animator?.cancel()
|
||||||
animator =
|
animator =
|
||||||
ValueAnimator.ofFloat(currentCornerRadiusRatio, target).apply {
|
ValueAnimator.ofFloat(currentCornerRadiusRatio, target).apply {
|
||||||
duration = context.resources.getInteger(R.integer.anim_fade_enter_duration).toLong()
|
duration = context.getInteger(R.integer.anim_fade_enter_duration).toLong()
|
||||||
addUpdateListener { updateCornerRadiusRatio(animatedValue as Float) }
|
addUpdateListener { updateCornerRadiusRatio(animatedValue as Float) }
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.Sort
|
import org.oxycblt.auxio.music.Sort
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.util.application
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -168,7 +168,7 @@ class SearchViewModel(application: Application) :
|
||||||
// would just want to leverage CollationKey, but that is not designed for a contains
|
// would just want to leverage CollationKey, but that is not designed for a contains
|
||||||
// algorithm. If that fails, filter impls have fallback values, primarily around
|
// algorithm. If that fails, filter impls have fallback values, primarily around
|
||||||
// sort tags or file names.
|
// sort tags or file names.
|
||||||
it.resolveNameNormalized(application).contains(value, ignoreCase = true) ||
|
it.resolveNameNormalized(context).contains(value, ignoreCase = true) ||
|
||||||
fallback(it)
|
fallback(it)
|
||||||
}
|
}
|
||||||
.ifEmpty { null }
|
.ifEmpty { null }
|
||||||
|
|
|
@ -72,19 +72,14 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateStatistics(statistics: MusicViewModel.Statistics?) {
|
private fun updateStatistics(statistics: MusicViewModel.Statistics?) {
|
||||||
|
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
binding.aboutSongCount.text = getString(R.string.fmt_lib_song_count, statistics?.songs ?: 0)
|
binding.aboutSongCount.text = getString(R.string.fmt_lib_song_count, statistics?.songs ?: 0)
|
||||||
|
|
||||||
requireBinding().aboutAlbumCount.text =
|
requireBinding().aboutAlbumCount.text =
|
||||||
getString(R.string.fmt_lib_album_count, statistics?.albums ?: 0)
|
getString(R.string.fmt_lib_album_count, statistics?.albums ?: 0)
|
||||||
|
|
||||||
requireBinding().aboutArtistCount.text =
|
requireBinding().aboutArtistCount.text =
|
||||||
getString(R.string.fmt_lib_artist_count, statistics?.artists ?: 0)
|
getString(R.string.fmt_lib_artist_count, statistics?.artists ?: 0)
|
||||||
|
|
||||||
requireBinding().aboutGenreCount.text =
|
requireBinding().aboutGenreCount.text =
|
||||||
getString(R.string.fmt_lib_genre_count, statistics?.genres ?: 0)
|
getString(R.string.fmt_lib_genre_count, statistics?.genres ?: 0)
|
||||||
|
|
||||||
binding.aboutTotalDuration.text =
|
binding.aboutTotalDuration.text =
|
||||||
getString(
|
getString(
|
||||||
R.string.fmt_lib_total_duration,
|
R.string.fmt_lib_total_duration,
|
||||||
|
|
|
@ -23,7 +23,7 @@ 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.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.util.getDimenSize
|
import org.oxycblt.auxio.util.getDimenPixels
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -38,7 +38,7 @@ class AccentGridLayoutManager(
|
||||||
) : GridLayoutManager(context, attrs, defStyleAttr, defStyleRes) {
|
) : GridLayoutManager(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
// We use 56dp 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.getDimenSize(R.dimen.size_accent_item)
|
private var columnWidth = context.getDimenPixels(R.dimen.size_accent_item)
|
||||||
|
|
||||||
private var lastWidth = -1
|
private var lastWidth = -1
|
||||||
private var lastHeight = -1
|
private var lastHeight = -1
|
||||||
|
|
|
@ -28,6 +28,7 @@ import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceViewHolder
|
import androidx.preference.PreferenceViewHolder
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.util.getInteger
|
||||||
import org.oxycblt.auxio.util.lazyReflectedField
|
import org.oxycblt.auxio.util.lazyReflectedField
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,7 +72,7 @@ constructor(
|
||||||
// Additional values: offValue defines an "off" position
|
// Additional values: offValue defines an "off" position
|
||||||
val offValueId = prefAttrs.getResourceId(R.styleable.IntListPreference_offValue, -1)
|
val offValueId = prefAttrs.getResourceId(R.styleable.IntListPreference_offValue, -1)
|
||||||
if (offValueId > -1) {
|
if (offValueId > -1) {
|
||||||
offValue = context.resources.getInteger(offValueId)
|
offValue = context.getInteger(offValueId)
|
||||||
}
|
}
|
||||||
|
|
||||||
val iconsId = prefAttrs.getResourceId(R.styleable.IntListPreference_entryIcons, -1)
|
val iconsId = prefAttrs.getResourceId(R.styleable.IntListPreference_entryIcons, -1)
|
||||||
|
|
|
@ -18,12 +18,10 @@
|
||||||
package org.oxycblt.auxio.util
|
package org.oxycblt.auxio.util
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.ContentResolver
|
|
||||||
import android.content.Context
|
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.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
|
||||||
|
@ -42,65 +40,62 @@ import kotlin.reflect.KClass
|
||||||
import org.oxycblt.auxio.IntegerTable
|
import org.oxycblt.auxio.IntegerTable
|
||||||
import org.oxycblt.auxio.MainActivity
|
import org.oxycblt.auxio.MainActivity
|
||||||
|
|
||||||
/** Shortcut to get a [LayoutInflater] from a [Context] */
|
/**
|
||||||
|
* Get a [LayoutInflater] instance from this [Context].
|
||||||
|
* @see LayoutInflater.from
|
||||||
|
*/
|
||||||
val Context.inflater: LayoutInflater
|
val Context.inflater: LayoutInflater
|
||||||
get() = LayoutInflater.from(this)
|
get() = LayoutInflater.from(this)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the current UI is in night mode or not. This will work if the theme is automatic
|
* Whether the device is in night mode or not.
|
||||||
* as well.
|
|
||||||
*/
|
*/
|
||||||
val Context.isNight
|
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
|
||||||
|
|
||||||
/** Returns if this device is in landscape. */
|
/**
|
||||||
|
* Whether the device is in landscape mode or not.
|
||||||
|
*/
|
||||||
val Context.isLandscape
|
val Context.isLandscape
|
||||||
get() = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
get() = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a content resolver in a way that does not mangle metadata on certain OEM skins. See
|
* @brief Get a plural resource.
|
||||||
* https://github.com/OxygenCobalt/Auxio/issues/50 for more info.
|
* @param pluralRes A plural resource ID.
|
||||||
*/
|
|
||||||
val Context.contentResolverSafe: ContentResolver
|
|
||||||
get() = applicationContext.contentResolver
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Convenience method for getting a plural.
|
|
||||||
* @param pluralRes Resource ID for the plural
|
|
||||||
* @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.getPlural(@PluralsRes pluralRes: Int, value: Int) =
|
fun Context.getPlural(@PluralsRes pluralRes: Int, value: Int) =
|
||||||
resources.getQuantityString(pluralRes, value, value)
|
resources.getQuantityString(pluralRes, value, value)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Convenience method for obtaining an integer resource
|
* @brief Get an integer resource.
|
||||||
* @param integerRes Resource ID for the integer
|
* @param integerRes An integer resource ID.
|
||||||
* @return The integer resource requested
|
* @return The integer resource requested.
|
||||||
*/
|
*/
|
||||||
fun Context.getInteger(@IntegerRes integerRes: Int) = resources.getInteger(integerRes)
|
fun Context.getInteger(@IntegerRes integerRes: Int) = resources.getInteger(integerRes)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a [ColorStateList] resource safely.
|
* Get a [ColorStateList] resource.
|
||||||
* @param color The color resource
|
* @param colorRes A color resource ID.
|
||||||
* @return The [ColorStateList] requested
|
* @return The [ColorStateList] requested.
|
||||||
*/
|
*/
|
||||||
fun Context.getColorCompat(@ColorRes color: Int) =
|
fun Context.getColorCompat(@ColorRes colorRes: Int) =
|
||||||
requireNotNull(ContextCompat.getColorStateList(this, color)) {
|
requireNotNull(ContextCompat.getColorStateList(this, colorRes)) {
|
||||||
"Invalid resource: State list was null"
|
"Invalid resource: State list was null"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a color attribute safely.
|
* Get a [ColorStateList] pointed to by an attribute.
|
||||||
* @param attr The color attribute
|
* @param attrRes An attribute resource ID.
|
||||||
* @return The attribute requested
|
* @return The [ColorStateList] the requested attribute points to.
|
||||||
*/
|
*/
|
||||||
fun Context.getAttrColorCompat(@AttrRes attr: Int): ColorStateList {
|
fun Context.getAttrColorCompat(@AttrRes attrRes: Int): ColorStateList {
|
||||||
// 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(attrRes, resolvedAttr, true)
|
||||||
|
|
||||||
// Then convert it to a proper color
|
// Then convert it to a proper color
|
||||||
val color =
|
val color =
|
||||||
|
@ -114,31 +109,31 @@ fun Context.getAttrColorCompat(@AttrRes attr: Int): ColorStateList {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a [Drawable] safely.
|
* Get a Drawable.
|
||||||
* @param drawable The drawable resource
|
* @param drawableRes The Drawable resource ID.
|
||||||
* @return The drawable requested
|
* @return The Drawable requested.
|
||||||
*/
|
*/
|
||||||
fun Context.getDrawableCompat(@DrawableRes drawable: Int) =
|
fun Context.getDrawableCompat(@DrawableRes drawableRes: Int) =
|
||||||
requireNotNull(ContextCompat.getDrawable(this, drawable)) {
|
requireNotNull(ContextCompat.getDrawable(this, drawableRes)) {
|
||||||
"Invalid resource: Drawable was null"
|
"Invalid resource: Drawable was null"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a dimension safely.
|
* Get the complex (i.e DP) size of a dimension.
|
||||||
* @param dimen The dimension resource
|
* @param dimenRes The dimension resource.
|
||||||
* @return The dimension requested
|
* @return The size of the dimension requested, in complex units.
|
||||||
*/
|
*/
|
||||||
@Dimension fun Context.getDimen(@DimenRes dimen: Int) = resources.getDimension(dimen)
|
@Dimension fun Context.getDimen(@DimenRes dimenRes: Int) = resources.getDimension(dimenRes)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a dimension pixel size safely.
|
* Get the pixel size of a dimension.
|
||||||
* @param dimen The dimension resource
|
* @param dimenRes The dimension resource
|
||||||
* @return The dimension requested, in pixels
|
* @return The size of the dimension requested, in pixels
|
||||||
*/
|
*/
|
||||||
@Px fun Context.getDimenSize(@DimenRes dimen: Int) = resources.getDimensionPixelSize(dimen)
|
@Px fun Context.getDimenPixels(@DimenRes dimenRes: Int) = resources.getDimensionPixelSize(dimenRes)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a system service without nullability issues.
|
* Get an instance of the requested system service.
|
||||||
* @param T The system service in question.
|
* @param T The system service in question.
|
||||||
* @param serviceClass The service's kotlin class [Java class will be used in function call]
|
* @param serviceClass The service's kotlin class [Java class will be used in function call]
|
||||||
* @return The system service
|
* @return The system service
|
||||||
|
@ -149,12 +144,17 @@ fun <T : Any> Context.getSystemServiceCompat(serviceClass: KClass<T>) =
|
||||||
"System service ${serviceClass.simpleName} could not be instantiated"
|
"System service ${serviceClass.simpleName} could not be instantiated"
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a toast using the provided string resource. */
|
/**
|
||||||
fun Context.showToast(@StringRes str: Int) {
|
* Create a short-length [Toast] with text from the specified string resource.
|
||||||
Toast.makeText(applicationContext, getString(str), Toast.LENGTH_SHORT).show()
|
* @param stringRes The resource to the string to use in the toast.
|
||||||
|
*/
|
||||||
|
fun Context.showToast(@StringRes stringRes: Int) {
|
||||||
|
Toast.makeText(applicationContext, getString(stringRes), Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a [PendingIntent] that leads to Auxio's [MainActivity] */
|
/**
|
||||||
|
* Create a [PendingIntent] that will launch the app activity when launched.
|
||||||
|
*/
|
||||||
fun Context.newMainPendingIntent(): PendingIntent =
|
fun Context.newMainPendingIntent(): PendingIntent =
|
||||||
PendingIntent.getActivity(
|
PendingIntent.getActivity(
|
||||||
this,
|
this,
|
||||||
|
@ -162,10 +162,13 @@ fun Context.newMainPendingIntent(): PendingIntent =
|
||||||
Intent(this, MainActivity::class.java),
|
Intent(this, MainActivity::class.java),
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
|
||||||
|
|
||||||
/** Create a broadcast [PendingIntent] */
|
/**
|
||||||
fun Context.newBroadcastPendingIntent(what: String): PendingIntent =
|
* Create a [PendingIntent] that will broadcast the specified command when launched.
|
||||||
|
* @param action The action to broadcast when the [PendingIntent] is launched.
|
||||||
|
*/
|
||||||
|
fun Context.newBroadcastPendingIntent(action: String): PendingIntent =
|
||||||
PendingIntent.getBroadcast(
|
PendingIntent.getBroadcast(
|
||||||
this,
|
this,
|
||||||
IntegerTable.REQUEST_CODE,
|
IntegerTable.REQUEST_CODE,
|
||||||
Intent(what).setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
|
Intent(action).setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
|
||||||
|
|
|
@ -14,13 +14,13 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.util
|
package org.oxycblt.auxio.util
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.graphics.PointF
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -47,13 +47,28 @@ import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if the point given by [x] and [y] falls within this view.
|
* Get if this [View] contains the given [PointF], with optional leeway.
|
||||||
* @param minTouchTargetSize The minimum touch size, independent of the view's size (Optional)
|
* @param x The x value of the point to check.
|
||||||
|
* @param y The y value of the point to check.
|
||||||
|
* @param minTouchTargetSize A minimum size to use when checking the value.
|
||||||
|
* This can be used to extend the range where a point is considered "contained"
|
||||||
|
* by the [View] beyond it's actual size.
|
||||||
|
* @return true if the [PointF] is contained by the view, false otherwise.
|
||||||
|
* Adapted from AndroidFastScroll: https://github.com/zhanghai/AndroidFastScroll
|
||||||
*/
|
*/
|
||||||
fun View.isUnder(x: Float, y: Float, minTouchTargetSize: Int = 0) =
|
fun View.isUnder(x: Float, y: Float, minTouchTargetSize: Int = 0) =
|
||||||
isUnderImpl(x, left, right, (parent as View).width, minTouchTargetSize) &&
|
isUnderImpl(x, left, right, (parent as View).width, minTouchTargetSize) &&
|
||||||
isUnderImpl(y, top, bottom, (parent as View).height, minTouchTargetSize)
|
isUnderImpl(y, top, bottom, (parent as View).height, minTouchTargetSize)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal implementation of [isUnder].
|
||||||
|
* @param position The position to check.
|
||||||
|
* @param viewStart The start of the view bounds, on the same axis as [position].
|
||||||
|
* @param viewEnd The end of the view bounds, on the same axis as [position]
|
||||||
|
* @param parentEnd The end of the parent bounds, on the same axis as [position].
|
||||||
|
* @param minTouchTargetSize The minimum size to use when checking if the value is
|
||||||
|
* in range.
|
||||||
|
*/
|
||||||
private fun isUnderImpl(
|
private fun isUnderImpl(
|
||||||
position: Float,
|
position: Float,
|
||||||
viewStart: Int,
|
viewStart: Int,
|
||||||
|
@ -62,12 +77,11 @@ private fun isUnderImpl(
|
||||||
minTouchTargetSize: Int
|
minTouchTargetSize: Int
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val viewSize = viewEnd - viewStart
|
val viewSize = viewEnd - viewStart
|
||||||
|
|
||||||
if (viewSize >= minTouchTargetSize) {
|
if (viewSize >= minTouchTargetSize) {
|
||||||
return position >= viewStart && position < viewEnd
|
return position >= viewStart && position < viewEnd
|
||||||
}
|
}
|
||||||
var touchTargetStart = viewStart - (minTouchTargetSize - viewSize) / 2
|
|
||||||
|
|
||||||
|
var touchTargetStart = viewStart - (minTouchTargetSize - viewSize) / 2
|
||||||
if (touchTargetStart < 0) {
|
if (touchTargetStart < 0) {
|
||||||
touchTargetStart = 0
|
touchTargetStart = 0
|
||||||
}
|
}
|
||||||
|
@ -76,7 +90,6 @@ private fun isUnderImpl(
|
||||||
if (touchTargetEnd > parentEnd) {
|
if (touchTargetEnd > parentEnd) {
|
||||||
touchTargetEnd = parentEnd
|
touchTargetEnd = parentEnd
|
||||||
touchTargetStart = touchTargetEnd - minTouchTargetSize
|
touchTargetStart = touchTargetEnd - minTouchTargetSize
|
||||||
|
|
||||||
if (touchTargetStart < 0) {
|
if (touchTargetStart < 0) {
|
||||||
touchTargetStart = 0
|
touchTargetStart = 0
|
||||||
}
|
}
|
||||||
|
@ -85,66 +98,88 @@ private fun isUnderImpl(
|
||||||
return position >= touchTargetStart && position < touchTargetEnd
|
return position >= touchTargetStart && position < touchTargetEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns if this view is RTL in a compatible manner. */
|
/**
|
||||||
|
* Whether this [View] is using an RTL layout direction.
|
||||||
|
*/
|
||||||
val View.isRtl: Boolean
|
val View.isRtl: Boolean
|
||||||
get() = layoutDirection == View.LAYOUT_DIRECTION_RTL
|
get() = layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||||
|
|
||||||
/** Returns if this drawable is RTL in a compatible manner.] */
|
/**
|
||||||
|
* Whether this [Drawable] is using an RTL layout direction.
|
||||||
|
*/
|
||||||
val Drawable.isRtl: Boolean
|
val Drawable.isRtl: Boolean
|
||||||
get() = DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL
|
get() = DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL
|
||||||
|
|
||||||
/** Shortcut to get a context from a ViewBinding */
|
/**
|
||||||
|
* Get a [Context] from a [ViewBinding]'s root [View].
|
||||||
|
*/
|
||||||
val ViewBinding.context: Context
|
val ViewBinding.context: Context
|
||||||
get() = root.context
|
get() = root.context
|
||||||
|
|
||||||
/** Returns whether a recyclerview can scroll. */
|
/**
|
||||||
|
* Compute if this [RecyclerView] can scroll through their items, or if the items can all fit on
|
||||||
|
* one screen.
|
||||||
|
*/
|
||||||
fun RecyclerView.canScroll() = computeVerticalScrollRange() > height
|
fun RecyclerView.canScroll() = computeVerticalScrollRange() > height
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut to obtain the CoordinatorLayout behavior of a view. Null if not from a coordinator
|
* Get the [CoordinatorLayout.Behavior] of a [View], or null if the [View] is not part of a
|
||||||
* layout or if no behavior is present.
|
* [CoordinatorLayout] or does not have a [CoordinatorLayout.Behavior].
|
||||||
*/
|
*/
|
||||||
val View.coordinatorLayoutBehavior: CoordinatorLayout.Behavior<View>?
|
val View.coordinatorLayoutBehavior: CoordinatorLayout.Behavior<View>?
|
||||||
get() = (layoutParams as? CoordinatorLayout.LayoutParams)?.behavior
|
get() = (layoutParams as? CoordinatorLayout.LayoutParams)?.behavior
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect a [stateFlow] into [block] eventually.
|
* Collect a [StateFlow] into [block] in a lifecycle-aware manner *eventually.* Due to co-routine
|
||||||
*
|
* launching, the initializing call will occur ~100ms after draw time. If this is not desirable,
|
||||||
* This does have an initializing call, but it usually occurs ~100ms into draw-time, which might not
|
* use [collectImmediately].
|
||||||
* be ideal for some views. This should be used in cases where the state only needs to be updated
|
* @param stateFlow The [StateFlow] to collect.
|
||||||
* during runtime.
|
* @param block The code to run when the [StateFlow] updates.
|
||||||
*/
|
*/
|
||||||
fun <T> Fragment.collect(stateFlow: StateFlow<T>, block: (T) -> Unit) {
|
fun <T> Fragment.collect(stateFlow: StateFlow<T>, block: (T) -> Unit) {
|
||||||
launch { stateFlow.collect(block) }
|
launch { stateFlow.collect(block) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect a [stateFlow] into [block] immediately.
|
* Collect a [StateFlow] into a [block] in a lifecycle-aware manner *immediately.* This will
|
||||||
*
|
* immediately run an initializing call to ensure the UI is set up before draw-time. Note
|
||||||
* This method automatically calls [block] when initially starting to ensure UI state consistency at
|
* that this will result in two initializing calls.
|
||||||
* soon as the view is visible. This does nominally mean that there are two initializing
|
* @param stateFlow The [StateFlow] to collect.
|
||||||
* collections, but this is considered okay. [block] should be a function pointer in order to ensure
|
* @param block The code to run when the [StateFlow] updates.
|
||||||
* lifecycle consistency.
|
|
||||||
*
|
|
||||||
* This should be used if the state absolutely needs to be shown at draw-time.
|
|
||||||
*/
|
*/
|
||||||
fun <T> Fragment.collectImmediately(stateFlow: StateFlow<T>, block: (T) -> Unit) {
|
fun <T> Fragment.collectImmediately(stateFlow: StateFlow<T>, block: (T) -> Unit) {
|
||||||
block(stateFlow.value)
|
block(stateFlow.value)
|
||||||
launch { stateFlow.collect(block) }
|
launch { stateFlow.collect(block) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Like [collectImmediately], but with two [StateFlow] values. */
|
/**
|
||||||
|
* Like [collectImmediately], but with two [StateFlow] instances that are collected
|
||||||
|
* with the same block.
|
||||||
|
* @param a The first [StateFlow] to collect.
|
||||||
|
* @param b The second [StateFlow] to collect.
|
||||||
|
* @param block The code to run when either [StateFlow] updates.
|
||||||
|
*/
|
||||||
fun <T1, T2> Fragment.collectImmediately(
|
fun <T1, T2> Fragment.collectImmediately(
|
||||||
a: StateFlow<T1>,
|
a: StateFlow<T1>,
|
||||||
b: StateFlow<T2>,
|
b: StateFlow<T2>,
|
||||||
block: (T1, T2) -> Unit
|
block: (T1, T2) -> Unit
|
||||||
) {
|
) {
|
||||||
block(a.value, b.value)
|
block(a.value, b.value)
|
||||||
|
// We can combine flows, but only if we transform them into one flow output.
|
||||||
|
// Thus, we have to first combine the two flow values into a Pair, and then
|
||||||
|
// decompose it when we collect the values.
|
||||||
val combine = a.combine(b) { first, second -> Pair(first, second) }
|
val combine = a.combine(b) { first, second -> Pair(first, second) }
|
||||||
launch { combine.collect { block(it.first, it.second) } }
|
launch { combine.collect { block(it.first, it.second) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Like [collectImmediately], but with three [StateFlow] values. */
|
/**
|
||||||
|
* Like [collectImmediately], but with three [StateFlow] instances that are collected
|
||||||
|
* with the same block.
|
||||||
|
* @param a The first [StateFlow] to collect.
|
||||||
|
* @param b The second [StateFlow] to collect.
|
||||||
|
* @param c The third [StateFlow] to collect.
|
||||||
|
* @param block The code to run when any of the [StateFlow]s update.
|
||||||
|
*/
|
||||||
fun <T1, T2, T3> Fragment.collectImmediately(
|
fun <T1, T2, T3> Fragment.collectImmediately(
|
||||||
a: StateFlow<T1>,
|
a: StateFlow<T1>,
|
||||||
b: StateFlow<T2>,
|
b: StateFlow<T2>,
|
||||||
|
@ -157,9 +192,12 @@ fun <T1, T2, T3> Fragment.collectImmediately(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launches [block] in a lifecycle-aware coroutine once [state] is reached. This is primarily a
|
* Launch a [Fragment] co-routine whenever the [Lifecycle] hits the given [Lifecycle.State].
|
||||||
* shortcut intended to correctly launch a co-routine on a fragment in a way that won't cause
|
* This should always been used when launching [Fragment] co-routines was it will not result
|
||||||
* miscellaneous coroutine insanity.
|
* in unexpected behavior.
|
||||||
|
* @param state The [Lifecycle.State] to launch the co-routine in.
|
||||||
|
* @param block The block to run in the co-routine.
|
||||||
|
* @see repeatOnLifecycle
|
||||||
*/
|
*/
|
||||||
private fun Fragment.launch(
|
private fun Fragment.launch(
|
||||||
state: Lifecycle.State = Lifecycle.State.STARTED,
|
state: Lifecycle.State = Lifecycle.State.STARTED,
|
||||||
|
@ -169,77 +207,108 @@ private fun Fragment.launch(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut to generate a AndroidViewModel [T] without having to specify the bloated factory syntax.
|
* An extension to [viewModels] that automatically provides an
|
||||||
|
* [ViewModelProvider.AndroidViewModelFactory]. Use whenever an [AndroidViewModel]
|
||||||
|
* is used.
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : AndroidViewModel> Fragment.androidViewModels() =
|
inline fun <reified T : AndroidViewModel> Fragment.androidViewModels() =
|
||||||
viewModels<T> { ViewModelProvider.AndroidViewModelFactory(requireActivity().application) }
|
viewModels<T> { ViewModelProvider.AndroidViewModelFactory(requireActivity().application) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut to generate a AndroidViewModel [T] without having to specify the bloated factory syntax.
|
* An extension to [viewModels] that automatically provides an
|
||||||
|
* [ViewModelProvider.AndroidViewModelFactory]. Use whenever an [AndroidViewModel]
|
||||||
|
* is used. Note that this implementation is for an [AppCompatActivity], and thus
|
||||||
|
* makes this functionally equivalent in scope to [androidActivityViewModels].
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : AndroidViewModel> AppCompatActivity.androidViewModels() =
|
inline fun <reified T : AndroidViewModel> AppCompatActivity.androidViewModels() =
|
||||||
viewModels<T> { ViewModelProvider.AndroidViewModelFactory(application) }
|
viewModels<T> { ViewModelProvider.AndroidViewModelFactory(application) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut to generate a AndroidViewModel [T] without having to specify the bloated factory syntax.
|
* An extension to [activityViewModels] that automatically provides an
|
||||||
|
* [ViewModelProvider.AndroidViewModelFactory]. Use whenever an [AndroidViewModel]
|
||||||
|
* is used.
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : AndroidViewModel> Fragment.androidActivityViewModels() =
|
inline fun <reified T : AndroidViewModel> Fragment.androidActivityViewModels() =
|
||||||
activityViewModels<T> {
|
activityViewModels<T> {
|
||||||
ViewModelProvider.AndroidViewModelFactory(requireActivity().application)
|
ViewModelProvider.AndroidViewModelFactory(requireActivity().application)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Shortcut to get the [Application] from an [AndroidViewModel] */
|
/**
|
||||||
val AndroidViewModel.application: Application
|
* The [Context] provided to an [AndroidViewModel].
|
||||||
|
*/
|
||||||
|
inline val AndroidViewModel.context: Context
|
||||||
get() = getApplication()
|
get() = getApplication()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut for querying all items in a database and running [block] with the cursor returned. Will
|
* Query all columns in the given [SQLiteDatabase] table, running the block when the [Cursor]
|
||||||
* not run if the cursor is null.
|
* is loaded. The block will be called with [use], allowing for automatic cleanup of [Cursor]
|
||||||
|
* resources.
|
||||||
|
* @param tableName The name of the table to query all columns in.
|
||||||
|
* @param block The code block to run with the loaded [Cursor].
|
||||||
*/
|
*/
|
||||||
fun <R> SQLiteDatabase.queryAll(tableName: String, block: (Cursor) -> R) =
|
inline fun <R> SQLiteDatabase.queryAll(tableName: String, block: (Cursor) -> R) =
|
||||||
query(tableName, null, null, null, null, null, null)?.use(block)
|
query(tableName, null, null, null, null, null, null)?.use(block)
|
||||||
|
|
||||||
// Note: WindowInsetsCompat and it's related methods cause too many issues.
|
|
||||||
// Use our own compat methods instead.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve system bar insets in a version-aware manner. This can be used to apply padding to a view
|
* Get the "System Bar" [Insets] in this [WindowInsets] instance in a version-compatible manner
|
||||||
* that properly follows all the changes that were made between Android 8-11.
|
* This can be used to prevent [View] elements from intersecting with the navigation bars.
|
||||||
*/
|
*/
|
||||||
val WindowInsets.systemBarInsetsCompat: Insets
|
val WindowInsets.systemBarInsetsCompat: Insets
|
||||||
get() =
|
get() =
|
||||||
when {
|
when {
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||||
|
// API 30+, use window inset map.
|
||||||
getCompatInsets(WindowInsets.Type.systemBars())
|
getCompatInsets(WindowInsets.Type.systemBars())
|
||||||
}
|
}
|
||||||
|
// API 21+, use window inset fields.
|
||||||
else -> getSystemWindowCompatInsets()
|
else -> getSystemWindowCompatInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve gesture insets in a version-aware manner. This can be used to apply padding to a view
|
* Get the "System Gesture" [Insets] in this [WindowInsets] instance in a version-compatible manner
|
||||||
* that properly follows all the changes that were made between Android 8-11. Note that if the
|
* This can be used to prevent [View] elements from intersecting with the navigation bars and
|
||||||
* gesture insets are not present (i.e zeroed), the bar insets will be used instead.
|
* their extended gesture hit-boxes. Note that "System Bar" insets will be used if the system
|
||||||
|
* does not provide gesture insets.
|
||||||
*/
|
*/
|
||||||
val WindowInsets.systemGestureInsetsCompat: Insets
|
val WindowInsets.systemGestureInsetsCompat: Insets
|
||||||
get() =
|
get() =
|
||||||
// The reasoning for why we take a larger inset is because gesture insets are seemingly
|
// Some android versions seemingly don't provide gesture insets, setting them to zero.
|
||||||
// not present on some android versions. To prevent the app from appearing below the
|
// To resolve this, we take the maximum between the system bar and system gesture
|
||||||
// system bars, we fall back to the bar insets. This is guaranteed not to fire in any
|
// insets. Since system gesture insets should extend further than system bar insets,
|
||||||
// context but the gesture insets being invalid, as gesture insets are intended to
|
// this should allow this code to fall back to system bar insets easily if the system
|
||||||
// be larger than bar insets.
|
// does not provide system gesture insets. This does require androidx Insets to allow
|
||||||
|
// us to use the max method on all versions however, so we will want to convert the
|
||||||
|
// system-provided insets to such..
|
||||||
when {
|
when {
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||||
|
// API 30+, use window inset map.
|
||||||
Insets.max(
|
Insets.max(
|
||||||
getCompatInsets(WindowInsets.Type.systemGestures()),
|
getCompatInsets(WindowInsets.Type.systemGestures()),
|
||||||
getCompatInsets(WindowInsets.Type.systemBars()))
|
getCompatInsets(WindowInsets.Type.systemBars()))
|
||||||
}
|
}
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
|
||||||
@Suppress("DEPRECATION")
|
// API 29, use window inset fields.
|
||||||
Insets.max(getSystemGestureCompatInsets(), getSystemWindowCompatInsets())
|
Insets.max(getSystemGestureCompatInsets(), getSystemWindowCompatInsets())
|
||||||
}
|
}
|
||||||
|
// API 21+ do not support gesture insets, as they don't have gesture navigation.
|
||||||
|
// Just use system bar insets.
|
||||||
else -> getSystemWindowCompatInsets()
|
else -> getSystemWindowCompatInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the given [Insets] based on the to the API 30+ [WindowInsets] convention.
|
||||||
|
* @param typeMask The type of [Insets] to obtain.
|
||||||
|
* @return Compat [Insets] corresponding to the given type.
|
||||||
|
* @see WindowInsets.getInsets
|
||||||
|
*/
|
||||||
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
|
private fun WindowInsets.getCompatInsets(typeMask: Int) = Insets.toCompatInsets(getInsets(typeMask))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns "System Bar" [Insets] based on the API 21+ [WindowInsets] convention.
|
||||||
|
* @return Compat [Insets] consisting of the [WindowInsets] "System Bar" [Insets] field.
|
||||||
|
* @see WindowInsets.getSystemWindowInsets
|
||||||
|
*/
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
private fun WindowInsets.getSystemWindowCompatInsets() =
|
private fun WindowInsets.getSystemWindowCompatInsets() =
|
||||||
Insets.of(
|
Insets.of(
|
||||||
|
@ -248,16 +317,22 @@ private fun WindowInsets.getSystemWindowCompatInsets() =
|
||||||
systemWindowInsetRight,
|
systemWindowInsetRight,
|
||||||
systemWindowInsetBottom)
|
systemWindowInsetBottom)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns "System Bar" [Insets] based on the API 29 [WindowInsets] convention.
|
||||||
|
* @return Compat [Insets] consisting of the [WindowInsets] "System Gesture" [Insets] fields.
|
||||||
|
* @see WindowInsets.getSystemGestureInsets
|
||||||
|
*/
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
private fun WindowInsets.getSystemGestureCompatInsets() = Insets.toCompatInsets(systemGestureInsets)
|
private fun WindowInsets.getSystemGestureCompatInsets() = Insets.toCompatInsets(systemGestureInsets)
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
|
||||||
private fun WindowInsets.getCompatInsets(typeMask: Int) = Insets.toCompatInsets(getInsets(typeMask))
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the system bar insets in a version-aware manner. This can be used to modify the insets
|
* Replace the "System Bar" [Insets] in [WindowInsets] with a new set of [Insets].
|
||||||
* for child views in a way that follows all of the changes that were made between 8-11.
|
* @param left The new left inset.
|
||||||
|
* @param top The new top inset.
|
||||||
|
* @param right The new right inset.
|
||||||
|
* @param bottom The new bottom inset.
|
||||||
|
* @return A new [WindowInsets] with the given "System Bar" inset values applied.
|
||||||
*/
|
*/
|
||||||
fun WindowInsets.replaceSystemBarInsetsCompat(
|
fun WindowInsets.replaceSystemBarInsetsCompat(
|
||||||
left: Int,
|
left: Int,
|
||||||
|
@ -267,6 +342,7 @@ fun WindowInsets.replaceSystemBarInsetsCompat(
|
||||||
): WindowInsets {
|
): WindowInsets {
|
||||||
return when {
|
return when {
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||||
|
// API 30+, use a Builder to create a new instance.
|
||||||
WindowInsets.Builder(this)
|
WindowInsets.Builder(this)
|
||||||
.setInsets(
|
.setInsets(
|
||||||
WindowInsets.Type.systemBars(),
|
WindowInsets.Type.systemBars(),
|
||||||
|
@ -274,6 +350,7 @@ fun WindowInsets.replaceSystemBarInsetsCompat(
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
// API 21+, replace the system bar inset fields.
|
||||||
@Suppress("DEPRECATION") replaceSystemWindowInsets(left, top, right, bottom)
|
@Suppress("DEPRECATION") replaceSystemWindowInsets(left, top, right, bottom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,21 +18,13 @@
|
||||||
package org.oxycblt.auxio.util
|
package org.oxycblt.auxio.util
|
||||||
|
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import java.lang.reflect.Field
|
|
||||||
import java.lang.reflect.Method
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
|
|
||||||
/** Assert that we are on a background thread. */
|
|
||||||
fun requireBackgroundThread() {
|
|
||||||
check(Looper.myLooper() != Looper.getMainLooper()) {
|
|
||||||
"This operation must be ran on a background thread"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitizes a nullable value that is not likely to be null. On debug builds, requireNotNull is
|
* Sanitizes a value that is unlikely to be null. On debug builds, this aliases to [requireNotNull],
|
||||||
* used, while on release builds, the unsafe assertion operator [!!] ]is used
|
* otherwise, it aliases to the unchecked dereference operator (!!). This can be used as a minor
|
||||||
|
* optimization in certain cases.
|
||||||
*/
|
*/
|
||||||
fun <T> unlikelyToBeNull(value: T?) =
|
fun <T> unlikelyToBeNull(value: T?) =
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
|
@ -41,21 +33,52 @@ fun <T> unlikelyToBeNull(value: T?) =
|
||||||
value!!
|
value!!
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns null if this value is 0. */
|
/**
|
||||||
|
* Aliases a check to ensure that the given number is non-zero.
|
||||||
|
* @return The given number if it's non-zero, null otherwise.
|
||||||
|
*/
|
||||||
fun Int.nonZeroOrNull() = if (this > 0) this else null
|
fun Int.nonZeroOrNull() = if (this > 0) this else null
|
||||||
|
|
||||||
/** Returns null if this value is 0. */
|
/**
|
||||||
|
* Aliases a check to ensure that the given number is non-zero.
|
||||||
|
* @return The same number if it's non-zero, null otherwise.
|
||||||
|
*/
|
||||||
fun Long.nonZeroOrNull() = if (this > 0) this else null
|
fun Long.nonZeroOrNull() = if (this > 0) this else null
|
||||||
|
|
||||||
/** Returns null if this value is not in [range]. */
|
/**
|
||||||
|
* Aliases a check to ensure a given value is in a specified range.
|
||||||
|
* @param range The valid range of values for this number.
|
||||||
|
* @return The same number if it is in the range, null otherwise.
|
||||||
|
*/
|
||||||
fun Int.inRangeOrNull(range: IntRange) = if (range.contains(this)) this else null
|
fun Int.inRangeOrNull(range: IntRange) = if (range.contains(this)) this else null
|
||||||
|
|
||||||
/** Lazily reflect to retrieve a [Field]. */
|
/**
|
||||||
|
* Lazily set up a reflected field. Automatically handles visibility changes.
|
||||||
|
* Adapted from Material Files: https://github.com/zhanghai/MaterialFiles
|
||||||
|
* @param clazz The [KClass] to reflect into.
|
||||||
|
* @param field The name of the field to obtain.
|
||||||
|
*/
|
||||||
fun lazyReflectedField(clazz: KClass<*>, field: String) = lazy {
|
fun lazyReflectedField(clazz: KClass<*>, field: String) = lazy {
|
||||||
clazz.java.getDeclaredField(field).also { it.isAccessible = true }
|
clazz.java.getDeclaredField(field).also { it.isAccessible = true }
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
/** Lazily reflect to retrieve a [Method]. */
|
* Lazily set up a reflected method. Automatically handles visibility changes.
|
||||||
|
* Adapted from Material Files: https://github.com/zhanghai/MaterialFiles
|
||||||
|
* @param clazz The [KClass] to reflect into.
|
||||||
|
* @param field The name of the method to obtain.
|
||||||
|
*/
|
||||||
fun lazyReflectedMethod(clazz: KClass<*>, method: String) = lazy {
|
fun lazyReflectedMethod(clazz: KClass<*>, method: String) = lazy {
|
||||||
clazz.java.getDeclaredMethod(method).also { it.isAccessible = true }
|
clazz.java.getDeclaredMethod(method).also { it.isAccessible = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the execution is currently on a background thread. This is helpful for
|
||||||
|
* functions that don't necessarily require suspend, but still want to ensure that they
|
||||||
|
* are being called with a co-routine.
|
||||||
|
* @throws IllegalStateException If the execution is not on a background thread.
|
||||||
|
*/
|
||||||
|
fun requireBackgroundThread() {
|
||||||
|
check(Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
|
"This operation must be ran on a background thread"
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,14 +24,14 @@ import org.oxycblt.auxio.BuildConfig
|
||||||
// Yes, I know timber exists but this does what I need.
|
// Yes, I know timber exists but this does what I need.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut method for logging a non-string [obj] to debug. Should only be used for debug
|
* Log an object to the debug channel. Automatically handles tags.
|
||||||
* preferably.
|
* @param obj The object to log.
|
||||||
*/
|
*/
|
||||||
fun Any.logD(obj: Any?) = logD("$obj")
|
fun Any.logD(obj: Any?) = logD("$obj")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut method for logging [msg] to the debug console. Handles debug builds and anonymous
|
* Log a string message to the debug channel. Automatically handles tags.
|
||||||
* objects
|
* @param msg The message to log.
|
||||||
*/
|
*/
|
||||||
fun Any.logD(msg: String) {
|
fun Any.logD(msg: String) {
|
||||||
if (BuildConfig.DEBUG && !copyleftNotice()) {
|
if (BuildConfig.DEBUG && !copyleftNotice()) {
|
||||||
|
@ -39,19 +39,28 @@ fun Any.logD(msg: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Shortcut method for logging [msg] as a warning to the console. Handles anonymous objects */
|
/**
|
||||||
|
* Log a string message to the warning channel. Automatically handles tags.
|
||||||
|
* @param msg The message to log.
|
||||||
|
*/
|
||||||
fun Any.logW(msg: String) = Log.w(autoTag, msg)
|
fun Any.logW(msg: String) = Log.w(autoTag, msg)
|
||||||
|
|
||||||
/** Shortcut method for logging [msg] as an error to the console. Handles anonymous objects */
|
/**
|
||||||
|
* Log a string message to the error channel. Automatically handles tags.
|
||||||
|
* @param msg The message to log.
|
||||||
|
*/
|
||||||
fun Any.logE(msg: String) = Log.e(autoTag, msg)
|
fun Any.logE(msg: String) = Log.e(autoTag, msg)
|
||||||
|
|
||||||
/** Automatically creates a tag that identifies the object currently logging. */
|
/**
|
||||||
|
* The LogCat-suitable tag for this string. Consists of the object's name, or "Anonymous Object"
|
||||||
|
* if the object does not exist.
|
||||||
|
*/
|
||||||
private val Any.autoTag: String
|
private val Any.autoTag: String
|
||||||
get() = "Auxio.${this::class.simpleName ?: "Anonymous Object"}"
|
get() = "Auxio.${this::class.simpleName ?: "Anonymous Object"}"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Please don't plagiarize Auxio! You are free to remove this as long as you continue to keep your
|
* Please don't plagiarize Auxio!
|
||||||
* source open.
|
* You are free to remove this as long as you continue to keep your source open.
|
||||||
*/
|
*/
|
||||||
@Suppress("KotlinConstantConditions")
|
@Suppress("KotlinConstantConditions")
|
||||||
private fun copyleftNotice(): Boolean {
|
private fun copyleftNotice(): Boolean {
|
||||||
|
@ -61,9 +70,7 @@ private fun copyleftNotice(): Boolean {
|
||||||
"Auxio Project",
|
"Auxio Project",
|
||||||
"Friendly reminder: Auxio is licensed under the " +
|
"Friendly reminder: Auxio is licensed under the " +
|
||||||
"GPLv3 and all derivative apps must be made open source!")
|
"GPLv3 and all derivative apps must be made open source!")
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ import org.oxycblt.auxio.playback.state.InternalPlayer
|
||||||
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.getDimenSize
|
import org.oxycblt.auxio.util.getDimenPixels
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,10 +84,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.getDimenSize(android.R.dimen.system_app_widget_inner_radius)
|
context.getDimenPixels(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.getDimenSize(R.dimen.size_corners_medium)
|
context.getDimenPixels(R.dimen.size_corners_medium)
|
||||||
} else {
|
} else {
|
||||||
// User did not enable round mode.
|
// User did not enable round mode.
|
||||||
0
|
0
|
||||||
|
|
Loading…
Reference in a new issue