133 lines
5.4 KiB
Markdown
133 lines
5.4 KiB
Markdown
# 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](#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
|
|
|
|
1. Open the project in **Android Studio** (Ladybug or newer). This automatically generates
|
|
the Gradle wrapper (`gradlew` + `gradle-wrapper.jar`).
|
|
2. Connect a device or start an emulator and press **Run**.
|
|
|
|
Or from the command line (after `gradle wrapper` once):
|
|
|
|
```bash
|
|
./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).
|
|
|
|
```bash
|
|
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`):
|
|
|
|
```properties
|
|
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)
|
|
|
|
1. Create an API token in Paperless: **Settings → profile → API token** (or via `/api/token/`).
|
|
2. 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.
|