Merge pull request #732 from OxygenCobalt/hotfixes

Version 3.4.2
This commit is contained in:
Alexander Capehart 2024-02-28 23:12:21 -07:00 committed by GitHub
commit c872f7890c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 106 additions and 63 deletions

View file

@ -1,5 +1,14 @@
# Changelog # Changelog
## 3.4.2
#### What's Fixed
- Fixed "Add to queue" incorrectly changing the queue and crashing the app
- Fixed 1x4 and 1x3 widgets having square edges
- Fixed crash when music library updates in such a way to change music information
- Fixed crash when music library updates while scrolled in a list
- Fixed inconsistent corner radius in wafer widgets
## 3.4.1 ## 3.4.1
#### What's Fixed #### What's Fixed

View file

@ -2,8 +2,8 @@
<h1 align="center"><b>Auxio</b></h1> <h1 align="center"><b>Auxio</b></h1>
<h4 align="center">A simple, rational music player for android.</h4> <h4 align="center">A simple, rational music player for android.</h4>
<p align="center"> <p align="center">
<a href="https://github.com/oxygencobalt/Auxio/releases/tag/v3.4.1"> <a href="https://github.com/oxygencobalt/Auxio/releases/tag/v3.4.2">
<img alt="Latest Version" src="https://img.shields.io/static/v1?label=tag&message=v3.4.1&color=64B5F6&style=flat"> <img alt="Latest Version" src="https://img.shields.io/static/v1?label=tag&message=v3.4.2&color=64B5F6&style=flat">
</a> </a>
<a href="https://github.com/oxygencobalt/Auxio/releases/"> <a href="https://github.com/oxygencobalt/Auxio/releases/">
<img alt="Releases" src="https://img.shields.io/github/downloads/OxygenCobalt/Auxio/total.svg?color=4B95DE&style=flat"> <img alt="Releases" src="https://img.shields.io/github/downloads/OxygenCobalt/Auxio/total.svg?color=4B95DE&style=flat">

View file

