Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
f196b81
fix: keep playback retries active after socket errors
Priveetee Oct 17, 2026
cb45442
feat: keep video quality local with mobile cap
Priveetee Oct 18, 2026
46e9102
fix: retry failed push registration automatically
Priveetee Oct 21, 2026
7458515
fix: retry undelivered local notifications
Priveetee Oct 21, 2026
2fe3c88
feat: classify incoming push messages
Priveetee Oct 21, 2026
4f31d5e
feat: trace push delivery outcomes
Priveetee Oct 21, 2026
ad58c88
feat: show push traces in diagnostics
Priveetee Oct 21, 2026
9f9f0c6
fix: hide unknown server counts
Priveetee Oct 22, 2026
9fab084
fix: retry empty related video metadata
Priveetee Oct 22, 2026
b6af288
fix: preserve server counts and playback metadata
Priveetee Oct 22, 2026
0c05a22
build: install benchmarks under their own package
Priveetee Oct 24, 2026
8ce1946
build: remove the TV app, now in its own repository
Priveetee Oct 24, 2026
abd91f6
build: drop the TV module and SDK build from settings and CI
Priveetee Oct 24, 2026
1909401
build: share Android defaults through build-logic convention plugins
Priveetee Oct 24, 2026
aa59b92
refactor: move SABR window polling out of the session preparer
Priveetee Oct 24, 2026
37a20ce
test: share a scripted SABR server between preparer tests
Priveetee Oct 24, 2026
bcb4863
fix: name the media item session in SABR recovery requests
Priveetee Oct 24, 2026
17d7dea
fix: replace a stuck SABR session in place while refreshing
Priveetee Oct 24, 2026
2de14a0
refactor: classify playback failures once for every recovery layer
Priveetee Oct 24, 2026
ae24569
fix: retry failed SABR windows in place for two minutes
Priveetee Oct 24, 2026
19a9ae0
fix: give up on a failing SABR control call within seconds
Priveetee Oct 24, 2026
10336f4
refactor: move shared resources into a core:resources module
Priveetee Oct 24, 2026
98c4462
ci: run unit tests and lint in every module
Priveetee Oct 24, 2026
cb59137
refactor: move the domain layer into a core:model module
Priveetee Oct 24, 2026
bff30b1
refactor: move error and URL helpers into core:model
Priveetee Oct 24, 2026
4d205a3
refactor: inject the app version instead of reading BuildConfig in data
Priveetee Oct 24, 2026
1664757
refactor: move local notifications next to the push notifier
Priveetee Oct 24, 2026
038bd09
refactor: move the data layer into a core:data module
Priveetee Oct 24, 2026
33a8404
build: add a Compose convention plugin
Priveetee Oct 24, 2026
48640b7
refactor: move shared UI into a core:ui module
Priveetee Oct 24, 2026
e832186
refactor: share queue mappings and player host state below features
Priveetee Oct 24, 2026
5954216
refactor: move codec keys, version and menu sheets out of the player …
Priveetee Oct 24, 2026
eaaa77e
build: add a feature convention plugin
Priveetee Oct 24, 2026
e11ef89
refactor: move the video menu into a feature:menu module
Priveetee Oct 24, 2026
9e8ffa9
refactor: move six screen families into feature modules
Priveetee Oct 24, 2026
d481bc9
refactor: move channel, library and subscriptions into feature modules
Priveetee Oct 24, 2026
a69adb7
docs: drop the TV app and SDK from the readme and contributing guide
Priveetee Oct 24, 2026
3d7020a
ci: run on GitHub-hosted runners
Priveetee Oct 24, 2026
21d75c7
refactor: move push and local notifications into a core:notifications…
Priveetee Oct 24, 2026
330e15e
refactor: move settings into a feature:settings module
Priveetee Oct 24, 2026
7552407
refactor: move the playback engine into a core:playback module
Priveetee Oct 24, 2026
c9af97f
refactor: move the player and Shorts into feature modules
Priveetee Oct 24, 2026
763a07b
build: compile Kotlin in its daemon so modules build in parallel
Priveetee Oct 25, 2026
df3a853
feat: track one playback recovery window per item
Priveetee Oct 25, 2026
30dba4e
fix: let the playback service own retries and tell the player UI
Priveetee Oct 25, 2026
3fa9b70
fix: start a linked video the player already holds paused
Priveetee Oct 25, 2026
ca9978d
perf: profile home, Shorts and playback in the baseline profile
Priveetee Oct 25, 2026
c8333aa
fix: keep coroutine cancellation out of failed results
Priveetee Oct 25, 2026
fe4f95c
perf: draw Shorts video in a Compose surface instead of a PlayerView
Priveetee Oct 25, 2026
caa7c83
build: run module instrumented tests with the AndroidX test runner
Priveetee Oct 25, 2026
7edbd2f
fix: keep the app theme in the app and declare Material for shared dr…
Priveetee Oct 25, 2026
e6e4749
fix: remember decoder failures across the app and across launches
Priveetee Oct 25, 2026
245f860
refactor: move the timeline track out of the player time bar
Priveetee Oct 25, 2026
863f95e
refactor: move app shell state helpers out of the nav host
Priveetee Oct 25, 2026
8772a16
test: share playback source fixtures and split long selector tests
Priveetee Oct 25, 2026
0ef6d00
refactor: move subtitle and language rows out of player settings
Priveetee Oct 25, 2026
15411f7
ci: fail when a Kotlin file goes over the length limit
Priveetee Oct 25, 2026
e94d6ec
refactor: take the modifier as the first optional parameter
Priveetee Oct 25, 2026
5d3bc06
refactor: use core-ktx helpers and drop an empty super call
Priveetee Oct 25, 2026
66906cb
refactor: check the API level before reading newer permission names
Priveetee Oct 25, 2026
a220221
build: fail lint on warnings
Priveetee Oct 25, 2026
a879a56
test: run service tests in the app and allow local HTTP in module tests
Priveetee Oct 25, 2026
cb31b39
fix: remember only definitive decoder rejections across launches
Priveetee Oct 25, 2026
775ac3c
test: run the Shorts session test in the app and follow the device lo…
Priveetee Oct 25, 2026
0a53bec
docs: describe the module layout and the current checks
Priveetee Oct 25, 2026
1fc9ba2
perf: keep Shorts pages from recomposing on every player update
Priveetee Oct 25, 2026
c4537d9
chore: merge beta into dev
Priveetee Oct 25, 2026
462c7d6
chore: prepare 1.11.0 beta 4
Priveetee Oct 25, 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
  •  
  •  
  •  
50 changes: 24 additions & 26 deletions .github/workflows/android-beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
- beta
paths:
- 'app/**'
- 'build-logic/**'
- 'core/**'
- 'feature/**'
- 'player/**'
- 'baseline-profile/**'
- 'gradle/**'
Expand All @@ -17,6 +20,9 @@ on:
- beta
paths:
- 'app/**'
- 'build-logic/**'
- 'core/**'
- 'feature/**'
- 'player/**'
- 'baseline-profile/**'
- 'gradle/**'
Expand All @@ -36,36 +42,31 @@ env:
CI: true
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
JAVA_TOOL_OPTIONS: -Djava.awt.headless=true
GRADLE_OPTS: -Dorg.gradle.workers.max=8 -Dorg.gradle.daemon.idletimeout=900000
ANDROID_HOME: /opt/android-sdk
ANDROID_SDK_ROOT: /opt/android-sdk
JAVA_HOME: /usr/lib/jvm/java-26-openjdk
GRADLE_USER_HOME: /var/tmp/typetype-android-gradle-user-home
GITHUB_REPO: TypeType-Video/TypeType-Android

jobs:
release:
runs-on: [self-hosted, Linux, X64, ci-stack, android]
timeout-minutes: 35
runs-on: ubuntu-latest
timeout-minutes: 50
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v7.0.1

- name: Set up Java
uses: actions/setup-java@v6.0.1
with:
distribution: temurin
java-version: 21

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6.4.0

- name: Install Android SDK platform
run: |
platform="$ANDROID_HOME/platforms/android-37.0"
(
flock 9
if [ ! -f "$platform/android.jar" ]; then
rm -rf "$platform"
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \
--sdk_root="$ANDROID_HOME" \
"platforms;android-37.0"
fi
test -f "$platform/android.jar"
) 9>"$ANDROID_HOME/.platform-install.lock"
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install \
"platforms;android-37.0" "build-tools;36.0.0" > /dev/null

- name: Validate beta release version
env:
Expand Down Expand Up @@ -123,14 +124,12 @@ jobs:
} > keystore.properties

- name: Verify and build beta release
timeout-minutes: 25
timeout-minutes: 40
run: |
./gradlew --build-cache --stacktrace \
./gradlew --stacktrace \
:baseline-profile:assemble \
:player:testDebugUnitTest \
:app:testDebugUnitTest \
:player:lintDebug \
:app:lintDebug \
testDebugUnitTest \
lintDebug \
:app:assembleDebug \
:app:assembleRelease

Expand All @@ -140,8 +139,7 @@ jobs:
with:
name: typetype-beta-lint-report
path: |
app/build/reports/lint-results-debug.html
player/build/reports/lint-results-debug.html
**/build/reports/lint-results-debug.html
if-no-files-found: ignore
retention-days: 14

