Make Notification show currently played model

Make the playback notification show the currently played music model [e.g the genre/artist/album thats being played].
This commit is contained in:
OxygenCobalt 2020-11-01 19:58:20 -07:00
parent 344e566aa3
commit 188d7e047f
6 changed files with 45 additions and 15 deletions

View file

@ -10,7 +10,7 @@ android {
defaultConfig { defaultConfig {
applicationId "org.oxycblt.auxio" applicationId "org.oxycblt.auxio"
minSdkVersion 24 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 30
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
@ -56,7 +56,7 @@ dependencies {
implementation 'androidx.fragment:fragment-ktx:1.3.0-beta01' implementation 'androidx.fragment:fragment-ktx:1.3.0-beta01'
// Layout // Layout
implementation 'androidx.constraintlayout:constraintlayout:2.0.2' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
// Lifecycle // Lifecycle
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

View file

@ -5,14 +5,22 @@ import android.content.Intent
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import org.oxycblt.auxio.playback.PlaybackService import org.oxycblt.auxio.playback.PlaybackService
import org.oxycblt.auxio.theme.accent import org.oxycblt.auxio.theme.accent
// FIXME: Fix bug where fast navigation will break the animations and // FIXME: Fix bug where fast navigation will break the animations and
// lead to nothing being displayed [Possibly Un-fixable] // lead to nothing being displayed [Possibly Un-fixable]
// TODO: Test for compatibility
// API 30 - No Issues
// API 29 - No Issues [Primary Testing Version]
// API 28-23 - Not tested yet
// API 22 - ProgressBar/SeekBar look wonky, RecyclerView dividers don't show
// API 21 - Not tested yet
class MainActivity : AppCompatActivity(R.layout.activity_main) { class MainActivity : AppCompatActivity(R.layout.activity_main) {
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? { override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
// Apply the theme // Apply the theme
setTheme(accent.second) setTheme(accent.second)

View file

@ -15,6 +15,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.coil.getBitmap import org.oxycblt.auxio.music.coil.getBitmap
import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.PlaybackStateManager
object NotificationUtils { object NotificationUtils {
@ -52,8 +53,7 @@ fun NotificationManager.createMediaNotification(
) )
// TODO: Things that probably aren't possible but would be nice // TODO: Things that probably aren't possible but would be nice
// - Swipe to close instead of a button to press // - Playing intent takes you to PlaybackFragment instead of MainFragment
// - Playing intent takes you to now playing screen instead of elsewhere
return NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID) return NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID)
.setSmallIcon(R.drawable.ic_song) .setSmallIcon(R.drawable.ic_song)
.setStyle( .setStyle(
@ -64,12 +64,12 @@ fun NotificationManager.createMediaNotification(
.setCategory(NotificationCompat.CATEGORY_SERVICE) .setCategory(NotificationCompat.CATEGORY_SERVICE)
.setChannelId(NotificationUtils.CHANNEL_ID) .setChannelId(NotificationUtils.CHANNEL_ID)
.setShowWhen(false) .setShowWhen(false)
.setTicker(context.getString(R.string.title_playback))
.addAction(newAction(NotificationUtils.ACTION_LOOP, context)) .addAction(newAction(NotificationUtils.ACTION_LOOP, context))
.addAction(newAction(NotificationUtils.ACTION_SKIP_PREV, context)) .addAction(newAction(NotificationUtils.ACTION_SKIP_PREV, context))
.addAction(newAction(NotificationUtils.ACTION_PLAY_PAUSE, context)) .addAction(newAction(NotificationUtils.ACTION_PLAY_PAUSE, context))
.addAction(newAction(NotificationUtils.ACTION_SKIP_NEXT, context)) .addAction(newAction(NotificationUtils.ACTION_SKIP_NEXT, context))
.addAction(newAction(NotificationUtils.ACTION_EXIT, context)) .addAction(newAction(NotificationUtils.ACTION_EXIT, context))
.setNotificationSilent()
.setSubText(context.getString(R.string.title_playback)) .setSubText(context.getString(R.string.title_playback))
.setContentIntent(mainIntent) .setContentIntent(mainIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
@ -91,8 +91,6 @@ fun NotificationCompat.Builder.setMetadata(song: Song, context: Context, onDone:
@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)
setOngoing(PlaybackStateManager.getInstance().isPlaying)
} }
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
@ -100,6 +98,18 @@ fun NotificationCompat.Builder.updateLoop(context: Context) {
mActions[0] = newAction(NotificationUtils.ACTION_LOOP, context) mActions[0] = newAction(NotificationUtils.ACTION_LOOP, context)
} }
@SuppressLint("RestrictedApi")
fun NotificationCompat.Builder.updateMode(context: Context) {
val playbackManager = PlaybackStateManager.getInstance()
// If the mode is ALL_SONGS, then just put a string, otherwise put the parent model's name.
if (playbackManager.mode == PlaybackMode.ALL_SONGS) {
setSubText(context.getString(R.string.title_all_songs))
} else {
setSubText(playbackManager.parent!!.name)
}
}
private fun newAction(action: String, context: Context): NotificationCompat.Action { private fun newAction(action: String, context: Context): NotificationCompat.Action {
val playbackManager = PlaybackStateManager.getInstance() val playbackManager = PlaybackStateManager.getInstance()

View file

@ -38,6 +38,7 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.coil.getBitmap import org.oxycblt.auxio.music.coil.getBitmap
import org.oxycblt.auxio.music.toURI import org.oxycblt.auxio.music.toURI
import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.playback.state.PlaybackStateCallback import org.oxycblt.auxio.playback.state.PlaybackStateCallback
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.PlaybackStateManager
@ -57,7 +58,6 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
private var changeIsFromAudioFocus = true private var changeIsFromAudioFocus = true
private var isForeground = false private var isForeground = false
private var isDetachedFromUI = false
private val serviceJob = Job() private val serviceJob = Job()
private val serviceScope = CoroutineScope( private val serviceScope = CoroutineScope(
@ -220,6 +220,12 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
stopForegroundAndNotification() stopForegroundAndNotification()
} }
override fun onModeUpdate(mode: PlaybackMode) {
notification.updateMode(this)
startForegroundOrNotify()
}
override fun onPlayingUpdate(isPlaying: Boolean) { override fun onPlayingUpdate(isPlaying: Boolean) {
changeIsFromAudioFocus = false changeIsFromAudioFocus = false

View file

@ -6,6 +6,7 @@ interface PlaybackStateCallback {
fun onSongUpdate(song: Song?) {} fun onSongUpdate(song: Song?) {}
fun onPositionUpdate(position: Long) {} fun onPositionUpdate(position: Long) {}
fun onQueueUpdate(queue: MutableList<Song>) {} fun onQueueUpdate(queue: MutableList<Song>) {}
fun onModeUpdate(mode: PlaybackMode) {}
fun onIndexUpdate(index: Int) {} fun onIndexUpdate(index: Int) {}
fun onPlayingUpdate(isPlaying: Boolean) {} fun onPlayingUpdate(isPlaying: Boolean) {}
fun onShuffleUpdate(isShuffling: Boolean) {} fun onShuffleUpdate(isShuffling: Boolean) {}

View file

@ -11,7 +11,7 @@ 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] // The manager of the current playback state [Current Song, Queue, Shuffling]
// This class is for sole use by the classes in /playback/. // 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 system-side things, add to PlaybackService.
// If you want to add ui-side things, add to PlaybackViewModel. // If you want to add ui-side things, add to PlaybackViewModel.
// [Yes, I know MediaSessionCompat exists, but I like having full control over the // [Yes, I know MediaSessionCompat exists, but I like having full control over the
@ -42,6 +42,10 @@ class PlaybackStateManager private constructor() {
callbacks.forEach { it.onIndexUpdate(value) } callbacks.forEach { it.onIndexUpdate(value) }
} }
private var mMode = PlaybackMode.ALL_SONGS private var mMode = PlaybackMode.ALL_SONGS
set(value) {
field = value
callbacks.forEach { it.onModeUpdate(value) }
}
private var mIsPlaying = false private var mIsPlaying = false
set(value) { set(value) {
@ -62,9 +66,11 @@ class PlaybackStateManager private constructor() {
} }
val song: Song? get() = mSong val song: Song? get() = mSong
val parent: BaseModel? get() = mParent
val position: Long get() = mPosition val position: Long get() = mPosition
val queue: MutableList<Song> get() = mQueue val queue: MutableList<Song> get() = mQueue
val index: Int get() = mIndex val index: Int get() = mIndex
val mode: PlaybackMode get() = mMode
val isPlaying: Boolean get() = mIsPlaying val isPlaying: Boolean get() = mIsPlaying
val isShuffling: Boolean get() = mIsShuffling val isShuffling: Boolean get() = mIsShuffling
val loopMode: LoopMode get() = mLoopMode val loopMode: LoopMode get() = mLoopMode
@ -136,6 +142,10 @@ class PlaybackStateManager private constructor() {
Log.d(this::class.simpleName, "Playing ${baseModel.name}") Log.d(this::class.simpleName, "Playing ${baseModel.name}")
mParent = baseModel
mIndex = 0
mIsShuffling = shuffled
when (baseModel) { when (baseModel) {
is Album -> { is Album -> {
mQueue = orderSongsInAlbum(baseModel) mQueue = orderSongsInAlbum(baseModel)
@ -157,10 +167,6 @@ class PlaybackStateManager private constructor() {
updatePlayback(mQueue[0]) updatePlayback(mQueue[0])
mIndex = 0
mParent = baseModel
mIsShuffling = shuffled
if (mIsShuffling) { if (mIsShuffling) {
genShuffle(false) genShuffle(false)
} else { } else {
@ -178,8 +184,7 @@ class PlaybackStateManager private constructor() {
} }
fun setPosition(position: Long) { fun setPosition(position: Long) {
// Due to the hacky way I poll ExoPlayer positions, don't accept any bugged positions // Don't accept any bugged positions that are over the duration of the song.
// that are over the duration of the song.
mSong?.let { mSong?.let {
if (position <= it.duration) { if (position <= it.duration) {
mPosition = position mPosition = position