Refactor file intent system

Finally find a way to handle file intents without having to coordinate it across three different fragments in onResume.
This commit is contained in:
OxygenCobalt 2021-02-23 14:04:45 -07:00
parent f04ffdb59b
commit 7f0042fd2f
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 96 additions and 110 deletions

View file

@ -5,10 +5,12 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.WindowInsets import android.view.WindowInsets
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import org.oxycblt.auxio.databinding.ActivityMainBinding import org.oxycblt.auxio.databinding.ActivityMainBinding
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.system.PlaybackService import org.oxycblt.auxio.playback.system.PlaybackService
import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.Accent import org.oxycblt.auxio.ui.Accent
@ -18,6 +20,8 @@ import org.oxycblt.auxio.ui.isEdgeOn
* The single [AppCompatActivity] for Auxio. * The single [AppCompatActivity] for Auxio.
*/ */
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private val playbackModel: PlaybackViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -34,6 +38,9 @@ class MainActivity : AppCompatActivity() {
// Apply the theme // Apply the theme
setTheme(accent.theme) setTheme(accent.theme)
// onNewIntent doesnt automatically call on startup, so call it here.
onNewIntent(intent)
if (isEdgeOn()) { if (isEdgeOn()) {
doEdgeToEdgeSetup(binding) doEdgeToEdgeSetup(binding)
} }
@ -49,20 +56,28 @@ class MainActivity : AppCompatActivity() {
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
// Since the activity is set to singleInstance [Given that there's only MainActivity] if (intent != null) {
// We have to manually push the intent whenever we get one so that the fragments val action = intent.action
// can catch any file intents val isConsumed = intent.getBooleanExtra(KEY_INTENT_CONSUMED, false)
setIntent(intent)
if (action == Intent.ACTION_VIEW && !isConsumed) {
// Mark the intent as used so this does not fire again
intent.putExtra(KEY_INTENT_CONSUMED, true)
intent.data?.let { fileUri ->
playbackModel.playWithUri(fileUri, this)
}
}
}
} }
@Suppress("DEPRECATION")
private fun doEdgeToEdgeSetup(binding: ActivityMainBinding) { private fun doEdgeToEdgeSetup(binding: ActivityMainBinding) {
window?.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Do modern edge to edge [Which is really a shot in the dark tbh] // Do modern edge to edge, which happens to be around twice the size of the
this@MainActivity.logD("Doing R+ edge-to-edge.") // old way of doing things. Thanks android, very cool!
logD("Doing R+ edge-to-edge.")
setDecorFitsSystemWindows(false) window?.setDecorFitsSystemWindows(false)
binding.root.setOnApplyWindowInsetsListener { _, insets -> binding.root.setOnApplyWindowInsetsListener { _, insets ->
WindowInsets.Builder() WindowInsets.Builder()
@ -73,12 +88,16 @@ class MainActivity : AppCompatActivity() {
.build() .build()
} }
} else { } else {
// Do old edge-to-edge otherwise // Do old edge-to-edge otherwise.
this@MainActivity.logD("Doing legacy edge-to-edge.") logD("Doing legacy edge-to-edge.")
@Suppress("DEPRECATION")
binding.root.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or binding.root.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE View.SYSTEM_UI_FLAG_LAYOUT_STABLE
} }
} }
companion object {
private const val KEY_INTENT_CONSUMED = "KEY_FILE_INTENT_USED"
} }
} }

View file

@ -19,8 +19,6 @@ 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.handleFileIntent
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.isLandscape import org.oxycblt.auxio.ui.isLandscape
@ -112,22 +110,13 @@ class MainFragment : Fragment() {
} }
} }
playbackModel.setupPlayback(requireContext())
logD("Fragment Created.") logD("Fragment Created.")
return binding.root return binding.root
} }
override fun onResume() {
super.onResume()
if (shouldHandleFileIntent()) {
handleFileIntent(playbackModel)
} else {
// If there is no file intent restore playback as usual
playbackModel.restorePlaybackIfNeeded(requireContext())
}
}
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()

View file

