5 Commits
Author SHA1 Message Date
austinried 3bd29f75e6 release: cover the playlist info dialog 2026-10-24 02:16:45 +00:00
austinried d7a3a41ab2 metadata: refresh the artist screenshot with the new biography
Regenerate the store screenshots from the demo library. Only the artist
page changed meaningfully, so keep that capture, using Nine Inch Nails,
which has a biography and three albums.

The demo test now fetches the artist biography and passes artist art to
the home, library and search screens, so a regeneration keeps their art
instead of falling back to letter placeholders.

The captures now come from a 393x786 dp, 440 dpi phone: Pixel XL's
640x360 dp renders text far larger than a real phone shows it. That
phone renders 1080x2161, one pixel over the 2:1 limit both stores
enforce, so captures are written out at 1080x2160.
2026-10-24 02:16:45 +00:00
austinried 05e318f9ab docs: add ReplayGain to the README features 2026-10-24 02:16:45 +00:00
austinried e8fb0ac564 release: split the GitHub notes from the store changelog
The GitHub release body was the 500-char store changelog. Give the
GitHub release its own detailed notes under docs/releases/<tag>.md and
point release.yml at it, leaving the store changelog to be the trimmed
version plus a link back to the GitHub release.

The notes cover everything since 3.0.0, including ReplayGain, the artist
page work, the sort name preference and the fixes.

The store changelog still must exist; the workflow now checks both
files.
2026-10-24 02:16:45 +00:00
austinried 7379f3d93e release: bump to 3.1.0 and add its changelog
Bump versionCode to 14 and versionName to 3.1.0, and add the
changelogs/14.txt release notes covering ReplayGain, artist pages,
sort names, keep-screen-on and the fixes.

