Add media controls to notification
Add media controls to the Playback Notification [Loop, Last, Play/Pause, Next, Shuffle]
This commit is contained in:
parent
09971afb42
commit
08bd0ece3a
20 changed files with 265 additions and 79 deletions
|
@ -15,6 +15,20 @@ import org.oxycblt.auxio.music.Artist
|
|||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Song
|
||||
|
||||
// Get a bitmap for a song, onDone will be called when the bitmap is loaded.
|
||||
// Don't use this on UI elements, thats what the BindingAdapters are for.
|
||||
fun getBitmap(song: Song, context: Context, onDone: (Bitmap) -> Unit) {
|
||||
Coil.enqueue(
|
||||
ImageRequest.Builder(context)
|
||||
.data(song.album.coverUri)
|
||||
.error(R.drawable.ic_song)
|
||||
.target { onDone(it.toBitmap()) }
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
// --- BINDING ADAPTERS ---
|
||||
|
||||
// Get the cover art for a song
|
||||
@BindingAdapter("coverArt")
|
||||
fun ImageView.bindCoverArt(song: Song) {
|
||||
|
@ -131,17 +145,6 @@ fun ImageView.bindGenreImage(genre: Genre) {
|
|||
Coil.imageLoader(context).enqueue(request)
|
||||
}
|
||||
|
||||
// Get a bitmap for a song, onDone will be called when the bitmap is loaded.
|
||||
fun getBitmap(song: Song, context: Context, onDone: (Bitmap) -> Unit) {
|
||||
Coil.enqueue(
|
||||
ImageRequest.Builder(context)
|
||||
.data(song.album.coverUri)
|
||||
.error(R.drawable.ic_song)
|
||||
.target { onDone(it.toBitmap()) }
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
// Get the base request used across the other functions.
|
||||
private fun getDefaultRequest(context: Context, imageView: ImageView): ImageRequest.Builder {
|
||||
return ImageRequest.Builder(context)
|
||||
|
|
|
@ -25,8 +25,6 @@ class CompactPlaybackFragment : Fragment() {
|
|||
): View? {
|
||||
val binding = FragmentCompactPlaybackBinding.inflate(inflater)
|
||||
|
||||
// FIXME: Prevent the play/pause icon from animating on startup
|
||||
// [requires new callback from PlaybackStateManager]
|
||||
val iconPauseToPlay = ContextCompat.getDrawable(
|
||||
requireContext(), R.drawable.ic_pause_to_play
|
||||
) as AnimatedVectorDrawable
|
||||
|
@ -62,7 +60,6 @@ class CompactPlaybackFragment : Fragment() {
|
|||
}
|
||||
|
||||
playbackModel.isPlaying.observe(viewLifecycleOwner) {
|
||||
if (true) {
|
||||
if (it) {
|
||||
// Animate the icon transition when the playing status switches
|
||||
binding.playbackControls.setImageDrawable(iconPauseToPlay)
|
||||
|
@ -71,13 +68,6 @@ class CompactPlaybackFragment : Fragment() {
|
|||
binding.playbackControls.setImageDrawable(iconPlayToPause)
|
||||
iconPlayToPause.start()
|
||||
}
|
||||
} else {
|
||||
if (it) {
|
||||
binding.playbackControls.setImageResource(R.drawable.ic_pause)
|
||||
} else {
|
||||
binding.playbackControls.setImageResource(R.drawable.ic_play)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playbackModel.positionAsProgress.observe(viewLifecycleOwner) {
|
||||
|
@ -88,10 +78,4 @@ class CompactPlaybackFragment : Fragment() {
|
|||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
// playbackModel.resetAnimStatus()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,15 +128,15 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
when (it) {
|
||||
LoopMode.NONE -> {
|
||||
binding.playbackLoop.imageTintList = controlColor
|
||||
binding.playbackLoop.setImageResource(R.drawable.ic_loop)
|
||||
binding.playbackLoop.setImageResource(R.drawable.ic_loop_large)
|
||||
}
|
||||
LoopMode.ONCE -> {
|
||||
binding.playbackLoop.imageTintList = accentColor
|
||||
binding.playbackLoop.setImageResource(R.drawable.ic_loop_one)
|
||||
binding.playbackLoop.setImageResource(R.drawable.ic_loop_one_large)
|
||||
}
|
||||
LoopMode.INFINITE -> {
|
||||
binding.playbackLoop.imageTintList = accentColor
|
||||
binding.playbackLoop.setImageResource(R.drawable.ic_loop)
|
||||
binding.playbackLoop.setImageResource(R.drawable.ic_loop_large)
|
||||
}
|
||||
|
||||
else -> return@observe
|
||||
|
|
|
@ -1,29 +1,39 @@
|
|||
package org.oxycblt.auxio.playback
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import android.support.v4.media.session.MediaSessionCompat
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.media.app.NotificationCompat.MediaStyle
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.coil.getBitmap
|
||||
import org.oxycblt.auxio.playback.state.LoopMode
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
|
||||
// Manager for the playback notification
|
||||
// TODO: Implement some ability
|
||||
internal class PlaybackNotificationHolder {
|
||||
// Holder for the playback notification, should only be used by PlaybackService.
|
||||
// TODO: You really need to rewrite this class. Christ.
|
||||
// TODO: Disable skip prev/next buttons when you cant do those actions
|
||||
// TODO: Implement a way to exit the notification
|
||||
class PlaybackNotificationHolder {
|
||||
private lateinit var mNotification: Notification
|
||||
|
||||
private lateinit var notificationManager: NotificationManager
|
||||
private lateinit var baseNotification: NotificationCompat.Builder
|
||||
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
|
||||
private var isForeground = false
|
||||
|
||||
fun init(context: Context, session: MediaSessionCompat) {
|
||||
fun init(context: Context, session: MediaSessionCompat, playbackService: PlaybackService) {
|
||||
// Never run if the notification has already been created
|
||||
if (!::mNotification.isInitialized) {
|
||||
notificationManager =
|
||||
|
@ -41,10 +51,20 @@ internal class PlaybackNotificationHolder {
|
|||
|
||||
baseNotification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_song)
|
||||
.setStyle(MediaStyle().setMediaSession(session.sessionToken))
|
||||
.setStyle(
|
||||
MediaStyle()
|
||||
.setMediaSession(session.sessionToken)
|
||||
.setShowActionsInCompactView(1, 2, 3)
|
||||
)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setChannelId(CHANNEL_ID)
|
||||
.setShowWhen(false)
|
||||
.setTicker(playbackService.getString(R.string.title_playback))
|
||||
.addAction(createAction(ACTION_LOOP, playbackService))
|
||||
.addAction(createAction(ACTION_SKIP_PREV, playbackService))
|
||||
.addAction(createAction(ACTION_PLAY_PAUSE, playbackService))
|
||||
.addAction(createAction(ACTION_SKIP_NEXT, playbackService))
|
||||
.addAction(createAction(ACTION_SHUFFLE, playbackService))
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
|
||||
mNotification = baseNotification.build()
|
||||
|
@ -67,8 +87,99 @@ internal class PlaybackNotificationHolder {
|
|||
|
||||
getBitmap(song, playbackService) {
|
||||
baseNotification.setLargeIcon(it)
|
||||
|
||||
startForegroundOrNotify(playbackService)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun updatePlaying(playbackService: PlaybackService) {
|
||||
baseNotification.mActions[2] = createAction(ACTION_PLAY_PAUSE, playbackService)
|
||||
|
||||
Log.d(this::class.simpleName, baseNotification.mActions[1].iconCompat?.resId.toString())
|
||||
|
||||
startForegroundOrNotify(playbackService)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun updateLoop(playbackService: PlaybackService) {
|
||||
baseNotification.mActions[0] = createAction(ACTION_LOOP, playbackService)
|
||||
|
||||
startForegroundOrNotify(playbackService)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun updateShuffle(playbackService: PlaybackService) {
|
||||
baseNotification.mActions[4] = createAction(ACTION_SHUFFLE, playbackService)
|
||||
|
||||
startForegroundOrNotify(playbackService)
|
||||
}
|
||||
|
||||
fun stop(playbackService: PlaybackService) {
|
||||
playbackService.stopForeground(true)
|
||||
notificationManager.cancel(NOTIFICATION_ID)
|
||||
|
||||
isForeground = false
|
||||
}
|
||||
|
||||
private fun createAction(action: String, playbackService: PlaybackService): NotificationCompat.Action {
|
||||
val drawable = when (action) {
|
||||
ACTION_LOOP -> {
|
||||
when (playbackManager.loopMode) {
|
||||
LoopMode.NONE -> R.drawable.ic_loop_disabled
|
||||
LoopMode.ONCE -> R.drawable.ic_loop_one
|
||||
LoopMode.INFINITE -> R.drawable.ic_loop
|
||||
}
|
||||
}
|
||||
|
||||
ACTION_SKIP_PREV -> {
|
||||
R.drawable.ic_skip_prev
|
||||
}
|
||||
|
||||
ACTION_PLAY_PAUSE -> {
|
||||
if (playbackManager.isPlaying) {
|
||||
R.drawable.ic_pause
|
||||
} else {
|
||||
R.drawable.ic_play
|
||||
}
|
||||
}
|
||||
|
||||
ACTION_SKIP_NEXT -> {
|
||||
R.drawable.ic_skip_next
|
||||
}
|
||||
|
||||
ACTION_SHUFFLE -> {
|
||||
if (playbackManager.isShuffling) {
|
||||
R.drawable.ic_shuffle
|
||||
} else {
|
||||
R.drawable.ic_shuffle_disabled
|
||||
}
|
||||
}
|
||||
|
||||
else -> R.drawable.ic_play
|
||||
}
|
||||
|
||||
return NotificationCompat.Action.Builder(
|
||||
drawable, action, createPlaybackAction(action, playbackService)
|
||||
).build()
|
||||
}
|
||||
|
||||
private fun createPlaybackAction(action: String, playbackService: PlaybackService): PendingIntent {
|
||||
val intent = Intent()
|
||||
intent.action = action
|
||||
|
||||
return PendingIntent.getBroadcast(
|
||||
playbackService,
|
||||
REQUEST_CODE,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
}
|
||||
|
||||
private fun startForegroundOrNotify(playbackService: PlaybackService) {
|
||||
mNotification = baseNotification.build()
|
||||
|
||||
// Start the service in the foreground if haven't already.
|
||||
if (!isForeground) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
playbackService.startForeground(
|
||||
|
@ -82,17 +193,16 @@ internal class PlaybackNotificationHolder {
|
|||
notificationManager.notify(NOTIFICATION_ID, mNotification)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stop(playbackService: PlaybackService) {
|
||||
playbackService.stopForeground(true)
|
||||
notificationManager.cancel(NOTIFICATION_ID)
|
||||
|
||||
isForeground = false
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "CHANNEL_AUXIO_PLAYBACK"
|
||||
const val NOTIFICATION_ID = 0xA0A0
|
||||
const val REQUEST_CODE = 0xA0C0
|
||||
|
||||
const val ACTION_LOOP = "ACTION_AUXIO_LOOP"
|
||||
const val ACTION_SKIP_PREV = "ACTION_AUXIO_SKIP_PREV"
|
||||
const val ACTION_PLAY_PAUSE = "ACTION_AUXIO_PLAY_PAUSE"
|
||||
const val ACTION_SKIP_NEXT = "ACTION_AUXIO_SKIP_NEXT"
|
||||
const val ACTION_SHUFFLE = "ACTION_AUXIO_SHUFFLE"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,11 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
// Set up callback for system events
|
||||
systemReceiver = SystemEventReceiver()
|
||||
IntentFilter().apply {
|
||||
addAction(PlaybackNotificationHolder.ACTION_LOOP)
|
||||
addAction(PlaybackNotificationHolder.ACTION_SKIP_PREV)
|
||||
addAction(PlaybackNotificationHolder.ACTION_PLAY_PAUSE)
|
||||
addAction(PlaybackNotificationHolder.ACTION_SKIP_NEXT)
|
||||
addAction(PlaybackNotificationHolder.ACTION_SHUFFLE)
|
||||
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
|
||||
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
|
||||
addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
||||
|
@ -115,7 +120,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
|
||||
notificationHolder = PlaybackNotificationHolder()
|
||||
|
||||
notificationHolder.init(applicationContext, mediaSession)
|
||||
notificationHolder.init(applicationContext, mediaSession, this)
|
||||
|
||||
// --- PLAYBACKSTATEMANAGER SETUP ---
|
||||
|
||||
|
@ -186,7 +191,6 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
// --- PLAYBACK STATE CALLBACK OVERRIDES ---
|
||||
|
||||
override fun onSongUpdate(song: Song?) {
|
||||
|
||||
song?.let {
|
||||
val item = MediaItem.fromUri(it.id.toURI())
|
||||
|
||||
|
@ -210,13 +214,20 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
|
||||
if (isPlaying && !player.isPlaying) {
|
||||
player.play()
|
||||
|
||||
notificationHolder.updatePlaying(this)
|
||||
startPollingPosition()
|
||||
} else {
|
||||
player.pause()
|
||||
notificationHolder.updatePlaying(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShuffleUpdate(isShuffling: Boolean) {
|
||||
changeIsFromAudioFocus = false
|
||||
|
||||
notificationHolder.updateShuffle(this)
|
||||
}
|
||||
|
||||
override fun onLoopUpdate(mode: LoopMode) {
|
||||
changeIsFromAudioFocus = false
|
||||
|
||||
|
@ -228,6 +239,8 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
player.repeatMode = Player.REPEAT_MODE_ONE
|
||||
}
|
||||
}
|
||||
|
||||
notificationHolder.updateLoop(this)
|
||||
}
|
||||
|
||||
override fun onSeekConfirm(position: Long) {
|
||||
|
@ -331,6 +344,15 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
|
||||
action?.let {
|
||||
when (it) {
|
||||
PlaybackNotificationHolder.ACTION_LOOP ->
|
||||
playbackManager.setLoopMode(playbackManager.loopMode.increment())
|
||||
PlaybackNotificationHolder.ACTION_SKIP_PREV -> playbackManager.prev()
|
||||
PlaybackNotificationHolder.ACTION_PLAY_PAUSE ->
|
||||
playbackManager.setPlayingStatus(!playbackManager.isPlaying)
|
||||
PlaybackNotificationHolder.ACTION_SKIP_NEXT -> playbackManager.next()
|
||||
PlaybackNotificationHolder.ACTION_SHUFFLE ->
|
||||
playbackManager.setShuffleStatus(!playbackManager.isShuffling)
|
||||
|
||||
BluetoothDevice.ACTION_ACL_CONNECTED -> resume()
|
||||
BluetoothDevice.ACTION_ACL_DISCONNECTED -> pause()
|
||||
|
||||
|
|
|
@ -16,9 +16,8 @@ import org.oxycblt.auxio.playback.state.PlaybackStateCallback
|
|||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
|
||||
// A ViewModel that acts as an intermediary between the UI and PlaybackStateManager
|
||||
// TODO: Implement Looping Modes
|
||||
// TODO: Implement User Queue
|
||||
// TODO: Implement Persistence through Bundles/Databases/Idk
|
||||
// TODO: Implement Persistence through a Database
|
||||
class PlaybackViewModel : ViewModel(), PlaybackStateCallback {
|
||||
// Playback
|
||||
private val mSong = MutableLiveData<Song?>()
|
||||
|
|
|
@ -16,7 +16,7 @@ import kotlin.random.Random
|
|||
// 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.]
|
||||
internal class PlaybackStateManager {
|
||||
class PlaybackStateManager private constructor() {
|
||||
// Playback
|
||||
private var mSong: Song? = null
|
||||
set(value) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
|
|
10
app/src/main/res/drawable/ic_loop_disabled.xml
Normal file
10
app/src/main/res/drawable/ic_loop_disabled.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#80ffffff"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_loop_large.xml
Normal file
11
app/src/main/res/drawable/ic_loop_large.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
|
||||
</vector>
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
|
|
11
app/src/main/res/drawable/ic_loop_one_large.xml
Normal file
11
app/src/main/res/drawable/ic_loop_one_large.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4zM13,15L13,9h-1l-2,1v1h1.5v4L13,15z"/>
|
||||
</vector>
|
|
@ -1,11 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="m 7.5,6 h 3 v 12 h -3 z m 9,0 h -3 v 12 h 3 z" />
|
||||
android:pathData="M 5.571429,19.5 H 9.857143 V 4.5000003 H 5.571429 Z M 14.142857,4.5000003 V 19.5 h 4.285714 V 4.5000003 Z"/>
|
||||
</vector>
|
|
@ -1,11 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8.25,6L8.25,12L18.75,12L18.75,12ZM8.25,18L8.25,12L18.75,12L18.75,12Z" />
|
||||
android:pathData="M 5.0778755,4.0890012 V 12 H 18.922123 v 0 z m 0,15.8219978 V 12 H 18.922123 v 0 z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_shuffle_disabled.xml
Normal file
10
app/src/main/res/drawable/ic_shuffle_disabled.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#80ffffff"
|
||||
android:pathData="M10.59,9.17L5.41,4 4,5.41l5.17,5.17 1.42,-1.41zM14.5,4l2.04,2.04L4,18.59 5.41,20 17.96,7.46 20,9.5L20,4h-5.5zM14.83,13.41l-1.41,1.41 3.13,3.13L14.5,20L20,20v-5.5l-2.04,2.04 -3.13,-3.13z" />
|
||||
</vector>
|
|
@ -1,9 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
|
|
12
app/src/main/res/drawable/ic_skip_next_large.xml
Normal file
12
app/src/main/res/drawable/ic_skip_next_large.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z" />
|
||||
</vector>
|
|
@ -1,9 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
|
|
12
app/src/main/res/drawable/ic_skip_prev_large.xml
Normal file
12
app/src/main/res/drawable/ic_skip_prev_large.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z" />
|
||||
</vector>
|
|
@ -167,7 +167,7 @@
|
|||
android:layout_marginStart="@dimen/margin_mid_large"
|
||||
android:contentDescription="@string/description_skip_next"
|
||||
android:background="@drawable/ui_unbounded_ripple"
|
||||
android:src="@drawable/ic_skip_next"
|
||||
android:src="@drawable/ic_skip_next_large"
|
||||
android:onClick="@{() -> playbackModel.skipNext()}"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
|
||||
app:layout_constraintStart_toEndOf="@+id/playback_play_pause"
|
||||
|
@ -178,7 +178,7 @@
|
|||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="@dimen/size_play_pause_compact"
|
||||
android:layout_height="@dimen/size_play_pause_compact"
|
||||
android:src="@drawable/ic_skip_prev"
|
||||
android:src="@drawable/ic_skip_prev_large"
|
||||
android:contentDescription="@string/description_skip_prev"
|
||||
android:background="@drawable/ui_unbounded_ripple"
|
||||
android:layout_marginEnd="@dimen/margin_mid_large"
|
||||
|
@ -207,7 +207,7 @@
|
|||
android:layout_width="@dimen/size_play_pause_compact"
|
||||
android:layout_height="@dimen/size_play_pause_compact"
|
||||
android:background="@drawable/ui_unbounded_ripple"
|
||||
android:src="@drawable/ic_loop"
|
||||
android:src="@drawable/ic_loop_large"
|
||||
android:layout_marginStart="@dimen/margin_mid_large"
|
||||
android:onClick="@{() -> playbackModel.incrementLoopStatus()}"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
|
||||
|
|
Loading…
Reference in a new issue