Expand Down
120 changes: 51 additions & 69 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,67 +37,64 @@ env:
CI: true
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
JAVA_TOOL_OPTIONS: -Djava.awt.headless=true
GRADLE_OPTS: -Dorg.gradle.workers.max=8 -Dorg.gradle.daemon.idletimeout=900000
ANDROID_HOME: /opt/android-sdk
ANDROID_SDK_ROOT: /opt/android-sdk
JAVA_HOME: /usr/lib/jvm/java-26-openjdk
GRADLE_USER_HOME: /var/tmp/typetype-android-gradle-user-home
TYPETYPE_SDK_PATH: .ci/TypeType-SDK

jobs:
verify:
if: ${{ github.event_name != 'workflow_dispatch' || !inputs.publish_release || github.ref != 'refs/heads/main' }}
runs-on: [self-hosted, Linux, X64, ci-stack, android]
timeout-minutes: 30
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v7.0.1

- name: Install Android SDK platform
- name: Check file length
run: |
platform="$ANDROID_HOME/platforms/android-37.0"
(
flock 9
if [ ! -f "$platform/android.jar" ]; then
rm -rf "$platform"
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \
--sdk_root="$ANDROID_HOME" \
"platforms;android-37.0"
set -euo pipefail
status=0
while IFS= read -r file; do
limit=330
case "$file" in */src/test/*|*/src/androidTest/*) limit=350 ;; esac
lines="$(wc -l < "$file")"
if [ "$lines" -gt "$limit" ]; then
echo "::error file=$file::$lines lines, the limit is $limit"
status=1
fi
test -f "$platform/android.jar"
) 9>"$ANDROID_HOME/.platform-install.lock"
done < <(git ls-files '*.kt')
exit "$status"

- name: Set up Java
uses: actions/setup-java@v6.0.1
with:
distribution: temurin
java-version: 21

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6.4.0

- name: Install Android SDK platform
run: |
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install \
"platforms;android-37.0" "build-tools;36.0.0" > /dev/null

- name: Verify Android project
timeout-minutes: 25
timeout-minutes: 40
run: |
set -euo pipefail
tasks=(
:baseline-profile:assemble
:player:testDebugUnitTest
:app:testDebugUnitTest
:player:lintDebug
:app:lintDebug
testDebugUnitTest
lintDebug
:app:assembleDebug
:app:assembleRelease
)
if [[ -f "$TYPETYPE_SDK_PATH/settings.gradle.kts" ]]; then
tasks+=(
:tv:testDebugUnitTest
:tv:lintDebug
:tv:assembleDebug
)
else
echo "::notice::TV verification skipped because the private TypeType-SDK checkout is unavailable."
fi
./gradlew --build-cache --stacktrace "${tasks[@]}"
./gradlew --stacktrace "${tasks[@]}"

- name: Upload debug APK
uses: actions/upload-artifact@v7.0.1
with:
name: typetype-debug-apk
path: |
app/build/outputs/apk/debug/*.apk
tv/build/outputs/apk/debug/*.apk
if-no-files-found: error
compression-level: 0
retention-days: 14
Expand All @@ -108,35 +105,33 @@ jobs:
with:
name: lint-report
path: |
app/build/reports/lint-results-debug.html
player/build/reports/lint-results-debug.html
tv/build/reports/lint-results-debug.html
**/build/reports/lint-results-debug.html
if-no-files-found: ignore
retention-days: 14

release:
if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && inputs.publish_release }}
runs-on: [self-hosted, Linux, X64, ci-stack, android]
timeout-minutes: 35
runs-on: ubuntu-latest
timeout-minutes: 50
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v7.0.1

- name: Set up Java
uses: actions/setup-java@v6.0.1
with:
distribution: temurin
java-version: 21

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6.4.0

- name: Install Android SDK platform
run: |
platform="$ANDROID_HOME/platforms/android-37.0"
(
flock 9
if [ ! -f "$platform/android.jar" ]; then
rm -rf "$platform"
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \
--sdk_root="$ANDROID_HOME" \
"platforms;android-37.0"
fi
test -f "$platform/android.jar"
) 9>"$ANDROID_HOME/.platform-install.lock"
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install \
"platforms;android-37.0" "build-tools;36.0.0" > /dev/null

- name: Prepare release metadata
env:
Expand Down Expand Up @@ -173,38 +168,25 @@ jobs:
} > keystore.properties

- name: Verify Android project and build release
timeout-minutes: 25
timeout-minutes: 40
run: |
set -euo pipefail
tasks=(
:baseline-profile:assemble
:player:testDebugUnitTest
:app:testDebugUnitTest
:player:lintDebug
:app:lintDebug
testDebugUnitTest
lintDebug
:app:assembleDebug
:app:assembleRelease
)
if [[ -f "$TYPETYPE_SDK_PATH/settings.gradle.kts" ]]; then
tasks+=(
:tv:testDebugUnitTest
:tv:lintDebug
:tv:assembleDebug
)
else
echo "::notice::TV verification skipped because the private TypeType-SDK checkout is unavailable."
fi
./gradlew --build-cache --stacktrace "${tasks[@]}"
./gradlew --stacktrace "${tasks[@]}"