No tag: this only prepares the release.
2026-10-24 02:16:45 +00:00
13 changed files with 69 additions and 7 deletions
+4 -2
View File
@@ -85,7 +85,9 @@ jobs:
echo "value=false" >> "$GITHUB_OUTPUT"
fi
- name: Check release notes exist
run: test -f "metadata/en-US/changelogs/${{ needs.build.outputs.version-code }}.txt"
run: |
test -f "metadata/en-US/changelogs/${{ needs.build.outputs.version-code }}.txt"
test -f "docs/releases/$GITHUB_REF_NAME.md"
- name: Write Play service account
if: steps.stable.outputs.value == 'true'
env:
@@ -123,7 +125,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
args=(--verify-tag --title "$GITHUB_REF_NAME" --notes-file "metadata/en-US/changelogs/${{ needs.build.outputs.version-code }}.txt")
args=(--verify-tag --title "$GITHUB_REF_NAME" --notes-file "docs/releases/$GITHUB_REF_NAME.md")
if [ "${{ steps.stable.outputs.value }}" != "true" ]; then
args+=(--prerelease)
fi
+1
View File
@@ -37,6 +37,7 @@ See [signing/README.md](signing/README.md) for the fingerprints and the commands
- **Adaptive Theming:** Colours drawn from the current song's artwork.
- **Search:** Fast search across the whole library, with per-list sort and filter controls.
- **Playback:** Gapless playback, an editable queue you can reorder and jump through, plus loop and shuffle modes.
- **ReplayGain:** Loudness normalization from the tags your server reports, with Auto, track and album modes and a preamp.
- **Up Next:** Add items into the currently playing queue without rewriting it.
- **Multiple Servers:** Connect several servers and switch between them quickly.
- **Stream Quality:** Bitrate and format settings for Wi-Fi and mobile, with automatic switching.
+2 -2
View File
@@ -36,8 +36,8 @@ android {
applicationId = "com.subtracks"
minSdk = 24
targetSdk = 37
versionCode = 13
versionName = "3.0.0"
versionCode = 14
versionName = "3.1.0"
buildConfigField("String", "COMMIT_HASH", "\"$commitHash\"")
}
@@ -40,12 +40,14 @@ import com.subtracks.data.model.Source
import com.subtracks.data.model.coverArtKey
import com.subtracks.data.prefs.ListQuery
import com.subtracks.data.repo.SEARCH_RESULT_LIMIT
import com.subtracks.data.repo.normalizeBiography
import com.subtracks.data.source.subsonic.SubsonicSource
import com.subtracks.data.source.subsonic.TestServer
import com.subtracks.data.source.subsonic.TestServers
import com.subtracks.data.sync.SyncService
import com.subtracks.playback.PlaybackState
import com.subtracks.playback.QueueItem
import com.subtracks.ui.components.ArtistArt
import com.subtracks.ui.home.HomeFeed
import com.subtracks.ui.home.HomeScreen
import com.subtracks.ui.library.AlbumDetailScreen
@@ -82,11 +84,15 @@ import javax.imageio.ImageIO
* by `:*UnitTest` or the integration suite; run it explicitly with `gradle :app:demoScreenshots`.
* The synced database is rebuilt each run; downloaded cover art is cached under the system temp dir
* so reruns do not re-fetch it.
*
* The device is a 393x786 dp phone at 440 dpi, which renders 1080x2161. A capture one pixel over
* 1080x2160 breaks the stores' 2:1 limit, so the captures are cropped to 1080x2160 afterwards, with
* the last row taken from the row above it.
*/
@OptIn(DelicateCoilApi::class)
@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(qualifiers = RobolectricDeviceQualifiers.PixelXL)
@Config(qualifiers = "w393dp-h786dp-normal-long-notround-any-440dpi-keyshidden-nonav")
class DemoScreenshotTest {
@get:Rule
val composeRule = createAndroidComposeRule<ComponentActivity>()
@@ -123,6 +129,7 @@ class DemoScreenshotTest {
HomeScreen(
feed = demo.feed,
coverArt = demo.coverArt,
artistArt = demo::artistArt,
playingSongId = demo.homePlayingSong.id,
modifier = Modifier.fillMaxSize(),
)
@@ -178,6 +185,7 @@ class DemoScreenshotTest {
ArtistDetailScreen(
artist = demo.artist,
artistName = demo.artist.name,
bio = demo.artistBio,
albums =
ArtistAlbums(
albumCredits =
@@ -216,6 +224,7 @@ class DemoScreenshotTest {
onSync = {},
onOpenSettings = {},
artwork = demo.artwork(demo.nowPlayingAlbum.coverArt),
artistArt = demo::artistArt,
modifier = Modifier.weight(1f),
)
demo.MiniPlayerOverlay(demo.nowPlayingSong, demo.nowPlayingAlbum, progressInset = true)
@@ -228,6 +237,7 @@ class DemoScreenshotTest {
onQueryChange = {},
results = demo.search,
coverArt = demo.coverArt,
artistArt = demo::artistArt,
playingSongId = demo.nowPlayingSong.id,
modifier = Modifier.fillMaxSize(),
)
@@ -288,7 +298,7 @@ class DemoScreenshotTest {
private fun reencodeOpaqueRgb(file: File) {
val decoded = ImageIO.read(file)
val rgb = BufferedImage(decoded.width, decoded.height, BufferedImage.TYPE_INT_RGB)
val rgb = BufferedImage(decoded.width, decoded.width * 2, BufferedImage.TYPE_INT_RGB)
rgb.createGraphics().run {
drawImage(decoded, 0, 0, null)
dispose()
@@ -329,6 +339,7 @@ class DemoScreenshotTest {
val homePlayingAlbum: Album,
val homePlayingSong: Song,
val artist: Artist,
val artistBio: String?,
val artistAlbums: List<Album>,
val search: SearchResults,
val coverArt: (String?, Boolean) -> CoverArtRef?,
@@ -371,6 +382,15 @@ class DemoScreenshotTest {
return artworkColorsFromSeeds(primary?.rgb ?: FALLBACK_SEED, null)
}
fun artistArt(artist: Artist): ArtistArt {
val thumbnail = artist.coverArt?.let { coverArt(it, true) }
return ArtistArt(
image = thumbnail,
thumbnail = thumbnail,
covers = artist.coverArts.take(4).mapNotNull { coverArt(it, true) },
)
}
companion object {
private const val FALLBACK_SEED = 0xFF3A7BD5.toInt()
private const val DEMO_BASE = "https://demo.navidrome.org/"
@@ -417,9 +437,11 @@ class DemoScreenshotTest {
val homePlayingAlbum = allAlbums.firstOrNull { it.name == "Shaking The Habitual" } ?: allAlbums.last()
val homePlayingSong = songsOf(homePlayingAlbum).first()
val artist =
allArtists.firstOrNull { it.name == "Ugress" }
allArtists.firstOrNull { it.name == "Nine Inch Nails" }
?: allArtists.firstOrNull { it.name == "pornophonique" }
?: allArtists.maxByOrNull { it.albumCount }
?: error("demo library has no artists")
val artistBio = normalizeBiography(source.artistBiography(artist.id))
val artistAlbums = dao.albumsForArtist(1, artist.id).first()
val coverArtIds =
@@ -509,6 +531,7 @@ class DemoScreenshotTest {
homePlayingAlbum = homePlayingAlbum,
homePlayingSong = homePlayingSong,
artist = artist,
artistBio = artistBio,
artistAlbums = artistAlbums.filter { it.coverArt != null },
search = search,
coverArt = coverArt,
+24
View File
@@ -0,0 +1,24 @@
## What's new
- ReplayGain: normalizes loudness from the tags your server reports. Auto, the default, uses album gain for album queues and track gain for playlists, singles and shuffles. Playback settings also offer track gain, album gain, off and a preamp
- Artist pages show an expandable biography, artwork from the server or built from the artist's albums, and separate sections for album credits and appearances
- Sort lists can use regular names (the default), sort names, or sort names only for names without Latin letters
- Improved handling for albums and tracks with multiple artists, including their names in song subtitles, the queue and info dialogs
- Info dialogs in album, artist and playlist menus, listing credited artists, sort names, owner and sharing, and play and download details
- Keep the screen on while now playing is open setting, off by default (thanks @chiangww!)
## Fixed
- Album and artist scroll positions survive navigating away and back
- Search keeps its query and focus when you return
- The library tab strip scrolls, so the settings gear is never squeezed out
- Notification progress for streams of unknown length, such as Opus/Ogg transcodes
- Album grid no longer shifts covers after a scrobble reloads the list
- Now playing screen: artwork wraps with repeat, and cover shadows are no longer clipped
## Performance
- Faster library sync on servers without `search3`
- Artist lists and downloads stay fast on large libraries
**Full changelog:** https://github.com/austinried/subtracks/compare/v3.0.0...v3.1.0
+12
View File
@@ -0,0 +1,12 @@
- ReplayGain: loudness from server tags, with Auto, track, album, off, preamp
- Artist pages: bio, art, credits and appearances
- Sort lists: regular names or sort names
- Improved handling for multi-artist albums/tracks
- Keep the screen on while now playing is open setting, off by default (thanks @chiangww!)
Fixed
- Notification progress for unknown-length streams (Opus/Ogg)
- Now playing artwork and shadow fixes
Release notes: https://github.com/austinried/subtracks/releases/tag/v3.1.0
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 KiB

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

After

Width:  |  Height:  |  Size: 371 KiB