@ -21,8 +21,8 @@ android {
defaultConfig { defaultConfig {
applicationId namespace applicationId namespace
versionName "3.4.1" versionName "3.4.2"
versionCode 42 versionCode 43
minSdk 24 minSdk 24
targetSdk 34 targetSdk 34

View file

@ -95,7 +95,7 @@ class AlbumListFragment :
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {
val album = homeModel.albumList.value[pos] val album = homeModel.albumList.value.getOrNull(pos) ?: return null
// Change how we display the popup depending on the current sort mode. // Change how we display the popup depending on the current sort mode.
return when (homeModel.albumSort.mode) { return when (homeModel.albumSort.mode) {
// By Name -> Use Name // By Name -> Use Name

View file

@ -90,7 +90,7 @@ class ArtistListFragment :
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {
val artist = homeModel.artistList.value[pos] val artist = homeModel.artistList.value.getOrNull(pos) ?: return null
// Change how we display the popup depending on the current sort mode. // Change how we display the popup depending on the current sort mode.
return when (homeModel.artistSort.mode) { return when (homeModel.artistSort.mode) {
// By Name -> Use Name // By Name -> Use Name

View file

@ -89,7 +89,7 @@ class GenreListFragment :
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {
val genre = homeModel.genreList.value[pos] val genre = homeModel.genreList.value.getOrNull(pos) ?: return null
// Change how we display the popup depending on the current sort mode. // Change how we display the popup depending on the current sort mode.
return when (homeModel.genreSort.mode) { return when (homeModel.genreSort.mode) {
// By Name -> Use Name // By Name -> Use Name

View file

@ -87,7 +87,7 @@ class PlaylistListFragment :
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {
val playlist = homeModel.playlistList.value[pos] val playlist = homeModel.playlistList.value.getOrNull(pos) ?: return null
// Change how we display the popup depending on the current sort mode. // Change how we display the popup depending on the current sort mode.
return when (homeModel.playlistSort.mode) { return when (homeModel.playlistSort.mode) {
// By Name -> Use Name // By Name -> Use Name

View file

@ -92,7 +92,7 @@ class SongListFragment :
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {
val song = homeModel.songList.value[pos] val song = homeModel.songList.value.getOrNull(pos) ?: return null
// Change how we display the popup depending on the current sort mode. // Change how we display the popup depending on the current sort mode.
// Note: We don't use the more correct individual artist name here, as sorts are largely // Note: We don't use the more correct individual artist name here, as sorts are largely
// based off the names of the parent objects and not the child objects. // based off the names of the parent objects and not the child objects.

View file

@ -305,35 +305,35 @@ constructor(
val userLibrary = synchronized(this) { userLibrary ?: return } val userLibrary = synchronized(this) { userLibrary ?: return }
logD("Creating playlist $name with ${songs.size} songs") logD("Creating playlist $name with ${songs.size} songs")
userLibrary.createPlaylist(name, songs) userLibrary.createPlaylist(name, songs)
dispatchLibraryChange(device = false, user = true) withContext(Dispatchers.Main) { dispatchLibraryChange(device = false, user = true) }
} }
override suspend fun renamePlaylist(playlist: Playlist, name: String) { override suspend fun renamePlaylist(playlist: Playlist, name: String) {
val userLibrary = synchronized(this) { userLibrary ?: return } val userLibrary = synchronized(this) { userLibrary ?: return }
logD("Renaming $playlist to $name") logD("Renaming $playlist to $name")
userLibrary.renamePlaylist(playlist, name) userLibrary.renamePlaylist(playlist, name)
dispatchLibraryChange(device = false, user = true) withContext(Dispatchers.Main) { dispatchLibraryChange(device = false, user = true) }
} }
override suspend fun deletePlaylist(playlist: Playlist) { override suspend fun deletePlaylist(playlist: Playlist) {
val userLibrary = synchronized(this) { userLibrary ?: return } val userLibrary = synchronized(this) { userLibrary ?: return }
logD("Deleting $playlist") logD("Deleting $playlist")
userLibrary.deletePlaylist(playlist) userLibrary.deletePlaylist(playlist)
dispatchLibraryChange(device = false, user = true) withContext(Dispatchers.Main) { dispatchLibraryChange(device = false, user = true) }
} }
override suspend fun addToPlaylist(songs: List<Song>, playlist: Playlist) { override suspend fun addToPlaylist(songs: List<Song>, playlist: Playlist) {
val userLibrary = synchronized(this) { userLibrary ?: return } val userLibrary = synchronized(this) { userLibrary ?: return }
logD("Adding ${songs.size} songs to $playlist") logD("Adding ${songs.size} songs to $playlist")
userLibrary.addToPlaylist(playlist, songs) userLibrary.addToPlaylist(playlist, songs)
dispatchLibraryChange(device = false, user = true) withContext(Dispatchers.Main) { dispatchLibraryChange(device = false, user = true) }
} }
override suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>) { override suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>) {
val userLibrary = synchronized(this) { userLibrary ?: return } val userLibrary = synchronized(this) { userLibrary ?: return }
logD("Rewriting $playlist with ${songs.size} songs") logD("Rewriting $playlist with ${songs.size} songs")
userLibrary.rewritePlaylist(playlist, songs) userLibrary.rewritePlaylist(playlist, songs)
dispatchLibraryChange(device = false, user = true) withContext(Dispatchers.Main) { dispatchLibraryChange(device = false, user = true) }
} }
@Synchronized @Synchronized

View file

@ -153,6 +153,9 @@ interface PlaybackStateHolder {
* ack. * ack.
*/ */
fun applySavedState(parent: MusicParent?, rawQueue: RawQueue, ack: StateAck.NewPlayback?) fun applySavedState(parent: MusicParent?, rawQueue: RawQueue, ack: StateAck.NewPlayback?)
/** Reset this instance to an empty state. */
fun reset(ack: StateAck.NewPlayback)
} }
/** /**

View file

@ -492,7 +492,7 @@ class PlaybackStateManagerImpl @Inject constructor() : PlaybackStateManager {
} else { } else {
val stateHolder = stateHolder ?: return val stateHolder = stateHolder ?: return
logD("Adding ${songs.size} songs to end of queue") logD("Adding ${songs.size} songs to end of queue")
stateHolder.addToQueue(songs, StateAck.AddToQueue(stateMirror.index + 1, songs.size)) stateHolder.addToQueue(songs, StateAck.AddToQueue(queue.size, songs.size))
} }
} }
@ -717,6 +717,8 @@ class PlaybackStateManagerImpl @Inject constructor() : PlaybackStateManager {
return return
} }
val stateHolder = stateHolder ?: return
// The heap may not be the same if the song composition changed between state saves/reloads. // The heap may not be the same if the song composition changed between state saves/reloads.
// This also means that we must modify the shuffled mapping as well, in what it points to // This also means that we must modify the shuffled mapping as well, in what it points to
// and it's general composition. // and it's general composition.
@ -741,19 +743,20 @@ class PlaybackStateManagerImpl @Inject constructor() : PlaybackStateManager {
} }
// Make sure we re-align the index to point to the previously playing song. // Make sure we re-align the index to point to the previously playing song.
fun pointingAtSong(): Boolean { fun pointingAtSong(index: Int): Boolean {
val currentSong = val currentSong =
if (shuffledMapping.isNotEmpty()) { if (shuffledMapping.isNotEmpty()) {
shuffledMapping.getOrNull(savedState.index)?.let { heap.getOrNull(it) } shuffledMapping.getOrNull(index)?.let { heap.getOrNull(it) }
} else { } else {
heap.getOrNull(savedState.index) heap.getOrNull(index)
} }
logD(currentSong)
return currentSong?.uid == savedState.songUid return currentSong?.uid == savedState.songUid
} }
var index = savedState.index var index = savedState.index
while (!pointingAtSong() && index > -1) { while (!pointingAtSong(index) && index > -1) {
index-- index--
} }
@ -763,32 +766,30 @@ class PlaybackStateManagerImpl @Inject constructor() : PlaybackStateManager {
"Queue inconsistency detected: Shuffled mapping indices out of heap bounds" "Queue inconsistency detected: Shuffled mapping indices out of heap bounds"
} }
if (index < 0) {
stateHolder.reset(StateAck.NewPlayback)
return
}
val rawQueue = val rawQueue =
RawQueue( RawQueue(
heap = heap, heap = heap,
shuffledMapping = shuffledMapping, shuffledMapping = shuffledMapping,
heapIndex = heapIndex =
if (shuffledMapping.isNotEmpty()) { if (shuffledMapping.isNotEmpty()) {
shuffledMapping[savedState.index] shuffledMapping[index]
} else { } else {
index index
}) })
if (index > -1) { // Valid state where something needs to be played, direct the stateholder to apply
// Valid state where something needs to be played, direct the stateholder to apply // this new state.
// this new state. val oldStateMirror = stateMirror
val oldStateMirror = stateMirror if (oldStateMirror.rawQueue != rawQueue) {
if (oldStateMirror.rawQueue != rawQueue) { logD("Queue changed, must reload player")
logD("Queue changed, must reload player") stateHolder.playing(false)
stateHolder?.applySavedState(parent, rawQueue, StateAck.NewPlayback) stateHolder.applySavedState(parent, rawQueue, StateAck.NewPlayback)
stateHolder?.playing(false) stateHolder.seekTo(savedState.positionMs)
}
if (oldStateMirror.progression.calculateElapsedPositionMs() != savedState.positionMs) {
logD("Seeking to saved position ${savedState.positionMs}ms")
stateHolder?.seekTo(savedState.positionMs)
stateHolder?.playing(false)
}
} }
isInitialized = true isInitialized = true

View file

@ -68,8 +68,16 @@ class BetterShuffleOrder(private val shuffled: IntArray) : ShuffleOrder {
return BetterShuffleOrder(insertionCount, -1) return BetterShuffleOrder(insertionCount, -1)
} }
// TODO: Fix this scuffed hacky logic
// TODO: Play next ordering needs to persist in unshuffle
val newShuffled = IntArray(shuffled.size + insertionCount) val newShuffled = IntArray(shuffled.size + insertionCount)
val pivot = indexInShuffled[insertionIndex] val pivot: Int =
if (insertionIndex < shuffled.size) {
indexInShuffled[insertionIndex]
} else {
indexInShuffled.size
}
for (i in shuffled.indices) { for (i in shuffled.indices) {
var currentIndex = shuffled[i] var currentIndex = shuffled[i]
if (currentIndex > insertionIndex) { if (currentIndex > insertionIndex) {
@ -82,8 +90,14 @@ class BetterShuffleOrder(private val shuffled: IntArray) : ShuffleOrder {
newShuffled[i + insertionCount] = currentIndex newShuffled[i + insertionCount] = currentIndex
} }
} }
for (i in 0 until insertionCount) { if (insertionIndex < shuffled.size) {
newShuffled[pivot + i + 1] = insertionIndex + i + 1 for (i in 0 until insertionCount) {
newShuffled[pivot + i + 1] = insertionIndex + i + 1
}
} else {
for (i in 0 until insertionCount) {
newShuffled[pivot + i] = insertionIndex + i
}
} }
return BetterShuffleOrder(newShuffled) return BetterShuffleOrder(newShuffled)
} }

View file

@ -485,6 +485,11 @@ class PlaybackService :
ack?.let { playbackManager.ack(this, it) } ack?.let { playbackManager.ack(this, it) }
} }
override fun reset(ack: StateAck.NewPlayback) {
player.setMediaItems(emptyList())
playbackManager.ack(this, ack)
}
// --- PLAYER OVERRIDES --- // --- PLAYER OVERRIDES ---
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {

View file

@ -158,6 +158,7 @@ class WidgetProvider : AppWidgetProvider() {
uiSettings, uiSettings,
) )
.setupCover(context, state.takeIf { canDisplayWaferCover(uiSettings) }) .setupCover(context, state.takeIf { canDisplayWaferCover(uiSettings) })
.setupFillingCover(uiSettings)
.setupTimelineControls(context, state) .setupTimelineControls(context, state)
private fun newWideWaferLayout( private fun newWideWaferLayout(
@ -170,6 +171,7 @@ class WidgetProvider : AppWidgetProvider() {
uiSettings, uiSettings,
) )
.setupCover(context, state.takeIf { canDisplayWaferCover(uiSettings) }) .setupCover(context, state.takeIf { canDisplayWaferCover(uiSettings) })
.setupFillingCover(uiSettings)
.setupFullControls(context, state) .setupFullControls(context, state)
private fun newThinDockedLayout( private fun newThinDockedLayout(
@ -231,9 +233,9 @@ class WidgetProvider : AppWidgetProvider() {
// On API 31+, the bar should always be round in order to fit in with other widgets. // On API 31+, the bar should always be round in order to fit in with other widgets.
val background = val background =
if (useRoundedRemoteViews(uiSettings)) { if (useRoundedRemoteViews(uiSettings)) {
R.drawable.ui_widget_bar_round R.drawable.ui_widget_bg_round
} else { } else {
R.drawable.ui_widget_bar_system R.drawable.ui_widget_bg_sharp
} }
setBackgroundResource(R.id.widget_controls, background) setBackgroundResource(R.id.widget_controls, background)
return this return this
@ -253,7 +255,7 @@ class WidgetProvider : AppWidgetProvider() {
if (useRoundedRemoteViews(uiSettings)) { if (useRoundedRemoteViews(uiSettings)) {
R.drawable.ui_widget_bg_round R.drawable.ui_widget_bg_round
} else { } else {
R.drawable.ui_widget_bg_system R.drawable.ui_widget_bg_sharp
} }
setBackgroundResource(android.R.id.background, background) setBackgroundResource(android.R.id.background, background)
return this return this
@ -292,6 +294,20 @@ class WidgetProvider : AppWidgetProvider() {
return this return this
} }
private fun RemoteViews.setupFillingCover(uiSettings: UISettings): RemoteViews {
// Below API 31, enable a rounded background only if round mode is enabled.
// On API 31+, the background should always be round in order to fit in with other
// widgets.
val background =
if (useRoundedRemoteViews(uiSettings)) {
R.drawable.ui_widget_bg_round
} else {
R.drawable.ui_widget_bg_sharp
}
setBackgroundResource(R.id.widget_cover, background)
return this
}
/** /**
* Set up the album cover, song title, and artist name in a [RemoteViews] layout that contains * Set up the album cover, song title, and artist name in a [RemoteViews] layout that contains
* them. * them.

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<corners android:radius="@android:dimen/system_app_widget_background_radius" />
<solid android:color="@android:color/white" /> <solid android:color="@android:color/white" />
<corners android:radius="@android:dimen/system_app_widget_background_radius" />
</shape> </shape>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/size_corners_mid_large" />
<solid android:color="@android:color/white" />
</shape>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white" />
</shape>

View file

@ -61,7 +61,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_gravity="center" android:layout_gravity="center"
android:background="@drawable/ui_widget_bar_system" android:background="@drawable/ui_widget_bg_round"
android:backgroundTint="?attr/colorSurface" android:backgroundTint="?attr/colorSurface"
android:orientation="horizontal" android:orientation="horizontal"
android:padding="@dimen/spacing_mid_medium"> android:padding="@dimen/spacing_mid_medium">

View file

@ -48,7 +48,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_gravity="center" android:layout_gravity="center"
android:background="@drawable/ui_widget_bar_system" android:background="@drawable/ui_widget_bg_round"
android:backgroundTint="?attr/colorSurface" android:backgroundTint="?attr/colorSurface"
android:orientation="horizontal" android:orientation="horizontal"
android:padding="@dimen/spacing_mid_medium"> android:padding="@dimen/spacing_mid_medium">

View file

@ -4,7 +4,7 @@
android:id="@android:id/background" android:id="@android:id/background"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/ui_widget_bg_system" android:background="@drawable/ui_widget_bg_sharp"
android:backgroundTint="?attr/colorSurface" android:backgroundTint="?attr/colorSurface"
android:theme="@style/Theme.Auxio.Widget"> android:theme="@style/Theme.Auxio.Widget">

View file

@ -4,7 +4,7 @@
android:id="@android:id/background" android:id="@android:id/background"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/ui_widget_bg_system" android:background="@drawable/ui_widget_bg_sharp"
android:backgroundTint="?attr/colorSurface" android:backgroundTint="?attr/colorSurface"
android:theme="@style/Theme.Auxio.Widget"> android:theme="@style/Theme.Auxio.Widget">

View file

@ -4,7 +4,7 @@
android:id="@android:id/background" android:id="@android:id/background"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/ui_widget_bg_system" android:background="@drawable/ui_widget_bg_sharp"
android:backgroundTint="?attr/colorSurface" android:backgroundTint="?attr/colorSurface"
android:baselineAligned="false" android:baselineAligned="false"
android:orientation="horizontal" android:orientation="horizontal"
@ -20,6 +20,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:background="@drawable/ui_widget_bg_round"
android:clipToOutline="true"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<android.widget.LinearLayout <android.widget.LinearLayout

View file

@ -4,22 +4,23 @@
android:id="@android:id/background" android:id="@android:id/background"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/ui_widget_bg_system" android:background="@drawable/ui_widget_bg_sharp"
android:backgroundTint="?attr/colorSurface" android:backgroundTint="?attr/colorSurface"
android:baselineAligned="false" android:baselineAligned="false"
android:orientation="horizontal" android:orientation="horizontal"
android:theme="@style/Theme.Auxio.Widget"> android:theme="@style/Theme.Auxio.Widget">
<!-- <!--
Wrapping the 1:1 ImageView hack in a LinearLayout allows the view to measure greedily clipToOutline won't actually do anything before Android 12, but that's fine since we won't
without squishing the controls. show a cover then anyway.
--> -->
<android.widget.ImageView <android.widget.ImageView
android:id="@+id/widget_cover" android:id="@+id/widget_cover"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:background="@drawable/ui_widget_bg_round"
android:clipToOutline="true"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<android.widget.LinearLayout <android.widget.LinearLayout

View file

@ -12,7 +12,7 @@ buildscript {
} }
plugins { plugins {
id "com.android.application" version '8.2.0' apply false id "com.android.application" version '8.2.1' apply false
id "androidx.navigation.safeargs.kotlin" version "$navigation_version" apply false id "androidx.navigation.safeargs.kotlin" version "$navigation_version" apply false
id "org.jetbrains.kotlin.android" version "$kotlin_version" apply false id "org.jetbrains.kotlin.android" version "$kotlin_version" apply false
id "com.google.devtools.ksp" version '1.9.10-1.0.13' apply false id "com.google.devtools.ksp" version '1.9.10-1.0.13' apply false

View file

@ -0,0 +1,3 @@
Auxio 3.4.0 adds gapless playback and new widget designs, alongside a variety of fixes.
This release fixes critical issues identified in the previous version.
For more information, see https://github.com/OxygenCobalt/Auxio/releases/tag/v3.4.2