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

View file

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

View file

@ -82,12 +82,12 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
// --- VIEWMODEL SETUP --
playbackModel.song.observe(viewLifecycleOwner) {
if (it != null) {
logD("Updating song display to ${it.name}.")
playbackModel.song.observe(viewLifecycleOwner) { song ->
if (song != null) {
logD("Updating song display to ${song.name}.")
binding.song = it
binding.playbackSeekBar.max = it.seconds.toInt()
binding.song = song
binding.playbackSeekBar.max = song.seconds.toInt()
} else {
logD("No song is being played, leaving.")
@ -182,10 +182,6 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
playbackModel.enableAnimation()
}
if (shouldHandleFileIntent()) {
handleFileIntent(playbackModel)
}
}
// --- 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
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
@ -53,6 +53,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
// Other
private val mIsSeeking = MutableLiveData(false)
private var mIntentUri: Uri? = null
private var mCanAnimate = false
/** The current song. */
@ -113,9 +114,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
// --- PLAYING FUNCTIONS ---
/**
* Play a song.
* @param song The song to be played
* @param mode The [PlaybackMode] for it to be played in. Defaults to the preferred song playback mode of the user if not specified.
* Play a [song] with the [mode] specified. [mode] will default to the preferred song
* playback mode of the user if not specified.
*/
fun playSong(song: Song, mode: PlaybackMode = settingsManager.songPlaybackMode) {
playbackManager.playSong(song, mode)
@ -154,15 +154,18 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
playbackManager.playParent(genre, shuffled)
}
/** Shuffle all songs */
fun shuffleAll() {
playbackManager.shuffleAll()
/** Play using a file URI. This will not play instantly during the initial startup sequence.*/
fun playWithUri(uri: Uri, context: Context) {
// 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 */
fun playWithIntent(intent: Intent, context: Context) {
val uri = intent.data ?: return
/** Actually play with a given URI internally. The frontend doesn't play instantly. */
private fun playWithUriInternal(uri: Uri, context: Context) {
viewModelScope.launch {
musicStore.getSongForUri(uri, context.contentResolver)?.let { song ->
playSong(song)
@ -170,6 +173,11 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
}
}
/** Shuffle all songs */
fun shuffleAll() {
playbackManager.shuffleAll()
}
// --- POSITION FUNCTIONS ---
/** 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.
* @param context [Context] required.
* Handle the file last-saved file intent, or restore playback, depending on the situation.
*/
fun restorePlaybackIfNeeded(context: Context) {
if (!playbackManager.isRestored && playbackManager.song == null) {
fun setupPlayback(context: Context) {
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 {
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.Header
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.shouldHandleFileIntent
import org.oxycblt.auxio.ui.isEdgeOn
import org.oxycblt.auxio.ui.isIrregularLandscape
@ -106,16 +105,6 @@ class QueueFragment : Fragment() {
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
* @return The list of headers/songs that should be displayed.

View file

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