Improve code slightly

Add some documentation and remove useless code/bugs.
This commit is contained in:
OxygenCobalt 2020-11-11 19:27:38 -07:00
parent 2c783beaba
commit 604145fb69
12 changed files with 88 additions and 76 deletions

View file

@ -5,21 +5,14 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.CoroutineScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.processing.MusicLoaderResponse import org.oxycblt.auxio.music.processing.MusicLoaderResponse
class LoadingViewModel(private val app: Application) : ViewModel() { class LoadingViewModel(private val app: Application) : ViewModel() {
// Coroutine
private val loadingJob = Job()
private val ioScope = CoroutineScope(
loadingJob + Dispatchers.IO
)
// UI control // UI control
private val mResponse = MutableLiveData<MusicLoaderResponse>() private val mResponse = MutableLiveData<MusicLoaderResponse>()
val response: LiveData<MusicLoaderResponse> get() = mResponse val response: LiveData<MusicLoaderResponse> get() = mResponse
@ -42,10 +35,12 @@ class LoadingViewModel(private val app: Application) : ViewModel() {
} }
private fun doLoad() { private fun doLoad() {
ioScope.launch { viewModelScope.launch {
val musicStore = MusicStore.getInstance() val musicStore = MusicStore.getInstance()
val response = musicStore.load(app) val response = withContext(Dispatchers.IO) {
return@withContext musicStore.load(app)
}
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
mResponse.value = response mResponse.value = response
@ -75,13 +70,6 @@ class LoadingViewModel(private val app: Application) : ViewModel() {
mDoGrant.value = false mDoGrant.value = false
} }
override fun onCleared() {
super.onCleared()
// Cancel the current loading job if the app has been stopped
loadingJob.cancel()
}
class Factory(private val application: Application) : ViewModelProvider.Factory { class Factory(private val application: Application) : ViewModelProvider.Factory {
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T { override fun <T : ViewModel?> create(modelClass: Class<T>): T {

View file

@ -8,7 +8,9 @@ import org.oxycblt.auxio.music.processing.MusicLoaderResponse
import org.oxycblt.auxio.music.processing.MusicSorter import org.oxycblt.auxio.music.processing.MusicSorter
import org.oxycblt.auxio.recycler.ShowMode import org.oxycblt.auxio.recycler.ShowMode
// Storage for Music Data. Only use getInstance() to access this object. /**
* The main storage for music items. Use [MusicStore.from()] to get the instance.
*/
class MusicStore private constructor() { class MusicStore private constructor() {
private var mGenres = listOf<Genre>() private var mGenres = listOf<Genre>()
val genres: List<Genre> get() = mGenres val genres: List<Genre> get() = mGenres
@ -70,9 +72,7 @@ class MusicStore private constructor() {
this::class.simpleName, this::class.simpleName,
"Music load completed successfully in ${elapsed}ms." "Music load completed successfully in ${elapsed}ms."
) )
}
if (loader.response == MusicLoaderResponse.DONE) {
loaded = true loaded = true
} }

View file

@ -98,16 +98,6 @@ fun ImageView.bindGenreImage(genre: Genre) {
Log.d(this::class.simpleName, genre.numAlbums.toString()) Log.d(this::class.simpleName, genre.numAlbums.toString())
// Try to create a 4x4 mosaic if possible, if not, just create a 2x2 mosaic.
if (genre.numAlbums >= 16) {
while (uris.size < 16) {
genre.artists.forEach { artist ->
artist.albums.forEach {
uris.add(it.coverUri)
}
}
}
} else {
// Get the Nth cover from each artist, if possible. // Get the Nth cover from each artist, if possible.
for (i in 0..3) { for (i in 0..3) {
val artist = genre.artists[i] val artist = genre.artists[i]
@ -120,7 +110,6 @@ fun ImageView.bindGenreImage(genre: Genre) {
} }
) )
} }
}
val fetcher = MosaicFetcher(context) val fetcher = MosaicFetcher(context)

View file

@ -15,6 +15,13 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentCompactPlaybackBinding import org.oxycblt.auxio.databinding.FragmentCompactPlaybackBinding
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
/**
* A [Fragment] that displays the currently played song at a glance, with some basic controls.
* Extends into [PlaybackFragment] when clicked on.
*
* Instantiation is done by the navigation component, **do not instantiate this fragment manually.**
* @author OxygenCobalt
*/
class CompactPlaybackFragment : Fragment() { class CompactPlaybackFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()

View file

@ -36,7 +36,6 @@ fun NotificationManager.createMediaNotification(
context: Context, context: Context,
mediaSession: MediaSessionCompat mediaSession: MediaSessionCompat
): NotificationCompat.Builder { ): NotificationCompat.Builder {
// Create a notification channel if required // Create a notification channel if required
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel( val channel = NotificationChannel(
@ -97,6 +96,7 @@ fun NotificationCompat.Builder.setMetadata(song: Song, context: Context, onDone:
} }
} }
// I have no idea how to update actions on the fly so I have to use these restricted APIs.
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
fun NotificationCompat.Builder.updatePlaying(context: Context) { fun NotificationCompat.Builder.updatePlaying(context: Context) {
mActions[2] = newAction(NotificationUtils.ACTION_PLAY_PAUSE, context) mActions[2] = newAction(NotificationUtils.ACTION_PLAY_PAUSE, context)

View file

@ -19,7 +19,12 @@ import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.ui.accent import org.oxycblt.auxio.ui.accent
import org.oxycblt.auxio.ui.toColor import org.oxycblt.auxio.ui.toColor
// TODO: Add a swipe-to-next-track function using a ViewPager /**
* A [Fragment] that displays more information about the song, along with more media controls.
*
* Instantiation is done by the navigation component, **do not instantiate this fragment manually.**
* @author OxygenCobalt
*/
class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
@ -30,6 +35,8 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
): View? { ): View? {
val binding = FragmentPlaybackBinding.inflate(inflater) val binding = FragmentPlaybackBinding.inflate(inflater)
// TODO: Add a swipe-to-next-track function using a ViewPager
// Create accents & icons to use // Create accents & icons to use
val accentColor = ColorStateList.valueOf(accent.first.toColor(requireContext())) val accentColor = ColorStateList.valueOf(accent.first.toColor(requireContext()))
val controlColor = ColorStateList.valueOf(R.color.control_color.toColor(requireContext())) val controlColor = ColorStateList.valueOf(R.color.control_color.toColor(requireContext()))
@ -173,7 +180,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
} }
playbackModel.userQueue.observe(viewLifecycleOwner) { playbackModel.userQueue.observe(viewLifecycleOwner) {
if (it.isEmpty() && playbackModel.queue.value!!.isEmpty()) { if (it.isEmpty() && playbackModel.nextItemsInQueue.value!!.isEmpty()) {
queueMenuItem.isEnabled = false queueMenuItem.isEnabled = false
queueMenuItem.icon = iconQueueInactive queueMenuItem.icon = iconQueueInactive
} else { } else {

View file

@ -40,8 +40,18 @@ import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.PlaybackStateManager
// A Service that manages the single ExoPlayer instance and manages the system-side /**
// aspects of playback. * A service that manages the system-side aspects of playback, such as:
* - The single [SimpleExoPlayer] instance.
* - The [MediaSessionCompat]
* - The Media Notification
* - Audio Focus
* - Headset management
*
* This service relies on [PlaybackStateManager.Callback], so therefore there's no need to bind
* to it to deliver commands.
* @author OxygenCobalt
*/
class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Callback { class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Callback {
private val player: SimpleExoPlayer by lazy { private val player: SimpleExoPlayer by lazy {
SimpleExoPlayer.Builder(applicationContext).build() SimpleExoPlayer.Builder(applicationContext).build()

View file

@ -15,8 +15,10 @@ import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.PlaybackStateManager
// A ViewModel that acts as an intermediary between the UI and PlaybackStateManager /**
// TODO: Implement Persistence through a Database * The ViewModel that provides a UI-Focused frontend for [PlaybackStateManager].
* @author OxygenCobalt
*/
class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
// Playback // Playback
private val mSong = MutableLiveData<Song?>() private val mSong = MutableLiveData<Song?>()
@ -198,6 +200,9 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
from -= mUserQueue.value!!.size.inc() from -= mUserQueue.value!!.size.inc()
to -= mUserQueue.value!!.size.inc() to -= mUserQueue.value!!.size.inc()
// Ignore movements that are past the next songs
if (to <= mIndex.value!!) return false
} }
playbackManager.moveQueueItems(from, to) playbackManager.moveQueueItems(from, to)

View file

@ -14,15 +14,12 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
recyclerView: RecyclerView, recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder viewHolder: RecyclerView.ViewHolder
): Int { ): Int {
// Make header objects unswipable by only returning the swipe flags if the ViewHolder // Only allow dragging/swiping with the queue item ViewHolder, not the header items.
// is for a queue item.
return if (viewHolder is QueueAdapter.ViewHolder) { return if (viewHolder is QueueAdapter.ViewHolder) {
makeFlag( makeFlag(
ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN
) or makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START) ) or makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START)
} else { } else 0
0
}
} }
override fun interpolateOutOfBoundsScroll( override fun interpolateOutOfBoundsScroll(

View file

@ -49,7 +49,6 @@ class QueueFragment : Fragment() {
} }
queueAdapter.submitList(createQueueDisplay()) { queueAdapter.submitList(createQueueDisplay()) {
binding.queueRecycler.scrollToPosition(0)
scrollRecyclerIfNeeded(binding) scrollRecyclerIfNeeded(binding)
} }
} }

View file

@ -10,12 +10,14 @@ import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import kotlin.random.Random import kotlin.random.Random
// The manager of the current playback state [Current Song, Queue, Shuffling] /**
// This class is for sole use by the code in /playback/. * Master class for the playback state. This should ***not*** be used outside of the playback module.
// If you want to add system-side things, add to PlaybackService. * - If you want to show the playback state in the UI, use [org.oxycblt.auxio.playback.PlaybackViewModel].
// If you want to add ui-side things, add to PlaybackViewModel. * - If you want to add to the system aspects or the exoplayer instance, use [org.oxycblt.auxio.playback.PlaybackService].
// [Yes, I know MediaSessionCompat exists, but I like having full control over the *
// playback state instead of dealing with android's likely buggy code.] * All instantiation should be done with [PlaybackStateManager.from()].
* @author OxygenCobalt
*/
class PlaybackStateManager private constructor() { class PlaybackStateManager private constructor() {
// Playback // Playback
private var mSong: Song? = null private var mSong: Song? = null
@ -438,10 +440,32 @@ class PlaybackStateManager private constructor() {
return final return final
} }
/**
* The interface for receiving updates from [PlaybackStateManager].
* Add the callback to [PlaybackStateManager] using [addCallback],
* remove them on destruction with [removeCallback].
*/
interface Callback {
fun onSongUpdate(song: Song?) {}
fun onParentUpdate(parent: BaseModel?) {}
fun onPositionUpdate(position: Long) {}
fun onQueueUpdate(queue: MutableList<Song>) {}
fun onUserQueueUpdate(userQueue: MutableList<Song>) {}
fun onModeUpdate(mode: PlaybackMode) {}
fun onIndexUpdate(index: Int) {}
fun onPlayingUpdate(isPlaying: Boolean) {}
fun onShuffleUpdate(isShuffling: Boolean) {}
fun onLoopUpdate(mode: LoopMode) {}
fun onSeekConfirm(position: Long) {}
}
companion object { companion object {
@Volatile @Volatile
private var INSTANCE: PlaybackStateManager? = null private var INSTANCE: PlaybackStateManager? = null
/**
* Get/Instantiate the single instance of [PlaybackStateManager].
*/
fun getInstance(): PlaybackStateManager { fun getInstance(): PlaybackStateManager {
val currentInstance = INSTANCE val currentInstance = INSTANCE
@ -456,18 +480,4 @@ class PlaybackStateManager private constructor() {
} }
} }
} }
interface Callback {
fun onSongUpdate(song: Song?) {}
fun onParentUpdate(parent: BaseModel?) {}
fun onPositionUpdate(position: Long) {}
fun onQueueUpdate(queue: MutableList<Song>) {}
fun onUserQueueUpdate(userQueue: MutableList<Song>) {}
fun onModeUpdate(mode: PlaybackMode) {}
fun onIndexUpdate(index: Int) {}
fun onPlayingUpdate(isPlaying: Boolean) {}
fun onShuffleUpdate(isShuffling: Boolean) {}
fun onLoopUpdate(mode: LoopMode) {}
fun onSeekConfirm(position: Long) {}
}
} }

View file

@ -1,8 +1,8 @@
package org.oxycblt.auxio.recycler package org.oxycblt.auxio.recycler
// TODO: Swap these temp values for actual constants // TODO: Swap these temp values for actual constants
enum class ShowMode(val constant: Long) { enum class ShowMode {
SHOW_GENRES(0), SHOW_ARTISTS(1), SHOW_ALBUMS(2), SHOW_SONGS(3); SHOW_GENRES, SHOW_ARTISTS, SHOW_ALBUMS, SHOW_SONGS;
// Make a slice of all the values that this ShowMode covers. // Make a slice of all the values that this ShowMode covers.
// ex. SHOW_ARTISTS would return SHOW_ARTISTS, SHOW_ALBUMS, and SHOW_SONGS // ex. SHOW_ARTISTS would return SHOW_ARTISTS, SHOW_ALBUMS, and SHOW_SONGS