@ -38,6 +38,7 @@ class CompactPlaybackFragment : Fragment() {
// Put a placeholder song in the binding & hide the playback fragment initially. // Put a placeholder song in the binding & hide the playback fragment initially.
binding.song = MusicStore.getInstance().songs[0] binding.song = MusicStore.getInstance().songs[0]
binding.playbackModel = playbackModel binding.playbackModel = playbackModel
binding.executePendingBindings()
binding.root.apply { binding.root.apply {
setOnClickListener { setOnClickListener {
@ -54,12 +55,12 @@ class CompactPlaybackFragment : Fragment() {
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
playbackModel.song.observe(viewLifecycleOwner) { playbackModel.song.observe(viewLifecycleOwner) { song ->
if (it != null) { if (song != null) {
logD("Updating song display to ${it.name}") logD("Updating song display to ${song.name}")
binding.song = it binding.song = song
binding.playbackProgress.max = it.seconds.toInt() binding.playbackProgress.max = song.seconds.toInt()
} }
} }

View file

@ -82,12 +82,12 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
// --- VIEWMODEL SETUP -- // --- VIEWMODEL SETUP --
playbackModel.song.observe(viewLifecycleOwner) { playbackModel.song.observe(viewLifecycleOwner) { song ->
if (it != null) { if (song != null) {
logD("Updating song display to ${it.name}.") logD("Updating song display to ${song.name}.")
binding.song = it binding.song = song
binding.playbackSeekBar.max = it.seconds.toInt() binding.playbackSeekBar.max = song.seconds.toInt()
} else { } else {
logD("No song is being played, leaving.") logD("No song is being played, leaving.")
@ -182,10 +182,6 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
playbackModel.enableAnimation() playbackModel.enableAnimation()
} }
if (shouldHandleFileIntent()) {
handleFileIntent(playbackModel)
}
} }
// --- SEEK CALLBACKS --- // --- SEEK CALLBACKS ---

View file

@ -1,31 +0,0 @@
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)
}
/**
* Actually use the intent and push it to [playbackModel]
*/
fun Fragment.handleFileIntent(playbackModel: PlaybackViewModel) {
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())
}

View file

@ -1,7 +1,7 @@
package org.oxycblt.auxio.playback package org.oxycblt.auxio.playback
import android.content.Context import android.content.Context
import android.content.Intent import android.net.Uri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
@ -53,6 +53,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
// Other // Other
private val mIsSeeking = MutableLiveData(false) private val mIsSeeking = MutableLiveData(false)
private var mIntentUri: Uri? = null
private var mCanAnimate = false private var mCanAnimate = false
/** The current song. */ /** The current song. */
@ -113,9 +114,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
// --- PLAYING FUNCTIONS --- // --- PLAYING FUNCTIONS ---
/** /**
* Play a song. * Play a [song] with the [mode] specified. [mode] will default to the preferred song
* @param song The song to be played * playback mode of the user if not specified.
* @param mode The [PlaybackMode] for it to be played in. Defaults to the preferred song playback mode of the user if not specified.
*/ */
fun playSong(song: Song, mode: PlaybackMode = settingsManager.songPlaybackMode) { fun playSong(song: Song, mode: PlaybackMode = settingsManager.songPlaybackMode) {
playbackManager.playSong(song, mode) playbackManager.playSong(song, mode)
@ -154,15 +154,18 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
playbackManager.playParent(genre, shuffled) playbackManager.playParent(genre, shuffled)
} }
/** Shuffle all songs */ /** Play using a file URI. This will not play instantly during the initial startup sequence.*/
fun shuffleAll() { fun playWithUri(uri: Uri, context: Context) {
playbackManager.shuffleAll() // Check if everything is already running to run the URI play
if (playbackManager.isRestored && musicStore.loaded) {
playWithUriInternal(uri, context)
} else {
mIntentUri = uri
}
} }
/** Play a song using an intent */ /** Actually play with a given URI internally. The frontend doesn't play instantly. */
fun playWithIntent(intent: Intent, context: Context) { private fun playWithUriInternal(uri: Uri, context: Context) {
val uri = intent.data ?: return
viewModelScope.launch { viewModelScope.launch {
musicStore.getSongForUri(uri, context.contentResolver)?.let { song -> musicStore.getSongForUri(uri, context.contentResolver)?.let { song ->
playSong(song) playSong(song)
@ -170,6 +173,11 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
} }
} }
/** Shuffle all songs */
fun shuffleAll() {
playbackManager.shuffleAll()
}
// --- POSITION FUNCTIONS --- // --- POSITION FUNCTIONS ---
/** Update the position and push it to [PlaybackStateManager] */ /** Update the position and push it to [PlaybackStateManager] */
@ -326,11 +334,19 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
} }
/** /**
* Get [PlaybackStateManager] to restore its state from the database, if needed. Called by MainFragment. * Handle the file last-saved file intent, or restore playback, depending on the situation.
* @param context [Context] required.
*/ */
fun restorePlaybackIfNeeded(context: Context) { fun setupPlayback(context: Context) {
if (!playbackManager.isRestored && playbackManager.song == null) { val intentUri = mIntentUri
if (intentUri != null) {
// Were not going to be restoring playbackManager after this, so mark it as such.
playbackManager.setRestored()
playWithUriInternal(intentUri, context)
// Remove the uri after finishing the calls so that this does not fire again.
mIntentUri = null
} else if (!playbackManager.isRestored) {
viewModelScope.launch { viewModelScope.launch {
playbackManager.getStateFromDatabase(context) playbackManager.getStateFromDatabase(context)
} }

View file

@ -16,7 +16,6 @@ import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
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.ui.isEdgeOn import org.oxycblt.auxio.ui.isEdgeOn
import org.oxycblt.auxio.ui.isIrregularLandscape import org.oxycblt.auxio.ui.isIrregularLandscape
@ -106,16 +105,6 @@ class QueueFragment : Fragment() {
return binding.root return binding.root
} }
override fun onResume() {
super.onResume()
// QueueFragment shouldn't be handling file intents as it will cause the queue recycler
// to flip out
if (shouldHandleFileIntent()) {
findNavController().navigateUp()
}
}
/** /**
* Create the queue data that should be displayed * Create the queue data that should be displayed
* @return The list of headers/songs that should be displayed. * @return The list of headers/songs that should be displayed.

View file

@ -598,6 +598,13 @@ class PlaybackStateManager private constructor() {
mHasPlayed = false mHasPlayed = false
} }
/**
* Mark this instance as restored.
*/
fun setRestored() {
mIsRestored = true
}
// --- PERSISTENCE FUNCTIONS --- // --- PERSISTENCE FUNCTIONS ---
/** /**