commit
c872f7890c
26 changed files with 106 additions and 63 deletions
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -21,8 +21,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId namespace
|
||||
versionName "3.4.1"
|
||||
versionCode 42
|
||||
versionName "3.4.2"
|
||||
versionCode 43
|
||||
|
||||
minSdk 24
|
||||
targetSdk 34
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
3
fastlane/metadata/android/en-US/changelogs/43.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/43.txt
Normal 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
|
Loading…
Reference in a new issue