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
## 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
#### What's Fixed

View file

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

View file

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

View file

@ -95,7 +95,7 @@ class AlbumListFragment :
}
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.
return when (homeModel.albumSort.mode) {
// By Name -> Use Name

View file

@ -90,7 +90,7 @@ class ArtistListFragment :
}
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.
return when (homeModel.artistSort.mode) {
// By Name -> Use Name

View file

@ -89,7 +89,7 @@ class GenreListFragment :
}
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.
return when (homeModel.genreSort.mode) {
// By Name -> Use Name

View file

@ -87,7 +87,7 @@ class PlaylistListFragment :
}
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.
return when (homeModel.playlistSort.mode) {
// By Name -> Use Name

View file

@ -92,7 +92,7 @@ class SongListFragment :
}
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.
// 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.

View file

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

View file

@ -153,6 +153,9 @@ interface PlaybackStateHolder {
* ack.
*/
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 {
val stateHolder = stateHolder ?: return
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
}
val stateHolder = stateHolder ?: return
// 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
// 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.
fun pointingAtSong(): Boolean {
fun pointingAtSong(index: Int): Boolean {
val currentSong =
if (shuffledMapping.isNotEmpty()) {
shuffledMapping.getOrNull(savedState.index)?.let { heap.getOrNull(it) }
shuffledMapping.getOrNull(index)?.let { heap.getOrNull(it) }
} else {
heap.getOrNull(savedState.index)
heap.getOrNull(index)
}
logD(currentSong)
return currentSong?.uid == savedState.songUid
}
var index = savedState.index
while (!pointingAtSong() && index > -1) {
while (!pointingAtSong(index) && index > -1) {
index--
}
@ -763,32 +766,30 @@ class PlaybackStateManagerImpl @Inject constructor() : PlaybackStateManager {
"Queue inconsistency detected: Shuffled mapping indices out of heap bounds"
}
if (index < 0) {
stateHolder.reset(StateAck.NewPlayback)
return
}
val rawQueue =
RawQueue(
heap = heap,
shuffledMapping = shuffledMapping,
heapIndex =
if (shuffledMapping.isNotEmpty()) {
shuffledMapping[savedState.index]
shuffledMapping[index]
} else {
index
})
if (index > -1) {
// Valid state where something needs to be played, direct the stateholder to apply
// this new state.
val oldStateMirror = stateMirror
if (oldStateMirror.rawQueue != rawQueue) {
logD("Queue changed, must reload player")
stateHolder?.applySavedState(parent, rawQueue, StateAck.NewPlayback)
stateHolder?.playing(false)
}
if (oldStateMirror.progression.calculateElapsedPositionMs() != savedState.positionMs) {
logD("Seeking to saved position ${savedState.positionMs}ms")
stateHolder?.seekTo(savedState.positionMs)
stateHolder?.playing(false)
}
// Valid state where something needs to be played, direct the stateholder to apply
// this new state.
val oldStateMirror = stateMirror
if (oldStateMirror.rawQueue != rawQueue) {
logD("Queue changed, must reload player")
stateHolder.playing(false)
stateHolder.applySavedState(parent, rawQueue, StateAck.NewPlayback)
stateHolder.seekTo(savedState.positionMs)
}
isInitialized = true

View file

@ -68,8 +68,16 @@ class BetterShuffleOrder(private val shuffled: IntArray) : ShuffleOrder {
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 pivot = indexInShuffled[insertionIndex]
val pivot: Int =
if (insertionIndex < shuffled.size) {
indexInShuffled[insertionIndex]
} else {
indexInShuffled.size
}
for (i in shuffled.indices) {
var currentIndex = shuffled[i]
if (currentIndex > insertionIndex) {
@ -82,8 +90,14 @@ class BetterShuffleOrder(private val shuffled: IntArray) : ShuffleOrder {
newShuffled[i + insertionCount] = currentIndex
}
}
for (i in 0 until insertionCount) {
newShuffled[pivot + i + 1] = insertionIndex + i + 1
if (insertionIndex < shuffled.size) {
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)
}

View file

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

View file

@ -158,6 +158,7 @@ class WidgetProvider : AppWidgetProvider() {
uiSettings,
)
.setupCover(context, state.takeIf { canDisplayWaferCover(uiSettings) })
.setupFillingCover(uiSettings)
.setupTimelineControls(context, state)
private fun newWideWaferLayout(
@ -170,6 +171,7 @@ class WidgetProvider : AppWidgetProvider() {
uiSettings,
)
.setupCover(context, state.takeIf { canDisplayWaferCover(uiSettings) })
.setupFillingCover(uiSettings)
.setupFullControls(context, state)
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.
val background =
if (useRoundedRemoteViews(uiSettings)) {
R.drawable.ui_widget_bar_round
R.drawable.ui_widget_bg_round
} else {
R.drawable.ui_widget_bar_system
R.drawable.ui_widget_bg_sharp
}
setBackgroundResource(R.id.widget_controls, background)
return this
@ -253,7 +255,7 @@ class WidgetProvider : AppWidgetProvider() {
if (useRoundedRemoteViews(uiSettings)) {
R.drawable.ui_widget_bg_round
} else {
R.drawable.ui_widget_bg_system
R.drawable.ui_widget_bg_sharp
}
setBackgroundResource(android.R.id.background, background)
return this
@ -292,6 +294,20 @@ class WidgetProvider : AppWidgetProvider() {
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
* them.

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@android:dimen/system_app_widget_background_radius" />
<solid android:color="@android:color/white" />
<corners android:radius="@android:dimen/system_app_widget_background_radius" />
</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_alignParentBottom="true"
android:layout_gravity="center"
android:background="@drawable/ui_widget_bar_system"
android:background="@drawable/ui_widget_bg_round"
android:backgroundTint="?attr/colorSurface"
android:orientation="horizontal"
android:padding="@dimen/spacing_mid_medium">

View file

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

View file

@ -4,7 +4,7 @@
android:id="@android:id/background"
android:layout_width="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:theme="@style/Theme.Auxio.Widget">

View file

@ -4,7 +4,7 @@
android:id="@android:id/background"
android:layout_width="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:theme="@style/Theme.Auxio.Widget">

View file

@ -4,7 +4,7 @@
android:id="@android:id/background"
android:layout_width="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:baselineAligned="false"
android:orientation="horizontal"
@ -20,6 +20,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:background="@drawable/ui_widget_bg_round"
android:clipToOutline="true"
tools:ignore="ContentDescription" />
<android.widget.LinearLayout

View file

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

View file

@ -12,7 +12,7 @@ buildscript {
}
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 "org.jetbrains.kotlin.android" version "$kotlin_version" 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