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:
parent
344e566aa3
commit
188d7e047f
6 changed files with 45 additions and 15 deletions
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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) {}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue