docs/FDROID.md covers both a self-hosted F-Droid repo and the official f-droid.org route (incl. the prebuilt-OpenCV caveat). scripts/update-fdroid-repo.sh pulls the release APKs from Gitea and rebuilds the signed index. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PaperScan
A local, privacy-conscious document scanner for Android. Scan paper documents with your camera, automatically detect the edges, correct the perspective, and bundle multiple pages into a single PDF. That PDF can be shared through Android or uploaded directly to your self-hosted Paperless-ngx.
Everything happens on the device. Camera frames, edge detection (OpenCV) and PDF creation never leave your phone. The only network connection is the optional upload to the Paperless server you configure yourself. There is no AI or cloud processing.
Features
- 📷 Camera preview with multi-page capture (add pages one by one)
- 🟢 Live edge-detection overlay while aiming — see the document outline before you capture
- ✂️ Automatic edge detection + perspective correction (OpenCV), with manual corner adjustment
- 🎨 Per-page filters: color, grayscale, black & white (document mode)
- 🔄 Rotate, reorder and delete pages
- 📄 Export to a multi-page PDF (A4 or original size)
- ☁️ Direct upload to Paperless-ngx or share via the Android share sheet
- ✅ Real upload feedback — the app waits for Paperless to consume the document and reports Added / Duplicate / Failed (with a Don't wait option to skip the wait)
- 🔊 Capture feedback: shutter sound (only when the ringer isn't silenced) + a brief flash
- 🌍 Localized UI (see Languages)
- 🔐 API token stored encrypted (Android Keystore / EncryptedSharedPreferences)
Tech
Kotlin · Jetpack Compose · CameraX · OpenCV (org.opencv:opencv via Maven) ·
PdfDocument · OkHttp · DataStore. minSdk 26 (Android 8), target/compile SDK 35.
Building locally
- Open the project in Android Studio (Ladybug or newer). This automatically generates
the Gradle wrapper (
gradlew+gradle-wrapper.jar). - Connect a device or start an emulator and press Run.
Or from the command line (after gradle wrapper once):
./gradlew assembleDebug # debug APK, no signing required
./gradlew assembleRelease # release APK, requires signing (see below)
The release APK ends up in app/build/outputs/apk/release/.
Creating a signing key
Required for release builds (and therefore for updates that can overwrite each other).
keytool -genkey -v -keystore release.jks -keyalg RSA -keysize 2048 \
-validity 10000 -alias paperscan
Keep release.jks safe and out of git (already in .gitignore).
For local release builds, create a keystore.properties in the project root
(see keystore.properties.example):
storeFile=/path/to/release.jks
storePassword=...
keyAlias=paperscan
keyPassword=...
Building via Gitea Actions
The workflow .gitea/workflows/build.yml builds a signed release APK on every version tag
(git tag v0.1.0 && git push --tags) or via a manual run, and publishes it as a Release
with the APK attached as a downloadable asset.
Add these secrets under Settings → Actions → Secrets in your Gitea repo:
| Secret | Contents |
|---|---|
SIGNING_KEYSTORE_BASE64 |
base64 -w0 release.jks (the keystore as base64) |
SIGNING_STORE_PASSWORD |
keystore password |
SIGNING_KEY_ALIAS |
e.g. paperscan |
SIGNING_KEY_PASSWORD |
key password |
The build uses env vars, so nothing sensitive ends up in the repo.
The build runs in a container (
mingc/android-build-box), so the Actions runner must be x86_64 — the Android build tools (aapt2) are x86_64-Linux only and won't run natively on an ARM64 runner.
Setting up Paperless-ngx (in the app)
- Create an API token in Paperless: Settings → profile → API token (or via
/api/token/). - In PaperScan, open the gear icon → enter the server URL (e.g.
https://paperless.home.lan) and the token → Test connection → Save.
Prefer https; with http:// the app warns that the token and documents travel over the
network unencrypted.
Languages
The UI ships in English (default), Dutch, German, French, Spanish, Italian and Portuguese. The app follows your device language automatically.
All strings live in app/src/main/res/values/strings.xml (English base). Contributing a
language is just adding a values-<code>/strings.xml with the same keys — e.g.
values-pl/strings.xml for Polish. Every string must be present in every locale (the release
lint enforces this).
Project layout
app/src/main/java/eu/geyskens/pdfscan/
├── PaperScanApp.kt # Application; loads OpenCV native libs
├── MainActivity.kt # Compose host + navigation
├── ScanViewModel.kt # scan session, rendering, export, upload
├── data/ # Page model, ScanFilter, SettingsRepository
├── scan/ # EdgeDetector, DocumentAnalyzer (live), ImageProcessor, BitmapIo
├── pdf/ # PdfBuilder (multi-page PDF)
├── paperless/ # PaperlessClient (OkHttp REST + task polling)
└── ui/screens/ # Camera, Pages, Crop, Export, Settings
UI strings are in res/values*/strings.xml (one file per language).
Roadmap
Tracked as issues in the repository:
- Batch import scans from the gallery (import existing photos as pages)
- More UI languages (contributions welcome)
OCR is intentionally not done on-device: Paperless-ngx already runs OCR (via OCRmyPDF/Tesseract) when it consumes the uploaded document.
License
PaperScan is free software licensed under the GNU General Public License v3.0 — see LICENSE. You may use, study, share and modify it; derivative works must stay under the GPL.
Store listing metadata (title, descriptions, changelogs) lives under
fastlane/metadata/android/, in the F-Droid/Fastlane layout.