Change file intent navigation structure
When a file intent is given, navigate to PlaybackFragment directly.
This commit is contained in:
parent
cca80c65eb
commit
4efbab8b05
9 changed files with 86 additions and 94 deletions
|
@ -19,9 +19,9 @@ import org.oxycblt.auxio.detail.DetailViewModel
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
|
import org.oxycblt.auxio.playback.shouldHandleFileIntent
|
||||||
import org.oxycblt.auxio.ui.Accent
|
import org.oxycblt.auxio.ui.Accent
|
||||||
import org.oxycblt.auxio.ui.fixAnimInfoLeak
|
import org.oxycblt.auxio.ui.fixAnimInfoLeak
|
||||||
import org.oxycblt.auxio.ui.handleFileIntent
|
|
||||||
import org.oxycblt.auxio.ui.isLandscape
|
import org.oxycblt.auxio.ui.isLandscape
|
||||||
import org.oxycblt.auxio.ui.isTablet
|
import org.oxycblt.auxio.ui.isTablet
|
||||||
import org.oxycblt.auxio.ui.toColor
|
import org.oxycblt.auxio.ui.toColor
|
||||||
|
@ -119,7 +119,14 @@ class MainFragment : Fragment() {
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
if (!handleFileIntent(playbackModel)) {
|
// File intents chain-navigate towards PlaybackFragment
|
||||||
|
// Not for any express purpose, I just prefer it this way.
|
||||||
|
if (shouldHandleFileIntent()) {
|
||||||
|
findNavController().navigate(
|
||||||
|
MainFragmentDirections.actionGoToPlayback()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// If there is no file intent restore playback as usual
|
||||||
playbackModel.restorePlaybackIfNeeded(requireContext())
|
playbackModel.restorePlaybackIfNeeded(requireContext())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,15 +70,15 @@ class MusicLinker(
|
||||||
logD("Linking genres")
|
logD("Linking genres")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Okay, I'm going to go on a bit of a tangent here, but why the hell do I have do this?
|
* Okay, I'm going to go on a bit of a tangent here because this bit of code infuriates me.
|
||||||
* In an ideal world I should just be able to write MediaStore.Media.Audio.GENRE in the
|
*
|
||||||
* original song projection and then have it fetch the genre from the database, but no,
|
* In an ideal world I should just be able to write MediaStore.Media.Audio.GENRE
|
||||||
* why would ANYONE do that? Instead, I have to manually PROJECT EACH GENRE, get their
|
* in the original song projection and then have it fetch the genre from the database, but
|
||||||
* song ids, and then waste CPU cycles REPEATEDLY ITERATING through the songs list
|
* no, why would ANYONE do that? Instead, I have to manually iterate through each genre, get
|
||||||
* to LINK SONG WITH THEIR GENRE. I bet the google dev who built this busted system
|
* A LIST OF SONGS FROM THEM, and then waste CPU cycles REPEATEDLY ITERATING through the
|
||||||
* feels REALLY happy about the promotion they likely got from rushing out another
|
* songs list to LINK EACH SONG WITH THEIR GENRE. Why is it this way? Nobody knows! Now this
|
||||||
* android API that quickly rots from the basic act of existing, because now this quirk
|
* quirk is immortalized and has to be replicated in all future iterations of the API!
|
||||||
* is immortalized and has to be replicated to be backwards compatible! Thanks for nothing!
|
* Yay! I hate this platform so much.
|
||||||
*/
|
*/
|
||||||
genres.forEach { genre ->
|
genres.forEach { genre ->
|
||||||
val songCursor = resolver.query(
|
val songCursor = resolver.query(
|
||||||
|
|
|
@ -8,7 +8,7 @@ import android.util.AttributeSet
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.widget.AppCompatImageButton
|
import androidx.appcompat.widget.AppCompatImageButton
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.ui.getAnimatedDrawable
|
import org.oxycblt.auxio.ui.toAnimDrawable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom [AppCompatImageButton] that handles the animated play/pause icons.
|
* Custom [AppCompatImageButton] that handles the animated play/pause icons.
|
||||||
|
@ -18,8 +18,8 @@ class PlayPauseButton @JvmOverloads constructor(
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = -1
|
defStyleAttr: Int = -1
|
||||||
) : AppCompatImageButton(context, attrs, defStyleAttr) {
|
) : AppCompatImageButton(context, attrs, defStyleAttr) {
|
||||||
private val iconPauseToPlay = context.getAnimatedDrawable(R.drawable.ic_pause_to_play)
|
private val iconPauseToPlay = R.drawable.ic_pause_to_play.toAnimDrawable(context)
|
||||||
private val iconPlayToPause = context.getAnimatedDrawable(R.drawable.ic_play_to_pause)
|
private val iconPlayToPause = R.drawable.ic_play_to_pause.toAnimDrawable(context)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
|
|
@ -17,8 +17,8 @@ import org.oxycblt.auxio.detail.DetailViewModel
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.playback.state.LoopMode
|
import org.oxycblt.auxio.playback.state.LoopMode
|
||||||
import org.oxycblt.auxio.ui.Accent
|
import org.oxycblt.auxio.ui.Accent
|
||||||
import org.oxycblt.auxio.ui.handleFileIntent
|
|
||||||
import org.oxycblt.auxio.ui.memberBinding
|
import org.oxycblt.auxio.ui.memberBinding
|
||||||
|
import org.oxycblt.auxio.ui.toDrawable
|
||||||
import org.oxycblt.auxio.ui.toStateList
|
import org.oxycblt.auxio.ui.toStateList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,13 +53,8 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||||
val normalTextColor = binding.playbackDurationCurrent.currentTextColor
|
val normalTextColor = binding.playbackDurationCurrent.currentTextColor
|
||||||
|
|
||||||
// Can't set the tint of a MenuItem below Android 8, so use icons instead.
|
// Can't set the tint of a MenuItem below Android 8, so use icons instead.
|
||||||
val iconQueueActive = ContextCompat.getDrawable(
|
val iconQueueActive = R.drawable.ic_queue.toDrawable(requireContext())
|
||||||
requireContext(), R.drawable.ic_queue
|
val iconQueueInactive = R.drawable.ic_queue_inactive.toDrawable(requireContext())
|
||||||
)
|
|
||||||
|
|
||||||
val iconQueueInactive = ContextCompat.getDrawable(
|
|
||||||
requireContext(), R.drawable.ic_queue_inactive
|
|
||||||
)
|
|
||||||
|
|
||||||
val queueMenuItem: MenuItem
|
val queueMenuItem: MenuItem
|
||||||
|
|
||||||
|
@ -202,7 +197,14 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||||
playbackModel.enableAnimation()
|
playbackModel.enableAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFileIntent(playbackModel)
|
if (shouldHandleFileIntent()) {
|
||||||
|
val intent = requireActivity().intent
|
||||||
|
|
||||||
|
// Ensure that this wont fire again by putting a boolean extra
|
||||||
|
intent.putExtra(PlaybackUtils.KEY_INTENT_FIRED, true)
|
||||||
|
|
||||||
|
playbackModel.playWithIntent(intent, requireContext())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- SEEK CALLBACKS ---
|
// --- SEEK CALLBACKS ---
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.oxycblt.auxio.playback
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
|
||||||
|
object PlaybackUtils {
|
||||||
|
const val KEY_INTENT_FIRED = "KEY_FILE_INTENT_FIRED"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current activity intent is a file intent and needs to be dealt with.
|
||||||
|
*/
|
||||||
|
fun Fragment.shouldHandleFileIntent(): Boolean {
|
||||||
|
val intent = requireActivity().intent
|
||||||
|
|
||||||
|
return intent != null &&
|
||||||
|
intent.action == Intent.ACTION_VIEW &&
|
||||||
|
!intent.getBooleanExtra(PlaybackUtils.KEY_INTENT_FIRED, false)
|
||||||
|
}
|
|
@ -160,12 +160,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||||
|
|
||||||
/** Play a song using an intent */
|
/** Play a song using an intent */
|
||||||
fun playWithIntent(intent: Intent, context: Context) {
|
fun playWithIntent(intent: Intent, context: Context) {
|
||||||
val fired = intent.getBooleanExtra(KEY_INTENT_PLAY_FIRED, false)
|
|
||||||
|
|
||||||
if (!fired) {
|
|
||||||
// Make sure any intents that are passed here never fire again
|
|
||||||
intent.putExtra(KEY_INTENT_PLAY_FIRED, true)
|
|
||||||
|
|
||||||
val uri = intent.data ?: return
|
val uri = intent.data ?: return
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
@ -174,7 +168,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// --- POSITION FUNCTIONS ---
|
// --- POSITION FUNCTIONS ---
|
||||||
|
|
||||||
|
@ -427,8 +420,4 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||||
override fun onInUserQueueUpdate(isInUserQueue: Boolean) {
|
override fun onInUserQueueUpdate(isInUserQueue: Boolean) {
|
||||||
mIsInUserQueue.value = isInUserQueue
|
mIsInUserQueue.value = isInUserQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val KEY_INTENT_PLAY_FIRED = "KEY_PLAY_DONE"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ import org.oxycblt.auxio.music.BaseModel
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Header
|
import org.oxycblt.auxio.music.Header
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
|
import org.oxycblt.auxio.playback.shouldHandleFileIntent
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||||
import org.oxycblt.auxio.ui.handleFileIntent
|
|
||||||
import org.oxycblt.auxio.ui.isEdgeOn
|
import org.oxycblt.auxio.ui.isEdgeOn
|
||||||
import org.oxycblt.auxio.ui.isIrregularLandscape
|
import org.oxycblt.auxio.ui.isIrregularLandscape
|
||||||
|
|
||||||
|
@ -102,7 +102,9 @@ class QueueFragment : Fragment() {
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
handleFileIntent(playbackModel)
|
if (shouldHandleFileIntent()) {
|
||||||
|
findNavController().navigateUp()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||||
* @see ActionMenu
|
* @see ActionMenu
|
||||||
*/
|
*/
|
||||||
fun Fragment.newMenu(anchor: View, data: BaseModel, flag: Int = ActionMenu.FLAG_NONE) {
|
fun Fragment.newMenu(anchor: View, data: BaseModel, flag: Int = ActionMenu.FLAG_NONE) {
|
||||||
ActionMenu(requireCompatActivity(), anchor, data, flag).show()
|
ActionMenu(requireActivity() as AppCompatActivity, anchor, data, flag).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,12 +2,12 @@ package org.oxycblt.auxio.ui
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
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.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Point
|
import android.graphics.Point
|
||||||
import android.graphics.drawable.AnimatedVectorDrawable
|
import android.graphics.drawable.AnimatedVectorDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -27,7 +27,6 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
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.logE
|
import org.oxycblt.auxio.logE
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
|
||||||
|
|
||||||
// --- VIEW CONFIGURATION ---
|
// --- VIEW CONFIGURATION ---
|
||||||
|
|
||||||
|
@ -65,6 +64,11 @@ fun MaterialButton.applyAccents(highlighted: Boolean) {
|
||||||
|
|
||||||
// --- CONVENIENCE ---
|
// --- CONVENIENCE ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut to get a [LayoutInflater] from a [Context]
|
||||||
|
*/
|
||||||
|
val Context.inflater: LayoutInflater get() = LayoutInflater.from(this)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a plural.
|
* Convenience method for getting a plural.
|
||||||
* @param pluralsRes Resource for the plural
|
* @param pluralsRes Resource for the plural
|
||||||
|
@ -75,40 +79,6 @@ fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String {
|
||||||
return resources.getQuantityString(pluralsRes, value, value)
|
return resources.getQuantityString(pluralsRes, value, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut to get a [LayoutInflater] from a [Context]
|
|
||||||
*/
|
|
||||||
val Context.inflater: LayoutInflater get() = LayoutInflater.from(this)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut to get an [AnimatedVectorDrawable] from a [Context]
|
|
||||||
*/
|
|
||||||
fun Context.getAnimatedDrawable(@DrawableRes drawableRes: Int): AnimatedVectorDrawable {
|
|
||||||
return ContextCompat.getDrawable(this, drawableRes) as AnimatedVectorDrawable
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [Toast] from a [String]
|
|
||||||
* @param context [Context] required to create the toast
|
|
||||||
*/
|
|
||||||
fun String.createToast(context: Context) {
|
|
||||||
Toast.makeText(context.applicationContext, this, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure that a not-null [AppCompatActivity] will be returned.
|
|
||||||
* @throws IllegalStateException When there is no [AppCompatActivity] or if the activity is null
|
|
||||||
*/
|
|
||||||
fun Fragment.requireCompatActivity(): AppCompatActivity {
|
|
||||||
val activity = requireActivity()
|
|
||||||
|
|
||||||
if (activity is AppCompatActivity) {
|
|
||||||
return activity
|
|
||||||
} else {
|
|
||||||
error("Required AppCompatActivity, got ${activity::class.simpleName} instead.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a color.
|
* Resolve a color.
|
||||||
* @param context [Context] required
|
* @param context [Context] required
|
||||||
|
@ -132,7 +102,26 @@ fun Int.toColor(context: Context): Int {
|
||||||
* @return The resolved color as a [ColorStateList]
|
* @return The resolved color as a [ColorStateList]
|
||||||
* @see toColor
|
* @see toColor
|
||||||
*/
|
*/
|
||||||
fun Int.toStateList(context: Context): ColorStateList = ColorStateList.valueOf(toColor(context))
|
fun Int.toStateList(context: Context) = ColorStateList.valueOf(toColor(context))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a drawable resource into a [Drawable]
|
||||||
|
*/
|
||||||
|
fun Int.toDrawable(context: Context) = ContextCompat.getDrawable(context, this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a drawable resource into an [AnimatedVectorDrawable]
|
||||||
|
* @see toDrawable
|
||||||
|
*/
|
||||||
|
fun Int.toAnimDrawable(context: Context) = toDrawable(context) as AnimatedVectorDrawable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [Toast] from a [String]
|
||||||
|
* @param context [Context] required to create the toast
|
||||||
|
*/
|
||||||
|
fun String.createToast(context: Context) {
|
||||||
|
Toast.makeText(context.applicationContext, this, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
// --- CONFIGURATION ---
|
// --- CONFIGURATION ---
|
||||||
|
|
||||||
|
@ -243,19 +232,3 @@ fun Fragment.fixAnimInfoLeak() {
|
||||||
logE("mAnimationInfo leak fix failed.")
|
logE("mAnimationInfo leak fix failed.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for handling a file intent.
|
|
||||||
* @return True if the file intent was pushed to [playbackModel], false if not
|
|
||||||
*/
|
|
||||||
fun Fragment.handleFileIntent(playbackModel: PlaybackViewModel): Boolean {
|
|
||||||
val intent = requireActivity().intent
|
|
||||||
|
|
||||||
if (intent != null && intent.action == Intent.ACTION_VIEW) {
|
|
||||||
playbackModel.playWithIntent(intent, requireContext())
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue