Improve code slightly
Add some documentation and remove useless code/bugs.
This commit is contained in:
parent
2c783beaba
commit
604145fb69
12 changed files with 88 additions and 76 deletions
|
@ -5,21 +5,14 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.music.processing.MusicLoaderResponse
|
||||
|
||||
class LoadingViewModel(private val app: Application) : ViewModel() {
|
||||
// Coroutine
|
||||
private val loadingJob = Job()
|
||||
private val ioScope = CoroutineScope(
|
||||
loadingJob + Dispatchers.IO
|
||||
)
|
||||
|
||||
// UI control
|
||||
private val mResponse = MutableLiveData<MusicLoaderResponse>()
|
||||
val response: LiveData<MusicLoaderResponse> get() = mResponse
|
||||
|
@ -42,10 +35,12 @@ class LoadingViewModel(private val app: Application) : ViewModel() {
|
|||
}
|
||||
|
||||
private fun doLoad() {
|
||||
ioScope.launch {
|
||||
viewModelScope.launch {
|
||||
val musicStore = MusicStore.getInstance()
|
||||
|
||||
val response = musicStore.load(app)
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
return@withContext musicStore.load(app)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
mResponse.value = response
|
||||
|
@ -75,13 +70,6 @@ class LoadingViewModel(private val app: Application) : ViewModel() {
|
|||
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 {
|
||||
@Suppress("unchecked_cast")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
|
|
|
@ -8,7 +8,9 @@ import org.oxycblt.auxio.music.processing.MusicLoaderResponse
|
|||
import org.oxycblt.auxio.music.processing.MusicSorter
|
||||
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() {
|
||||
private var mGenres = listOf<Genre>()
|
||||
val genres: List<Genre> get() = mGenres
|
||||
|
@ -70,9 +72,7 @@ class MusicStore private constructor() {
|
|||
this::class.simpleName,
|
||||
"Music load completed successfully in ${elapsed}ms."
|
||||
)
|
||||
}
|
||||
|
||||
if (loader.response == MusicLoaderResponse.DONE) {
|
||||
loaded = true
|
||||
}
|
||||
|
||||
|
|
|
@ -98,28 +98,17 @@ fun ImageView.bindGenreImage(genre: Genre) {
|
|||
|
||||
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.
|
||||
for (i in 0..3) {
|
||||
val artist = genre.artists[i]
|
||||
// Get the Nth cover from each artist, if possible.
|
||||
for (i in 0..3) {
|
||||
val artist = genre.artists[i]
|
||||
|
||||
uris.add(
|
||||
if (artist.albums.size > i) {
|
||||
artist.albums[i].coverUri
|
||||
} else {
|
||||
artist.albums[0].coverUri
|
||||
}
|
||||
)
|
||||
}
|
||||
uris.add(
|
||||
if (artist.albums.size > i) {
|
||||
artist.albums[i].coverUri
|
||||
} else {
|
||||
artist.albums[0].coverUri
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val fetcher = MosaicFetcher(context)
|
||||
|
|
|
@ -15,6 +15,13 @@ import org.oxycblt.auxio.R
|
|||
import org.oxycblt.auxio.databinding.FragmentCompactPlaybackBinding
|
||||
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() {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ fun NotificationManager.createMediaNotification(
|
|||
context: Context,
|
||||
mediaSession: MediaSessionCompat
|
||||
): NotificationCompat.Builder {
|
||||
|
||||
// Create a notification channel if required
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
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")
|
||||
fun NotificationCompat.Builder.updatePlaying(context: Context) {
|
||||
mActions[2] = newAction(NotificationUtils.ACTION_PLAY_PAUSE, context)
|
||||
|
|
|
@ -19,7 +19,12 @@ import org.oxycblt.auxio.playback.state.LoopMode
|
|||
import org.oxycblt.auxio.ui.accent
|
||||
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 {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
|
||||
|
@ -30,6 +35,8 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
): View? {
|
||||
val binding = FragmentPlaybackBinding.inflate(inflater)
|
||||
|
||||
// TODO: Add a swipe-to-next-track function using a ViewPager
|
||||
|
||||
// Create accents & icons to use
|
||||
val accentColor = ColorStateList.valueOf(accent.first.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) {
|
||||
if (it.isEmpty() && playbackModel.queue.value!!.isEmpty()) {
|
||||
if (it.isEmpty() && playbackModel.nextItemsInQueue.value!!.isEmpty()) {
|
||||
queueMenuItem.isEnabled = false
|
||||
queueMenuItem.icon = iconQueueInactive
|
||||
} else {
|
||||
|
|
|
@ -40,8 +40,18 @@ import org.oxycblt.auxio.playback.state.LoopMode
|
|||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
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 {
|
||||
private val player: SimpleExoPlayer by lazy {
|
||||
SimpleExoPlayer.Builder(applicationContext).build()
|
||||
|
|
|
@ -15,8 +15,10 @@ import org.oxycblt.auxio.playback.state.LoopMode
|
|||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
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 {
|
||||
// Playback
|
||||
private val mSong = MutableLiveData<Song?>()
|
||||
|
@ -198,6 +200,9 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
|
||||
from -= 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)
|
||||
|
|
|
@ -14,15 +14,12 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
|
|||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
// Make header objects unswipable by only returning the swipe flags if the ViewHolder
|
||||
// is for a queue item.
|
||||
// Only allow dragging/swiping with the queue item ViewHolder, not the header items.
|
||||
return if (viewHolder is QueueAdapter.ViewHolder) {
|
||||
makeFlag(
|
||||
ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
) or makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else 0
|
||||
}
|
||||
|
||||
override fun interpolateOutOfBoundsScroll(
|
||||
|
|
|
@ -49,7 +49,6 @@ class QueueFragment : Fragment() {
|
|||
}
|
||||
|
||||
queueAdapter.submitList(createQueueDisplay()) {
|
||||
binding.queueRecycler.scrollToPosition(0)
|
||||
scrollRecyclerIfNeeded(binding)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,14 @@ import org.oxycblt.auxio.music.MusicStore
|
|||
import org.oxycblt.auxio.music.Song
|
||||
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/.
|
||||
// If you want to add system-side things, add to PlaybackService.
|
||||
// If you want to add ui-side things, add to PlaybackViewModel.
|
||||
// [Yes, I know MediaSessionCompat exists, but I like having full control over the
|
||||
// playback state instead of dealing with android's likely buggy code.]
|
||||
/**
|
||||
* Master class for the playback state. This should ***not*** be used outside of the playback module.
|
||||
* - If you want to show the playback state in the UI, use [org.oxycblt.auxio.playback.PlaybackViewModel].
|
||||
* - If you want to add to the system aspects or the exoplayer instance, use [org.oxycblt.auxio.playback.PlaybackService].
|
||||
*
|
||||
* All instantiation should be done with [PlaybackStateManager.from()].
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class PlaybackStateManager private constructor() {
|
||||
// Playback
|
||||
private var mSong: Song? = null
|
||||
|
@ -438,10 +440,32 @@ class PlaybackStateManager private constructor() {
|
|||
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 {
|
||||
@Volatile
|
||||
private var INSTANCE: PlaybackStateManager? = null
|
||||
|
||||
/**
|
||||
* Get/Instantiate the single instance of [PlaybackStateManager].
|
||||
*/
|
||||
fun getInstance(): PlaybackStateManager {
|
||||
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) {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package org.oxycblt.auxio.recycler
|
||||
|
||||
// TODO: Swap these temp values for actual constants
|
||||
enum class ShowMode(val constant: Long) {
|
||||
SHOW_GENRES(0), SHOW_ARTISTS(1), SHOW_ALBUMS(2), SHOW_SONGS(3);
|
||||
enum class ShowMode {
|
||||
SHOW_GENRES, SHOW_ARTISTS, SHOW_ALBUMS, SHOW_SONGS;
|
||||
|
||||
// Make a slice of all the values that this ShowMode covers.
|
||||
// ex. SHOW_ARTISTS would return SHOW_ARTISTS, SHOW_ALBUMS, and SHOW_SONGS
|
||||
|
|
Loading…
Reference in a new issue