Android app for capturing media (photo, video, audio) and sending it to an AI webhook endpoint.
Dual language support: English and Russian.
| Profiles | Queue | Audio Recording | Shortcuts |
|---|---|---|---|
| Create & manage profiles | Track pending/sent messages | Record audio with timer | Home screen shortcuts |
| Card-based UI with capture | Swipe to delete, retry failed | Foreground service | One-tap capture from launcher |
- Profile management — create, edit, delete named profiles with webhook URL, prompt, bearer token, and media type
- Media capture — take photo/video with system camera or record audio with MediaRecorder
- Base64 encoding — streaming encode, temp file deleted immediately after encoding
- Queue system — Room database queue with WorkManager background processing
- Automatic retry — exponential backoff (30s → 5min max), retries when network becomes available
- Smart error handling — HTTP 4xx (except 408/429) →
FAILED, HTTP 5xx / network error →PENDING+ retry - Home screen shortcuts — pinned shortcuts for one-tap capture without opening the app
- Dynamic shortcuts — long-press app icon menu for quick capture
- Foreground audio recording — persistent notification with stop action
- Dark / Light / System theme — Material 3 dynamic color with manual override
- Bilingual UI — English and Russian, all strings in resources
- JSON payload — proper
{"messages": [...]}format as per spec
WebhookNoteSenderApp (Hilt Application)
└─ MainActivity (ComponentActivity + Compose)
└─ AppNavigation (Navigation Compose, Bottom Nav)
├─ ProfilesScreen — list of profiles as cards
├─ ProfileEditScreen — create/edit profile form
├─ QueueScreen — queue items with status indicators
└─ SettingsScreen — theme & language selection
└─ ShortcutReceiverActivity — transparent activity for shortcuts
└─ AudioRecorderService — foreground service for audio recording
┌──────────────────────────────────────────────┐
│ UI Layer (Compose + ViewModels) │
│ ProfilesScreen · QueueScreen · Settings │
├──────────────────────────────────────────────┤
│ Domain Layer │
│ MediaType · QueueStatus · ThemeMode │
├──────────────────────────────────────────────┤
│ Data Layer │
│ Room DB · Repositories · WebhookApi (OkHttp)│
├──────────────────────────────────────────────┤
│ Infrastructure │
│ WorkManager · Hilt DI · DataStore · CameraX │
└──────────────────────────────────────────────┘
webhooknotesender/
├── app/src/main/java/com/kascorp/webhooknotesender/
│ ├── WebhookNoteSenderApp.kt # Hilt Application
│ ├── MainActivity.kt # Single Activity
│ ├── ShortcutReceiverActivity.kt # Transparent shortcut activity
│ ├── di/ # Hilt modules
│ │ ├── AppModule.kt # DataStore, OkHttp, JSON
│ │ ├── DatabaseModule.kt # Room database
│ │ └── RepositoryModule.kt # Repository bindings
│ ├── data/
│ │ ├── local/ # Room DB, DAOs, Entities
│ │ ├── repository/ # ProfileRepository, QueueRepository
│ │ ├── remote/ # WebhookApi (OkHttp)
│ │ └── model/ # MediaType, QueueStatus, ThemeMode
│ ├── work/ # QueueWorker (CoroutineWorker)
│ ├── ui/
│ │ ├── theme/ # Material 3 dynamic color
│ │ ├── navigation/ # NavHost + bottom nav
│ │ ├── profiles/ # Profile list + edit
│ │ ├── queue/ # Queue list
│ │ ├── settings/ # Settings
│ │ └── components/ # CaptureButton, AudioRecorder, StatusBadge
│ └── util/ # Base64Encoder, NetworkMonitor, ShortcutHelper
├── app/src/main/res/
│ ├── values/strings.xml # English (135 strings)
│ └── values-ru/strings.xml # Russian (135 strings)
├── build.sh # Universal build script
├── .github/workflows/build-apk.yml # CI/CD pipeline
└── AGENTS.md # AI agent documentation
| Component | Technology |
|---|---|
| Language | Kotlin 2.4.10 |
| UI | Jetpack Compose + Material 3 (BOM 2026.06.01) |
| Navigation | Navigation Compose 2.9.8 (Bottom Nav) |
| DI | Dagger Hilt 2.60.1 (KSP) |
| Database | Room 2.8.4 (KSP) |
| HTTP Client | OkHttp 5.4.0 |
| Background | WorkManager + CoroutineWorker |
| Serialization | kotlinx.serialization 1.7.3 |
| Preferences | DataStore Preferences 1.2.1 |
| Camera | CameraX 1.4.1 + ActivityResultContracts |
| Audio | MediaRecorder + Foreground Service |
| Video Compression | GZIP (file bytes) — transcoding removed |
| Coroutines | Kotlinx Coroutines 1.11.0 |
| Build | AGP 9.3.1 / Gradle 9.5.0 / compileSdk 37 |
| Release build | R8 minify + resource shrink (APK ~2.5 MB) |
| minSdk / targetSdk / compileSdk | 26 / 35 / 37 |
| Testing | JUnit 4.13.2, Robolectric 4.16.1 |
- Android Studio Hedgehog (2023.1.1+) or IntelliJ IDEA
- JDK 17+
- Android SDK API 35
# Make the build script executable
chmod +x build.sh gradlew
# Debug build
./build.sh
# Build, install, and launch on device
./build.sh --run
# Install APK on device
./build.sh --install
# Launch app on device
./build.sh --launch
# Clear app data
./build.sh --clear
# Show filtered logcat
./build.sh --logs
# Send test POST to webhook
./build.sh --test https://your-webhook.com/endpoint
./build.sh --test https://your-webhook.com/endpoint your-bearer-token
# Open in Android Studio
studio . # or: android-studio . (Linux) / open -a "Android Studio" . (macOS)./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apkRelease builds are minified with R8 (isMinifyEnabled + isShrinkResources) — APK size dropped from 3.2 MB to ~2.5 MB, single classes.dex (no Xiaomi antivirus warning).
# Release with debug keystore (no KEYSTORE_PASSWORD env var)
export KEYSTORE_PATH="$(pwd)/webhooknotesender-release.jks" # optional, default: ../webhooknotesender-release.jks
./build.sh --release
# Release with production keystore
export KEYSTORE_PASSWORD=your-password
./build.sh --releaseCI/CD & GitHub Secrets setup: Detailed guide on keystore creation, Base64 encoding, GitHub Secrets configuration, and the release process — see SETUP.md.
Comprehensive bash script for debugging, building, and testing:
| Flag | Description |
|---|---|
| (no flags) | Build debug APK |
--run |
Build debug APK, install, and launch |
--release |
Build release APK (signed with production or debug keystore) |
--install [apk] |
Install APK on device (default: debug) |
--launch |
Launch app on connected device |
--clear |
Clear app data on device |
--logs |
Show logcat filtered by app package |
--test <url> [token] |
Send test POST to webhook |
--help |
Show help |
| Attribute | Value |
|---|---|
| Method | POST |
| Content-Type | application/json |
| Authorization | Bearer <token> (optional) |
| Accept | application/json |
| Timeout | connect: 30s, read: 120s, write: 120s |
{
"messages": [
{
"name": "profile_name",
"prompt": "Describe what the AI should do with this media...",
"datetime": "2026-07-18T12:00:00Z",
"type": "image",
"data": "/9j/4AAQSkZJRg...Base64-encoded-content...",
"encoding": "jpeg"
}
]
}| Field | Description |
|---|---|
name |
Profile name (snapshot at capture time) |
prompt |
AI prompt from profile configuration |
datetime |
ISO 8601 UTC timestamp of capture (yyyy-MM-dd'T'HH:mm:ss'Z') |
type |
Media type: image, audio, or video |
data |
Base64-encoded file content (NO_WRAP flag) |
encoding |
(optional) Compression: "jpeg" for images, "gzip" for audio/video, absent when compression is disabled |
| HTTP Status | Action |
|---|---|
| 200, 201, 204 | ✅ Mark as SENT, delete from queue |
| 408, 429 | 🔄 Keep PENDING, retry with exponential backoff |
| 4xx (except 408, 429) | ❌ Mark as FAILED, no retry |
| 5xx | 🔄 Keep PENDING, retry |
| Network error / timeout | 🔄 Keep PENDING, retry when network available |
Each profile can be pinned to the device home screen for one-tap capture:
| Type | Shortcut action |
|---|---|
| Image | Opens camera directly, no app UI |
| Video | Opens video camera directly, no app UI |
| Audio | Starts foreground recording service, no app UI |
How to create:
- Long-press a profile card → Create Shortcut
- System will prompt to confirm pinning to home screen
Dynamic shortcuts (app icon long-press menu):
- Top 5 most-used profiles (by usage frequency) are available in the long-press menu
- Updated automatically on startup, after captures, saves, and deletes
- Distinct
"app_shortcut_"ID prefix avoids collision with pinned shortcuts
Workflow: .github/workflows/build-apk.yml
- Push to
main,develop - Pull Request to
main - Push tag
v*(e.g.,v0.3) - Manual via
workflow_dispatch
Push
├─ lint — lintDebug (continue-on-error: true)
├─ test — testDebugUnitTest → upload test-results artifact
├─ locales — validate string keys match between EN and RU
└─ build-debug — assembleDebug → upload debug APK (7 days)
Tag push (after parallel jobs):
└─ build-release
├─ bump versionName from tag, increment versionCode
├─ commit + push version bump to main
├─ decode keystore from KEYSTORE_BASE64 secret
├─ assembleRelease (signed)
└─ upload release APK (30 days)
Tag push (after build-release):
└─ release
├─ download release APK
├─ generate changelog from git log
└─ create GitHub Release with APK
| Secret | Description |
|---|---|
KEYSTORE_BASE64 |
webhooknotesender-release.jks in base64 |
KEYSTORE_PASSWORD |
Keystore password |
KEY_ALIAS |
Key alias (default: webhooknotesender) |
KEY_PASSWORD |
Key password (default: same as KEYSTORE_PASSWORD) |
git tag v0.4
git push origin v0.4
# CI: bump version → build → create GitHub Release- English —
app/src/main/res/values/strings.xml(135 strings) - Russian —
app/src/main/res/values-ru/strings.xml(135 strings)
# 1. Create locale directory
mkdir -p app/src/main/res/values-de
# 2. Copy English strings
cp app/src/main/res/values/strings.xml app/src/main/res/values-de/strings.xml
# 3. Translate all <string> values (keep name attributes!)
# 4. Update SettingsScreen to include the new locale
# 5. Add badge link in README.md:
# [](README.de.md)Pro tip: All format placeholders (
%1$s,%2$d) must match between locales — they are filled programmatically.
| Permission | Purpose |
|---|---|
INTERNET |
Webhook HTTP requests |
ACCESS_NETWORK_STATE |
Network connectivity monitoring |
CAMERA |
Photo and video capture |
RECORD_AUDIO |
Audio recording |
POST_NOTIFICATIONS |
Foreground service notification (API 33+) |
FOREGROUND_SERVICE |
Audio recording service |
FOREGROUND_SERVICE_MICROPHONE |
Microphone foreground service type |
| Version | Date | Highlights |
|---|---|---|
| v0.4 | 2026-08-05 | Build migration (Gradle 9.5, Kotlin 2.4, Hilt KSP), R8 minification (APK 2.5 MB), security fixes (token out of nav args, retry cap at 10), dependency updates |
| v0.3-hotfix | 2026-07-27 | Retry empty JSON fix, dependency updates (Room 2.8.4, Nav 2.9.8, Coroutines 1.11.0), GitHub Secrets setup |
| v0.3 | 2026-07-19 | App Shortcuts (long-press), use_count tracking, shortcut lifecycle fixes, video transcode removed |
| v0.2 | 2026-07-18 | Initial feature release: profile CRUD, media capture, queue with WorkManager, shortcuts, bilingual UI |
| v0.1 | 2026-07-18 | Project scaffolding |
MIT