feat(android): complete the app — parity fixes, library pagination and local release signing - #10
Merged
Merged
Conversation
- Shuffling replaced the whole list before the index was applied, so the tapped track pointed at whatever landed on that position; only the rest of the queue is shuffled now.
- Masters from SoundCloud and YouTube Music sit 6-8 dB apart, so tracks from different sources sounded like different apps; both players now share one target (-14 LUFS) and one estimator. - Gain is fixed for the whole track and looked up from a per-track cache, so nothing pumps mid-song and a quiet passage is never mistaken for a quiet master. It is applied on decoded PCM (media3 AudioProcessor on Android, the existing sample loop on desktop) and measured pre-gain, so replays do not compound their own correction. - Replaces the desktop AGC, whose smoothed RMS gain could not tell either case apart and drifted for seconds at the start of every track. - A track plays at its native level the first time and is normalised from the next play on, which is what keeping the gain static costs.
- Both the session card (lock screen, quick settings) and the notification body opened MainActivity bare, so the app always landed on Search; the content intent now carries an action that the app turns into a Now Playing request. - singleTop keeps the tap inside the running instance, and the request is pushed onto the tab history when the app is already open (back returns to where the user was) or becomes the entry tab on a cold start.
- Releasing the engine from onDestroy was meant as teardown, but isFinishing is true for a plain back press, so leaving the app released the player and stopped the foreground service it had just started.
- The overlays are drawn above the tabs, so switching the tab behind one left the user on the SoundCloud WebView with the request silently consumed. - Drops the back handler over that overlay: SoundCloudLoginScreen's own is composed later, so it always won, and it cancels to the same place.
- Tab, platform and the back stack were plain `remember`, so a system-initiated recreate (dark mode, locale, font scale) or a restore after process death dropped the user on Search with the queue still playing. - The theme now lives in a config file: it is a user preference, and it was resetting on every cold start.
…aying - Tapping the bar there switched to the tab the user was already on, so the tap went nowhere; the queue sheet is the only thing left to reveal.
- The store was Android-only while the desktop kept the state in memory, so the same choice had two implementations and only one of them survived a restart. Both apps already share AppDirs.
- Nothing stopped the service after the release-on-finish was removed, so a paused notification could be swiped away leaving a foreground service with no notification to control: unstoppable for the user, and against the platform's foreground-service rules. Dismissal now stops the service, and a sticky restart with no player never enters the foreground.
- Liked tracks, own playlists and saved playlists all made a single call (`limit=50` by default) and dropped `next_href`, so a library came back truncated at the first page — a 1.7k-likes account saw 50. - One cursor walk now serves all four listings, and it keeps what it has when a page fails instead of replacing a partial list with an empty one.
- Every library listing (liked songs, own playlists, subscriptions) took the first Data API page and ignored `nextPageToken`, so all three capped at 50 items regardless of how much was actually there. - The `videos.list` enrichment that adds durations is chunked by the API's 50-id limit and runs a few chunks at a time, since walking a large liked list one request at a time makes the library screen crawl. - Channel lookups for saved playlists are chunked too: past 50 of them the single request was rejected and every owner silently went missing.
- The release variant had no signing config, so the only installable APK was the debug one. Credentials now come from WREN_KEYSTORE* variables, which keeps them out of the repo and lets another machine build unsigned instead of failing.
- versionCode was a literal 1, so an installed build could never be replaced by a later one without uninstalling; it now counts commits, and versionName is the `git describe` string, so a device build names its own revision. - Both are overridable from the environment for a tagged release, and a missing git checkout falls back instead of failing the build. - `mise run android:apk` wraps the signing variables and the release task, and a project tool pin keeps Gradle on the Java 21 the toolchain asks for.
- AGP's `app-release.apk` says nothing about which build it is, so a file on a phone could not be traced back to a revision; it is now `wren-<versionName>.apk` from the same string the installed app reports. - An unsigned build keeps AGP's `-unsigned` marker, decided from the signing variables rather than the variant (which reports a signing config either way): an APK that cannot be installed should not look like one that can.
- Attaching the APK to the tag's GitHub release had to be done by hand or not at all; `mise run android:release` does it without wiring anything into CI, so which builds become public stays a decision rather than a push side effect. - Refuses a dirty tree or an untagged commit (the APK is named from git, so there would be nothing to trace it to) and verifies the CN=wren signature before uploading, since a release page is not the place to find that out.
- gh would create the missing tag at origin's HEAD, publishing an APK under a revision it was not built from; the remote tag must already point at this commit, so a release never becomes the thing that creates its own tag.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Everything since #9, in atomic commits:
Playback
isFinishing, and the service stops with its notification instead of outliving it.Navigation
Library completeness
next_href(they stopped at the first page, e.g. 50 of 1.7k likes).nextPageToken; thevideos.listenrichment is chunked and bounded-concurrent.Release tooling (local, no CI)
versionCode/versionName, APK namedwren-<version>.apk,misetasks, and a manualmise run android:releasethat refuses dirty/untagged/unpushed states and verifies the signature before uploading.