- name: Upload lint report
if: ${{ always() }}
uses: actions/upload-artifact@v7.0.1
with:
name: lint-report
path: |
app/build/reports/lint-results-debug.html
player/build/reports/lint-results-debug.html
tv/build/reports/lint-results-debug.html
**/build/reports/lint-results-debug.html
if-no-files-found: ignore
retention-days: 14

Expand Down
47 changes: 23 additions & 24 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ Gradle wrapper.
```sh
git switch dev
./gradlew :app:assembleDebug
# Android TV
./gradlew :tv:assembleDebug
```

On first launch, the app asks for a TypeType instance. Use your own test
Expand All @@ -59,18 +57,25 @@ the contract advertised by the selected TypeType instance.

## Source layout

| Path | Responsibility |
| Module | Responsibility |
| --- | --- |
| `app/src/main/java/dev/typetype/android/core` | Shared UI and platform utilities |
| `app/src/main/java/dev/typetype/android/data` | Network, database, cache, and repository implementations |
| `app/src/main/java/dev/typetype/android/domain` | Models and application contracts |
| `app/src/main/java/dev/typetype/android/feature` | Compose screens and feature state |
| `app/src/main/java/dev/typetype/android/services` | Long-lived playback and Android services |
| `app` | Activity, navigation, dependency wiring, app theme, and manifest |
| `core:model` | Models, repository contracts, and small pure helpers |
| `core:data` | Network, Room database, DataStore, and repository implementations |
| `core:playback` | Playback service, SABR sessions, source and codec selection, recovery |
| `core:notifications` | Push and local notifications |
| `core:ui` | Shared Compose components, theme helpers, and navigation routes |
| `core:resources` | Strings, drawables, and other shared resources |
| `core:testing` | Helpers shared by device tests |
| `feature:*` | One module per screen family: home, search, channel, library, subscriptions, player, Shorts, settings, setup, and more |
| `player` | TypeType playback integration for Media3 |
| `app/src/test` | JVM unit and repository tests |
| `app/src/androidTest` | Room, Compose, platform, and device tests |
| `tv/src/main` | Native Android TV UI, navigation, focus, and playback |
| `tv/src/test` | Android TV JVM unit tests |
| `baseline-profile` | Baseline profile generation and macrobenchmarks |
| `build-logic` | Convention plugins that hold the shared Android setup |

Each module keeps its JVM tests in `src/test` and its device tests in
`src/androidTest`. Features depend on `core` modules, not on each other, except
where a screen embeds another one, such as Shorts using the player. Device tests
that need the real playback service or the Hilt graph live in `app`.

Most changes only touch one or two of these areas. As a rule of thumb,
composables render state, repositories decide how data is loaded and cached,
Expand Down Expand Up @@ -117,22 +122,16 @@ Before opening a pull request, run the same core checks as CI:

```sh
./gradlew --no-daemon \
:player:testDebugUnitTest \
:app:testDebugUnitTest \
:player:lintDebug \
:app:lintDebug \
:baseline-profile:assemble \
testDebugUnitTest \
lintDebug \
:app:assembleDebug \
:app:assembleRelease
```

For TV changes, also run:

```sh
./gradlew --no-daemon \
:tv:testDebugUnitTest \
:tv:lintDebug \
:tv:assembleDebug
```
Lint warnings fail the build, and CI rejects a Kotlin file over the length
limit. To run one module's device tests on a connected device or emulator, use
for example `./gradlew :feature:home:connectedDebugAndroidTest`.

Add the checks that make sense for your change. You are not expected to own
every Android version or device; if something could not be tested, mention it
Expand Down
17 changes: 0 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ Play Services.
TypeType Android uses the TypeType Server selected during setup for extraction,
playback sessions, recommendations, synchronization, and downloads.

The Android TV client is part of this repository in the `tv` module. It is a
separate native TV application that uses the same TypeType SDK and server
contracts as the mobile client.

## Screenshots

| Welcome | Add an instance | Home |
Expand All @@ -56,19 +52,6 @@ contracts as the mobile client.
sleep timer.
- Download supported videos and import existing data.

## Android TV

Build the TV client with the included Gradle wrapper:

```sh
./gradlew :tv:assembleDebug
```

The TV module targets Android TV devices from API 23 onward and keeps its TV
navigation, focus behavior, layouts, and playback presentation independent from
the mobile UI. Local builds automatically use a sibling `TypeType-SDK`
checkout; set `TYPETYPE_SDK_PATH` when the SDK is stored elsewhere.

## Install

### F-Droid
Expand Down
Loading
Loading