pressbox-android

Source: github.com/red-oxide/pressbox-android

The Android app for pressbox, a self-hosted feed reader whose feeds and users are declared in NixOS. It reads from your pressbox server over its HTTP API (/api/v1) and shares read and starred state with the other clients, such as the pressbox terminal reader and pressbox-darwin.

pressbox-android is in its early days. Expect rough edges, and features and behavior that may change between versions.

Requirements

  • Android 12 (API 31) or later.
  • A pressbox server the phone can reach. https is best; tailscale serve --bg 8470 on the server is the least work, and a reverse proxy with ACME works too. Plain http works, and the app warns when an http address isn't on the local network or a tailnet.
  • To build: JDK 17 and the Android SDK (platform 36), or nix develop, which provides both. Gradle comes from the wrapper.

Building

The repo is a Gradle project (Kotlin and Jetpack Compose) with one module, app/.

nix develop    # JDK 17, the Android SDK, adb; or use Android Studio
just build     # ./gradlew assembleDebug → app/build/outputs/apk/debug
just install   # ./gradlew installDebug, onto a connected device

Android Studio opens the directory as is. CI builds the debug APK and runs lint on every change (.github/workflows/android.yml) and keeps the APK as a workflow artifact.

just release builds a minified release APK, unsigned. Sign it with apksigner, or add a signingConfig to app/build.gradle.kts reading a keystore that stays out of the repo (*.jks and *.keystore are ignored).

Using it

On first launch, enter the server address and tap "Pair this device". The app shows a code like K7W-4QX: approve it on the server with sudo pressboxctl approve K7W-4QX, or from a device that is already paired (pressbox approve K7W-4QX, or Settings in either app). See pressbox's docs on pairing and users. "Use a token instead" takes a token from pressboxctl add-device.

The feed list has All items, Starred, a Tags section and the feeds, each with its unread count; feeds removed from the server's config are listed apart while they still have starred items. In an item list, swipe right to toggle read and left to toggle the star, or open an article to read it. Every screen keeps its controls in a bottom bar, within thumb reach: Settings and Refresh under the feeds; Mark all as read, the Unread/All filter and search under the items; star, read, share and open in the browser under an article. On a phone, Back (and Home, from an article) lead the bar, and the system back gesture works too. On a tablet or a wide window the lists and the article sit side by side.

Settings also has:

  • Approve a device, for pairing other clients from your phone. Admins can approve a device for another user.
  • Require unlock, which asks for your fingerprint, face or screen lock whenever the app comes back from the background, and keeps its content out of the recent apps screen.
  • Mark as read when scrolled past, off by default: items are marked read as they scroll off the top of the list while you scroll (not in search results).
  • Unread count on app icon: a silent notification with the count, which launchers show as a badge on the icon. A background job refreshes it about every half hour; Android decides exactly when. It needs the notification permission, which the app asks for when you turn it on.

The Server section shows the address, the signed-in user and device, and the server's version.

Opened articles, and the content of up to 30 unread items per refresh, are saved for offline reading. Articles are shown with JavaScript off, and links open in your browser. The token is encrypted with a key in the Android Keystore that can't leave the device, and the app's data is excluded from backups and device transfers.

Podcasts

Podcast episodes (items with audio or video) are marked in the list: a waveform, a half circle once started, a speaker while playing. Their article has Play (or "Resume from 12:34"), Video when there is one, and buttons for the chapters and transcript. Opening an episode doesn't mark it read; playing it to the end (or stopping in its last 30 seconds) does.

Episodes play in a Media3 (ExoPlayer) media session service, so they keep playing with the screen off or the app closed, and the notification, lock screen, headphones and Bluetooth controls work. While one plays, a bar sits along the bottom; tap it for the full player: video (with Media3's full-screen button), a scrubber, back 10 / forward 30 seconds, previous and next chapter, the speed (1× to 2×, and 0.75×, keeping the pitch), the chapters and the transcript, which follows along and jumps where you tap.

The service saves the position on the server every 15 seconds and on pause, so playback resumes where you left off here or in any other pressbox client, even if the app's screens were closed while it played.