Skip to content

Instantly share code, notes, and snippets.

@ColeMurray
Created May 16, 2025 21:13
Show Gist options
  • Save ColeMurray/44f2ed90e5e589470582ab3e7c3716fe to your computer and use it in GitHub Desktop.
Save ColeMurray/44f2ed90e5e589470582ab3e7c3716fe to your computer and use it in GitHub Desktop.
auxio.patch
diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt
index 10342f1d..1500a57e 100644
--- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt
@@ -133,66 +133,68 @@ class MainFragment :
SelectionBackPressedCallback(listModel).also { selectionBackCallback = it }
speedDialBackCallback = SpeedDialBackPressedCallback()
navigationListener = DialogAwareNavigationListener(::onExploreNavigate)
// --- UI SETUP ---
val context = requireActivity()
binding.root.setOnApplyWindowInsetsListener { _, insets ->
lastInsets = insets
insets
}
// Send meaningful accessibility events for bottom sheets
ViewCompat.setAccessibilityPaneTitle(
binding.playbackSheet, context.getString(R.string.lbl_playback))
ViewCompat.setAccessibilityPaneTitle(
binding.queueSheet, context.getString(R.string.lbl_queue))
if (queueSheetBehavior != null) {
// In portrait mode, set up click listeners on the stacked sheets.
L.d("Configuring stacked bottom sheets")
unlikelyToBeNull(binding.queueHandleWrapper).setOnClickListener {
playbackModel.openQueue()
}
+ binding.queueClear?.setOnClickListener { playbackModel.clearQueue() }
} else {
// Dual-pane mode, manually style the static queue sheet.
L.d("Configuring dual-pane bottom sheet")
binding.queueSheet.apply {
// Emulate the elevated bottom sheet style.
background =
MaterialShapeDrawable.createWithElevationOverlay(context).apply {
shapeAppearanceModel =
ShapeAppearanceModel.builder(
context,
MR.style.ShapeAppearance_Material3_Corner_ExtraLarge,
MR.style.ShapeAppearanceOverlay_Material3_Corner_Top)
.build()
fillColor = context.getAttrColorCompat(MR.attr.colorSurfaceContainerHigh)
}
}
+ binding.queueClear?.setOnClickListener { playbackModel.clearQueue() }
}
normalCornerSize = playbackSheetBehavior.sheetBackgroundDrawable.topLeftCornerResolvedSize
maxScaleXDistance =
context.getDimen(MR.dimen.m3_back_progress_bottom_container_max_scale_x_distance)
binding.playbackSheet.elevation = 0f
binding.mainScrim.setOnClickListener { binding.homeNewPlaylistFab.close() }
binding.sheetScrim.setOnClickListener { binding.homeNewPlaylistFab.close() }
binding.homeShuffleFab.setOnClickListener { playbackModel.shuffleAll() }
binding.homeNewPlaylistFab.apply {
inflate(R.menu.new_playlist_actions)
setOnActionSelectedListener(this@MainFragment)
setChangeListener(::updateSpeedDial)
}
forceHideAllFabs()
updateSpeedDial(false)
updateFabVisibility(
binding,
homeModel.songList.value,
homeModel.isFastScrolling.value,
homeModel.currentTabType.value)
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt
index 67d8160f..eec41628 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt
@@ -536,50 +536,56 @@ constructor(
fun addToQueue(genre: Genre) {
L.d("Adding $genre to queue")
playbackManager.addToQueue(listSettings.genreSongSort.songs(genre.songs))
}
/**
* Add a [Playlist] to the end of the queue.
*
* @param playlist The [Playlist] to add.
*/
fun addToQueue(playlist: Playlist) {
L.d("Adding $playlist to queue")
playbackManager.addToQueue(playlist.songs)
}
/**
* Add [Song]s to the end of the queue.
*
* @param songs The [Song]s to add.
*/
fun addToQueue(songs: List<Song>) {
L.d("Adding ${songs.size} songs to queue")
playbackManager.addToQueue(songs)
}
+ /** Remove all songs from the queue except the one currently playing. */
+ fun clearQueue() {
+ L.d("Clearing queue")
+ playbackManager.clearQueue()
+ }
+
// --- STATUS FUNCTIONS ---
/** Toggle [isPlaying] (i.e from playing to paused) */
fun togglePlaying() {
L.d("Toggling playing state")
playbackManager.playing(!playbackManager.progression.isPlaying)
}
/** Toggle [isShuffled] (ex. from on to off) */
fun toggleShuffled() {
L.d("Toggling shuffled state")
playbackManager.shuffled(!playbackManager.isShuffled)
}
/**
* Toggle [repeatMode] (ex. from [RepeatMode.NONE] to [RepeatMode.TRACK])
*
* @see RepeatMode.increment
*/
fun toggleRepeatMode() {
L.d("Toggling repeat mode")
playbackManager.repeatMode(playbackManager.repeatMode.increment())
}
// --- UI CONTROL ---
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt
index e0aede3a..a3018fdc 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt
@@ -155,50 +155,55 @@ interface PlaybackStateManager {
*/
fun addToQueue(songs: List<Song>)
/**
* Add a [Song] to the end of the queue.
*
* @param song The [Song] to add.
*/
fun addToQueue(song: Song) = addToQueue(listOf(song))
/**
* Move a [Song] in the queue.
*
* @param src The position of the [Song] to move in the queue.
* @param dst The destination position in the queue.
*/
fun moveQueueItem(src: Int, dst: Int)
/**
* Remove a [Song] from the queue.
*
* @param at The position of the [Song] to remove in the queue.
*/
fun removeQueueItem(at: Int)
+ /**
+ * Remove all songs from the queue except the one currently playing.
+ */
+ fun clearQueue()
+
/**
* (Re)shuffle or (Re)order this instance.
*
* @param shuffled Whether to shuffle the queue or not.
*/
fun shuffled(shuffled: Boolean)
/**
* Acknowledges that an event has happened that modified the state held by the current
* [PlaybackStateHolder].
*
* @param stateHolder The [PlaybackStateHolder] to synchronize with. Must be the current
* [PlaybackStateHolder]. Does nothing if invoked by another [PlaybackStateHolder]
* implementation.
* @param ack The [StateAck] to acknowledge.
*/
fun ack(stateHolder: PlaybackStateHolder, ack: StateAck)
/**
* Start a [DeferredPlayback] for the current [PlaybackStateHolder] to handle eventually.
*
* @param action The [DeferredPlayback] to perform.
*/
fun playDeferred(action: DeferredPlayback)
@@ -494,50 +499,61 @@ class PlaybackStateManagerImpl @Inject constructor() : PlaybackStateManager {
L.d("Adding ${songs.size} songs to end of queue")
stateHolder.addToQueue(songs, StateAck.AddToQueue(queue.size, songs.size))
}
}
private class QueueCommand(override val queue: List<Song>) : PlaybackCommand {
override val song: Song? = null
override val parent: MusicParent? = null
override val shuffled = false
}
@Synchronized
override fun moveQueueItem(src: Int, dst: Int) {
val stateHolder = stateHolder ?: return
L.d("Moving item $src to position $dst")
stateHolder.move(src, dst, StateAck.Move(src, dst))
}
@Synchronized
override fun removeQueueItem(at: Int) {
val stateHolder = stateHolder ?: return
L.d("Removing item at $at")
stateHolder.remove(at, StateAck.Remove(at))
}
+ @Synchronized
+ override fun clearQueue() {
+ val stateHolder = stateHolder ?: return
+ L.d("Clearing queue except current song")
+ for (i in stateMirror.queue.indices.reversed()) {
+ if (i != stateMirror.index) {
+ stateHolder.remove(i, StateAck.Remove(i))
+ }
+ }
+ }
+
@Synchronized
override fun shuffled(shuffled: Boolean) {
val stateHolder = stateHolder ?: return
L.d("Reordering queue [shuffled=$shuffled]")
stateHolder.shuffled(shuffled)
}
// --- INTERNAL PLAYER FUNCTIONS ---
@Synchronized
override fun playDeferred(action: DeferredPlayback) {
val stateHolder = stateHolder
if (stateHolder == null || !stateHolder.handleDeferred(action)) {
L.d("Internal player not present or did not consume action, waiting")
pendingDeferredPlayback = action
}
}
@Synchronized
override fun requestAction(stateHolder: PlaybackStateHolder) {
if (BuildConfig.DEBUG && this.stateHolder !== stateHolder) {
L.w("Given internal player did not match current internal player")
return
}
diff --git a/app/src/main/res/layout-w720dp/fragment_main.xml b/app/src/main/res/layout-w720dp/fragment_main.xml
index c7cd805f..90aa551f 100644
--- a/app/src/main/res/layout-w720dp/fragment_main.xml
+++ b/app/src/main/res/layout-w720dp/fragment_main.xml
@@ -86,54 +86,75 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
style="@style/Widget.Auxio.DisableDropShadows"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/playback_panel_fragment"
android:name="org.oxycblt.auxio.playback.PlaybackPanelFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintEnd_toStartOf="@+id/queue_sheet"
app:layout_constraintStart_toStartOf="parent" />
<LinearLayout
android:id="@+id/queue_sheet"
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/playback_panel_fragment">
- <TextView
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/queue_header"
android:layout_width="match_parent"
- android:layout_height="64dp"
- android:gravity="center"
- android:text="@string/lbl_queue"
- android:textAppearance="@style/TextAppearance.Auxio.LabelLarge"
- android:textColor="?attr/colorOnSurfaceVariant"
- app:layout_constraintBottom_toBottomOf="@+id/handle"
- app:layout_constraintEnd_toEndOf="@+id/handle"
- app:layout_constraintStart_toStartOf="parent" />
+ android:layout_height="64dp">
+
+ <TextView
+ android:id="@+id/queue_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/spacing_small"
+ android:layout_gravity="center_vertical"
+ android:text="@string/lbl_queue"
+ android:textAppearance="@style/TextAppearance.Auxio.LabelLarge"
+ android:textColor="?attr/colorOnSurfaceVariant"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <org.oxycblt.auxio.ui.RippleFixMaterialButton
+ android:id="@+id/queue_clear"
+ style="@style/Widget.Auxio.Button.Icon.Small.Secondary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/spacing_small"
+ android:contentDescription="@string/desc_clear_queue"
+ app:icon="@drawable/ic_delete_24"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/queue_fragment"
android:name="org.oxycblt.auxio.playback.queue.QueueFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:id="@+id/sheet_scrim"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml
index 9c4de0ad..cb6223e2 100644
--- a/app/src/main/res/layout/fragment_main.xml
+++ b/app/src/main/res/layout/fragment_main.xml
@@ -101,48 +101,60 @@
android:focusable="true"
android:orientation="vertical"
app:layout_behavior="org.oxycblt.auxio.playback.queue.QueueBottomSheetBehavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queue_handle_wrapper"
android:layout_width="match_parent"
android:layout_height="@dimen/size_touchable_large"
android:contentDescription="@string/desc_queue_bar">
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
android:id="@+id/queue_handle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/spacing_medium"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/queue_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/lbl_queue"
android:textAppearance="@style/TextAppearance.Auxio.LabelLarge"
android:textColor="?attr/colorOnSurfaceVariant"
app:layout_constraintBottom_toBottomOf="@+id/queue_handle"
- app:layout_constraintEnd_toEndOf="@+id/queue_handle"
+ app:layout_constraintEnd_toStartOf="@id/queue_clear"
app:layout_constraintStart_toStartOf="parent" />
+ <org.oxycblt.auxio.ui.RippleFixMaterialButton
+ android:id="@+id/queue_clear"
+ style="@style/Widget.Auxio.Button.Icon.Small.Secondary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/spacing_small"
+ android:contentDescription="@string/desc_clear_queue"
+ app:icon="@drawable/ic_delete_24"
+ app:layout_constraintBottom_toBottomOf="@+id/queue_handle"
+ app:layout_constraintEnd_toEndOf="@+id/queue_handle"
+ app:layout_constraintTop_toTopOf="@+id/queue_handle" />
+
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/queue_fragment"
android:name="org.oxycblt.auxio.playback.queue.QueueFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<View
android:id="@+id/sheet_scrim"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0b5830db..cae8d340 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -106,50 +106,51 @@
<!-- As in to not filter -->
<string name="lbl_filter_all">All</string>
<string name="lbl_name">Name</string>
<string name="lbl_date">Date</string>
<string name="lbl_duration">Duration</string>
<string name="lbl_song_count">Song count</string>
<string name="lbl_disc">Disc</string>
<string name="lbl_track">Track</string>
<string name="lbl_date_added">Date added</string>
<string name="lbl_sort">Sort</string>
<string name="lbl_sort_mode">Sort by</string>
<string name="lbl_sort_direction">Direction</string>
<string name="lbl_sort_asc">Ascending</string>
<string name="lbl_sort_dsc">Descending</string>
<string name="lbl_playback">Now playing</string>
<string name="lbl_equalizer">Equalizer</string>
<string name="lbl_play">Play</string>
<string name="lbl_shuffle">Shuffle</string>
<string name="lbl_queue">Queue</string>
<string name="lbl_play_next">Play next</string>
<string name="lbl_queue_add">Add to queue</string>
+ <string name="lbl_clear_queue">Clear queue</string>
<string name="lbl_playlist_add">Add to playlist</string>
<string name="lbl_artist_details">Go to artist</string>
<string name="lbl_album_details">Go to album</string>
<string name="lbl_song_detail">View properties</string>
<string name="lbl_parent_detail">View</string>
<string name="lbl_share">Share</string>
<string name="lbl_props">Song properties</string>
<string name="lbl_path">Path</string>
<!-- As in audio format -->
<string name="lbl_format">Format</string>
<!-- As in file size -->
<string name="lbl_size">Size</string>
<string name="lbl_bitrate">Bit rate</string>
<string name="lbl_sample_rate">Sample rate</string>
<string name="lbl_replaygain_track">ReplayGain Track Adjustment</string>
<string name="lbl_replaygain_album">ReplayGain Album Adjustment</string>
<!-- Limit to 10 characters -->
<string name="lbl_shuffle_shortcut_short">Shuffle</string>
<!-- Limit to 25 characters -->
<string name="lbl_shuffle_shortcut_long">Shuffle all</string>
<string name="lbl_start_playback">Start playback</string>
@@ -323,50 +324,51 @@
<!-- Error Namespace | Error Labels -->
<string name="err_index_failed">Music loading failed</string>
<string name="err_import_failed">Unable to import a playlist from this file</string>
<string name="err_export_failed">Unable to export the playlist to this file</string>
<string name="err_no_app">No app found that can handle this task</string>
<!-- No folders in the "Music Folders" setting -->
<string name="err_bad_location">This folder is not supported</string>
<!-- Description Namespace | Accessibility Strings -->
<string name="desc_track_number">Track %d</string>
<string name="desc_play_pause">Play or pause</string>
<string name="desc_skip_next">Skip to next song</string>
<string name="desc_skip_prev">Skip to last song</string>
<string name="desc_change_repeat">Change repeat mode</string>
<string name="desc_shuffle">Turn shuffle on or off</string>
<string name="desc_exit">Stop playback</string>
<string name="desc_remove_song">Remove this song</string>
<string name="desc_song_handle">Move this song</string>
<string name="desc_queue_bar">Open the queue</string>
<string name="desc_tab_handle">Move this tab</string>
<string name="desc_clear_search">Clear search query</string>
+ <string name="desc_clear_queue">Clear queue</string>
<string name="desc_music_location_delete">Remove folder</string>
<string name="desc_auxio_icon">Auxio icon</string>
<string name="desc_no_cover">Album cover</string>
<string name="desc_album_cover">Album cover for %s</string>
<string name="desc_artist_image">Artist image for %s</string>
<string name="desc_genre_image">Genre image for %s</string>
<string name="desc_playlist_image">Playlist image for %s</string>
<string name="desc_selection_image">Selection image</string>
<!-- Default Namespace | Placeholder values -->
<eat-comment />
<string name="def_album">Unknown album</string>
<string name="def_artist">Unknown artist</string>
<string name="def_genre">Unknown genre</string>
<string name="def_date">No date</string>
<string name="def_disc">No disc</string>
<string name="def_track">No track</string>
<string name="def_song_count">No songs</string>
<string name="def_album_count">No albums</string>
<string name="def_playback">No music playing</string>
<!-- Codec Namespace | Format names -->
<eat-comment />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment