Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
1f35cf0
feat(playback): add independent subtitle text opacity and a gray colo…
Joloxx9 Sep 30, 2026
f0cfae4
ci: add trusted reusable Linux workflow (#425)
Quick104 Sep 30, 2026
e24d9e1
ci: route member builds to private Linux runners (#426)
Quick104 Sep 30, 2026
41f5d40
feat(detail): show the same external ratings on every title page (#423)
Quick104 Oct 1, 2026
354058b
feat(requests): make requests look and behave like the rest of the ap…
Quick104 Oct 2, 2026
1d834f5
feat(downloads): save poster and series artwork with downloads for of…
Rhainland Oct 2, 2026
d7d8580
feat(tv): add Release Date library sort on Android TV (#433)
Rhainland Oct 2, 2026
9d54e3c
feat(detail): mark a whole season watched on Android phone and TV (#437)
Rhainland Oct 2, 2026
6f1a366
fix(downloads): show why a series or episode download didn't start (#…
rangoDJ Oct 2, 2026
9518b26
fix(overlays): honor a profile's poster badge switch on phone and TV …
Rhainland Oct 2, 2026
fa0f83a
feat(settings): let profiles turn off title art on Android phone and …
Rhainland Oct 2, 2026
1e903dd
feat(settings): lay out Android and Android TV Settings like the Appl…
Quick104 Oct 2, 2026
74fc3e9
feat(auth): sign in with the server's identity provider and the new T…
Quick104 Oct 2, 2026
be56fd5
ci: run CI on GitHub-hosted runners (#453)
Quick104 Oct 2, 2026
b26ddda
feat(auth): sign in with network identity on phone and TV (#454)
Quick104 Oct 3, 2026
32488e8
fix(tv): play phone-launched titles with sequenced playback (#380)
Quick104 Oct 3, 2026
844ff74
feat(onboarding): redesign the first-run flow on Android phone and TV…
Quick104 Oct 3, 2026
7ddec04
fix(downloads): refresh saved subtitles the server retimed (#444)
Quick104 Oct 4, 2026
e21a0fa
feat(playback): play embedded MKV text subtitles from the stream (#464)
tomislav Oct 5, 2026
a8d7aea
fix(tv): offer the full subtitle-language list in search and AI trans…
tomislav Oct 5, 2026
b0e3fc3
fix(playback): stop claiming m2ts/mts direct play (#452)
Rhainland Oct 5, 2026
dc8f0b5
fix(player): reload a subtitle the server retimed during playback (#443)
Quick104 Oct 5, 2026
98b8cea
feat(subtitles): show sync status and sync/reset actions for stored s…
Quick104 Oct 5, 2026
a6611c3
fix(cast): keep the TV reachable after a TLS 1.2 client connects (#379)
Quick104 Oct 5, 2026
ca53cb0
fix(cast): speak the Remote Control protocol the way Apple does (#381)
Quick104 Oct 5, 2026
bb8d9cd
fix(tv): end the remote-playback identity after the player's session …
Quick104 Oct 5, 2026
6e61709
feat(watch-party): bring Watch Party to phone and TV behind an Experi…
Quick104 Oct 6, 2026
535ddda
fix(tv): start and stop the cast receiver as sign-in changes (#482)
Quick104 Oct 6, 2026
81e9206
feat(tv-settings): opt-in True Black Bars for grey HDR letterbox bars…
Quick104 Oct 6, 2026
b42ad84
fix(playback): never block playback on an earlier unfinished session …
Quick104 Oct 6, 2026
d71824d
fix(tv): apply quality changes from the phone remote (#485)
Quick104 Oct 6, 2026
2a50183
refactor(playback): remove dead playback code (#484)
Quick104 Oct 6, 2026
39eaf8e
feat(playback): shuffle libraries, series, seasons, and collections o…
Rhainland Oct 6, 2026
9343dde
docs(contributing): require evidence for user-visible changes (#490)
Quick104 Oct 6, 2026
975df0d
fix(settings): say connecting Tailscale keeps the Silo password (#492)
Quick104 Oct 7, 2026
3f8c867
docs(pr): offer the evidence site as an optional place for PR evidenc…
Quick104 Oct 7, 2026
95d20a9
feat(player): show the server quality menu on phone and align the TV …
Quick104 Oct 7, 2026
69c6f2f
fix(libraries): re-check the library list when the Libraries tab resu…
Quick104 Oct 7, 2026
b89b538
fix(profiles): manage household profiles as the primary profile (#493)
Rhainland Oct 7, 2026
b6a4a9a
docs: ask for issues instead of unsolicited pull requests (#504)
Quick104 Oct 7, 2026
27f30c3
docs(issues): add bug report and feature request forms (#505)
Quick104 Oct 7, 2026
8389e5c
feat(ui): restyle the player, menus and dialogs on phone and TV (#502)
Quick104 Oct 7, 2026
1574897
fix(tv): close the TV's own playback before a phone takes over (#503)
Quick104 Oct 7, 2026
8b0f374
test: keep shuffle countdown on one scheduler
Quick104 Oct 7, 2026
6808ea8
test: serialize library shrink fixtures on the test scheduler
Quick104 Oct 7, 2026
bbbb430
fix(settings): flush settings before a phone profile switch and match…
Rhainland Oct 7, 2026
7f8c7e1
test: use test scheduler for library recovery fixture
Quick104 Oct 7, 2026
b6f081b
test: scope TV startup ownership barrier
Quick104 Oct 7, 2026
7ccc5db
ci: cache Android build outputs while rerunning source checks
Quick104 Oct 7, 2026
4f19bc0
ci: keep cache baseline runs read only
Quick104 Oct 7, 2026
dcdb918
test: prove Android source checks execute with warm caches
Quick104 Oct 7, 2026
d9e4a9b
ci: report executed tests and explicit cache policy
Quick104 Oct 7, 2026
d2a2bc4
ci: count only the requested debug test profile
Quick104 Oct 7, 2026
7cb1fd2
ci(android): route prose changes and enforce validation results
Quick104 Oct 7, 2026
0ba9233
ci: overlap Android APK preparation with Play
Quick104 Oct 7, 2026
2ed1882
ci: parallelize Android unit projects within two workers
Quick104 Oct 7, 2026
dbaec2b
ci: prepare Play bundles alongside release validation
Quick104 Oct 7, 2026
f34f360
fix(tv): show mixed libraries in movie and series tabs (#441)
RXWatcher Oct 7, 2026
7fa78b4
ci: integrate verified Android CI improvements
Quick104 Oct 8, 2026
bfa6188
Merge pull request #513 from Silo-Server/ci/android-play-bundle-overlap
Quick104 Oct 8, 2026
3651536
feat(tv): ask who's watching at launch with a Profile Selection setti…
amokster88 Oct 8, 2026
cad4d0b
fix(libraries): leave hidden libraries out of navigation on phone and…
Quick104 Oct 8, 2026
ab86a85
chore(issues): apply the feature-request label from the feature reque…
Quick104 Oct 8, 2026
88e0700
fix(subtitles): prefer embedded tracks over external sidecars (#529)
Quick104 Oct 8, 2026
def0532
fix(subtitles): bound how far a PGS sidecar reads ahead (#528)
Quick104 Oct 8, 2026
cfac75b
fix(tv): keep library browse sort and filters across app restarts (#468)
Rhainland Oct 9, 2026
1b9e181
feat(artwork): show a faint brand glow with a type mark for missing a…
Quick104 Oct 9, 2026
021c5ea
fix(playback): decode DTS with FFmpeg when the HDMI sink has no DTS (…
d3v1l1989 Oct 9, 2026
67c58fc
fix(tv): keep a mixed library's Movies and Series browse filters apar…
RXWatcher Oct 9, 2026
7401e55
fix(mobile): separate movies and series in mixed libraries (#447)
RXWatcher Oct 9, 2026
c0172bf
feat(cast): start phone-to-TV playback without a handoff when the TV …
amokster88 Oct 9, 2026
05b3f91
fix(tv): show the real Silo app icon in setup step 1 (#533)
Quick104 Oct 9, 2026
1e956c3
fix(settings): let a device go back to the profile's playback setting…
Rhainland Oct 10, 2026
af99d87
fix(collections): open library-tab collection items without a library…
d3v1l1989 Oct 10, 2026
7f5e058
fix(player): consume and reconcile file-specific markers (#527)
Rhainland Oct 10, 2026
74490e9
fix(libraries): tell an empty library apart from filters that match n…
amokster88 Oct 10, 2026
e535ddc
fix(profiles): recover from access changes without failed loads (#440)
Rhainland Oct 10, 2026
95a6f21
feat(settings): hide spoilers for unwatched episodes, per profile (#427)
Quick104 Oct 10, 2026
97f6773
feat(tv): add artwork and an On Deck row to the Up Next overlay (#541)
im85288 Oct 10, 2026
747efff
feat(metadata): mark AI translations and translate episode lists and …
Rhainland Oct 11, 2026
78f554c
feat(tv): ask who's watching on a new Android TV and offer to remembe…
Rhainland Oct 11, 2026
538aa09
feat(profiles): ask who's watching at launch on Android phones and ta…
Rhainland Oct 11, 2026
726684d
perf(home): load 100-item Home rows without blocking the main thread …
Quick104 Oct 11, 2026
3972743
Merge upstream Silo-Server/silo-android 726684df7f into main
JonahMMay Oct 12, 2026
940602b
Point the TV setup marquee at prairie_icon_background
JonahMMay Oct 12, 2026
251aed8
Drop the retired set_hdr_enabled cast assertion
JonahMMay Oct 12, 2026
a6bbb8e
Rename TvPlayerScreen's toSiloCast* helpers to toPrairieCast*
JonahMMay Oct 12, 2026
fc0ef55
Restore Hud body text constants and MaterialTheme import; cancel VM s…
JonahMMay Oct 12, 2026
da16a54
Restore the 95% :shared coverage gate after the upstream merge
JonahMMay Oct 12, 2026
778b44f
CollectionsViewModelLoadOrderTest: cancelAndJoin VM scope before rese…
JonahMMay Oct 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 8 additions & 0 deletions .github/evidence.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Pull requests that change the phone app, the TV app, or the Android code they share
# need evidence before the Evidence check passes. The check reads this file at a pull
# request's base commit. Maintainers override it with the evidence-required and
# evidence-not-needed labels. See "Show visible changes" in CONTRIBUTING.md.
require:
- androidApp/src/androidMain/**
- androidTvApp/src/androidMain/**
- android-shared/src/androidMain/**
175 changes: 135 additions & 40 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ name: Release

# One release pipeline, playing the role of silo-apple's TestFlight
# (release.yml) and sideload (sideload-ipa.yml) workflows combined: a version
# tag or a manual dispatch publishes both App Bundles to Google Play, then
# builds the signed sideload APKs and attaches them to a GitHub release for
# tag or a manual dispatch prepares both App Bundles for Google Play and
# signed sideload APKs in parallel, then attaches the APKs to a GitHub release for
# the same version. Dispatch runs mint the v<version> tag when creating the
# GitHub release — no manual tagging is necessary (apple's sideload pattern).
#
Expand Down Expand Up @@ -80,6 +80,17 @@ jobs:
play_publish: ${{ steps.ver.outputs.play_publish }}
tag: ${{ steps.ver.outputs.tag }}
steps:
- name: Check out sources
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false

- name: Check build supply chain
run: |
./scripts/test-release-workflow.sh
./scripts/test-check-build-supply-chain.sh
./scripts/check-build-supply-chain.sh

- id: ver
shell: bash
env:
Expand Down Expand Up @@ -184,6 +195,7 @@ jobs:
# owned the APK release path.
unit-tests:
name: Unit tests
needs: [setup]
runs-on: ubuntu-latest

steps:
Expand All @@ -192,12 +204,6 @@ jobs:
with:
persist-credentials: false

- name: Check build supply chain
run: |
./scripts/test-release-workflow.sh
./scripts/test-check-build-supply-chain.sh
./scripts/check-build-supply-chain.sh

- name: Set up JDK 21
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5
with:
Expand All @@ -207,6 +213,16 @@ jobs:
- name: Set up Gradle
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6

# Robolectric downloads its Android runtimes outside Gradle's cache.
# Share the normal CI key so releases reuse those runtime downloads.
- name: Cache Robolectric Android runtimes
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.m2/repository/org/robolectric
key: robolectric-${{ runner.os }}-${{ hashFiles('gradle/libs.versions.toml', '**/build.gradle.kts') }}
restore-keys: |
robolectric-${{ runner.os }}-

- name: Run unit tests
shell: bash
run: |
Expand All @@ -226,14 +242,18 @@ jobs:
**/build/reports/tests/**
if-no-files-found: ignore

# Publish both App Bundles to Google Play via the fastlane android beta
# lane. No status function in the if -> implicit success() gate on needs,
# so failed setup/tests skip this job.
play:
name: Publish to Google Play
needs: [setup, unit-tests]
# Prepare final signed bundles while tests run. Publication consumes these
# exact bytes only after both preparation and tests succeed.
play-bundles:
name: Prepare Google Play bundles
needs: [setup]
if: ${{ needs.setup.outputs.play_publish == 'true' }}
runs-on: ubuntu-latest
outputs:
artifact_id: ${{ steps.bundles.outputs.artifact-id }}
artifact_name: ${{ steps.identity.outputs.artifact_name }}
producer_attempt: ${{ steps.identity.outputs.producer_attempt }}
manifest_sha256: ${{ steps.identity.outputs.manifest_sha256 }}

steps:
- name: Check out sources
Expand All @@ -254,33 +274,98 @@ jobs:
with:
bundler-cache: true

# The Fastfile re-validates the version and derives the versionCode
# base from it before building.
- name: Build & upload to Google Play
run: bundle exec fastlane android beta
- name: Prepare signed Google Play bundles
run: bundle exec fastlane android prepare_bundles
env:
PRAIRIE_VERSION_NAME: ${{ needs.setup.outputs.version_name }}
PRAIRIE_BUILD_NUMBER: ${{ needs.setup.outputs.build_number }}
PLAY_TRACK: ${{ inputs.track || 'beta' }}
PLAY_RELEASE_NOTES: ${{ vars.PLAY_RELEASE_NOTES }}
PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
PRAIRIE_RELEASE_KEYSTORE_B64: ${{ secrets.PRAIRIE_RELEASE_KEYSTORE_B64 }}
PRAIRIE_RELEASE_KEYSTORE_PASSWORD: ${{ secrets.PRAIRIE_RELEASE_KEYSTORE_PASSWORD }}
PRAIRIE_RELEASE_KEY_PASSWORD: ${{ secrets.PRAIRIE_RELEASE_KEY_PASSWORD }}
PRAIRIE_RELEASE_KEY_ALIAS: ${{ secrets.PRAIRIE_RELEASE_KEY_ALIAS }}
PRAIRIE_GOOGLE_SERVICES_JSON_B64: ${{ secrets.PRAIRIE_GOOGLE_SERVICES_JSON_B64 }}

# Signed sideload APKs (moved here from android-build.yml). Runs when the
# Play publish succeeded or was skipped (prerelease tags) — never when it
# failed, so the GitHub release can't outrun the Play track.
- name: Identify bundle artifact
id: identity
shell: bash
run: |
set -euo pipefail
manifest_sha256="$(sha256sum build/play-bundles-manifest.json | cut -d ' ' -f1)"
{
echo "artifact_name=android-play-bundles-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
echo "producer_attempt=${GITHUB_RUN_ATTEMPT}"
echo "manifest_sha256=${manifest_sha256}"
} >> "${GITHUB_OUTPUT}"

- name: Upload prepared Play bundles
id: bundles
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: ${{ steps.identity.outputs.artifact_name }}
path: |
androidApp/build/outputs/bundle/release/androidApp-release.aab
androidTvApp/build/outputs/bundle/release/androidTvApp-release.aab
build/play-bundles-manifest.json
if-no-files-found: error
compression-level: 0

play:
name: Publish to Google Play
needs: [setup, unit-tests, play-bundles]
if: ${{ needs.setup.outputs.play_publish == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
steps:
- name: Check out sources
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false

- uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1
with:
bundler-cache: true

- name: Verify bundle artifact provenance
shell: bash
env:
GH_TOKEN: ${{ github.token }}
PLAY_BUNDLE_ARTIFACT_ID: ${{ needs.play-bundles.outputs.artifact_id }}
PLAY_BUNDLE_ARTIFACT_NAME: ${{ needs.play-bundles.outputs.artifact_name }}
PRAIRIE_PLAY_BUNDLE_ATTEMPT: ${{ needs.play-bundles.outputs.producer_attempt }}
PRAIRIE_PLAY_BUNDLE_MANIFEST_SHA256: ${{ needs.play-bundles.outputs.manifest_sha256 }}
run: |
set -euo pipefail
[[ "${GITHUB_REPOSITORY}" == 'Prairie-Server/prairie-android' ]]
[[ "${PLAY_BUNDLE_ARTIFACT_ID}" =~ ^[1-9][0-9]*$ ]]
[[ "${PRAIRIE_PLAY_BUNDLE_MANIFEST_SHA256}" =~ ^[0-9a-f]{64}$ ]]
gh api "repos/${GITHUB_REPOSITORY}/actions/artifacts/${PLAY_BUNDLE_ARTIFACT_ID}" |
ruby fastlane/play_bundle_manifest.rb verify-artifact

- name: Download prepared Play bundles
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
artifact-ids: ${{ needs.play-bundles.outputs.artifact_id }}
path: .

- name: Upload verified bundles to Google Play
run: bundle exec fastlane android upload_bundles
env:
PRAIRIE_VERSION_NAME: ${{ needs.setup.outputs.version_name }}
PRAIRIE_BUILD_NUMBER: ${{ needs.setup.outputs.build_number }}
PLAY_TRACK: ${{ inputs.track || 'beta' }}
PRAIRIE_PLAY_BUNDLE_ATTEMPT: ${{ needs.play-bundles.outputs.producer_attempt }}
PRAIRIE_PLAY_BUNDLE_MANIFEST_SHA256: ${{ needs.play-bundles.outputs.manifest_sha256 }}
PLAY_RELEASE_NOTES: ${{ vars.PLAY_RELEASE_NOTES }}
PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}

# Prepare signed sideload APKs alongside Play after setup and tests succeed.
# publish-release waits for both paths before exposing the APKs.
apks:
name: ${{ matrix.name }}
needs: [setup, unit-tests, play]
if: >-
${{ !cancelled() &&
needs.setup.result == 'success' &&
needs.unit-tests.result == 'success' &&
(needs.play.result == 'success' || needs.play.result == 'skipped') }}
needs: [setup, unit-tests]
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down Expand Up @@ -385,14 +470,6 @@ jobs:
apk_name="$(basename "${apk}")"
variant="${apk_name#${{ matrix.module }}-}"
cp "${apk}" "${artifact_dir}/${{ matrix.release-prefix }}-${version_label}-${variant}"

if [[ "${variant}" == "universal-release.apk" ]]; then
cp "${apk}" "${artifact_dir}/${{ matrix.release-prefix }}-latest-${variant}"
# The Downloader by AFTVnews short codes redirect to the old
# debug-named asset URL. Keep publishing the release build under
# that filename until the aftv.news codes are re-registered.
cp "${apk}" "${artifact_dir}/${{ matrix.release-prefix }}-latest-universal-debug.apk"
fi
done

- name: Upload release APKs
Expand All @@ -409,11 +486,14 @@ jobs:
# re-trigger workflows, so this cannot recurse.
publish-release:
name: Publish GitHub Release
needs: [setup, apks]
needs: [setup, unit-tests, play-bundles, play, apks]
if: >-
${{ !cancelled() &&
needs.setup.result == 'success' &&
needs.apks.result == 'success' }}
needs.unit-tests.result == 'success' &&
needs.apks.result == 'success' &&
((needs.setup.outputs.play_publish == 'true' && needs.play-bundles.result == 'success' && needs.play.result == 'success') ||
(needs.setup.outputs.play_publish == 'false' && needs.play-bundles.result == 'skipped' && needs.play.result == 'skipped')) }}
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -431,11 +511,26 @@ jobs:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ needs.setup.outputs.tag }}
RELEASE_VERSION: ${{ needs.setup.outputs.version }}
run: |
set -euo pipefail
shopt -s globstar nullglob

release_title="Silo Android ${RELEASE_TAG}"
# Transfer each APK once, then recreate the universal aliases here.
# Downloader by AFTVnews still redirects to the debug-named aliases.
for prefix in prairie-android prairie-android-tv; do
universal_apks=(release-artifacts/**/"${prefix}-${RELEASE_VERSION}-universal-release.apk")
if (( ${#universal_apks[@]} != 1 )) || [[ ! -s "${universal_apks[0]}" ]]; then
echo "::error::Expected one nonempty universal APK for ${prefix}."
exit 1
fi
universal_apk="${universal_apks[0]}"
artifact_dir="$(dirname "${universal_apk}")"
cp "${universal_apk}" "${artifact_dir}/${prefix}-latest-universal-release.apk"
cp "${universal_apk}" "${artifact_dir}/${prefix}-latest-universal-debug.apk"
done

release_title="Prairie Android ${RELEASE_TAG}"
android_latest_url="https://github.com/${GH_REPO}/releases/latest/download/prairie-android-latest-universal-release.apk"
android_tv_latest_url="https://github.com/${GH_REPO}/releases/latest/download/prairie-android-tv-latest-universal-release.apk"
android_downloader_url="http://aftv.news/1051382"
Expand Down
Loading
Loading