Skip to content

Repository files navigation

WebhookNoteSender 🚀

CI Kotlin Compose Material3 API License Русский

Android app for capturing media (photo, video, audio) and sending it to an AI webhook endpoint.
Dual language support: English and Russian.


Preview 📱

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

Features ✨

  • 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

Architecture 🏗️

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

Clean Architecture layers

┌──────────────────────────────────────────────┐
│  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 │
└──────────────────────────────────────────────┘

Project structure

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

Tech Stack ⚡

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

Quick Start 🚀

Prerequisites

  • Android Studio Hedgehog (2023.1.1+) or IntelliJ IDEA
  • JDK 17+
  • Android SDK API 35

Build & Install

# 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)

Direct Gradle

./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk

Release Build

Release 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 --release

CI/CD & GitHub Secrets setup: Detailed guide on keystore creation, Base64 encoding, GitHub Secrets configuration, and the release process — see SETUP.md.


Build Script (build.sh) 🔧

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

Webhook Payload 📡

Request format

Attribute Value
Method POST
Content-Type application/json
Authorization Bearer <token> (optional)
Accept application/json
Timeout connect: 30s, read: 120s, write: 120s

JSON Payload

{
  "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

Queue retry strategy

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

Home Screen Shortcuts 📌

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:

  1. Long-press a profile card → Create Shortcut
  2. 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

CI/CD 🚀

Workflow: .github/workflows/build-apk.yml

Triggers

  • Push to main, develop
  • Pull Request to main
  • Push tag v* (e.g., v0.3)
  • Manual via workflow_dispatch

Pipeline

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

GitHub Secrets

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)

Release Process

git tag v0.4
git push origin v0.4
# CI: bump version → build → create GitHub Release

Localization 🌐

  • Englishapp/src/main/res/values/strings.xml (135 strings)
  • Russianapp/src/main/res/values-ru/strings.xml (135 strings)

How to add a new language

# 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:
# [![Deutsch](https://img.shields.io/badge/README-Deutsch-blue)](README.de.md)

Pro tip: All format placeholders (%1$s, %2$d) must match between locales — they are filled programmatically.


Permissions 🔐

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

Changelog 📋

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

License 📄

MIT


English Русский

About

Android app for capturing media (photo, video, audio) and sending it to an AI webhook endpoint. Features: profile management, media capture, WorkManager queue, shortcuts, bilingual UI (EN/RU), Material 3 dynamic color.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages