A conversation analysis app that helps you decode conversations, understand communication patterns, and build better relationships. Built by Digital ABCs.
Paste or type a conversation and Text Decoder uses AI (Google Gemini) to:
- Split conversations into individual messages with speaker identification
- Analyse communication patterns and behaviours
- Provide insights from a curated behaviour library
- Track conversation history with profiles
Available on iOS (App Store / TestFlight), Android (Google Play), and Web.
| Layer | Technology |
|---|---|
| Frontend | Flutter / Dart (iOS, Android, Web, Desktop) |
| Backend API | Python / Flask on Google Cloud Run |
| AI | Google Gemini via Google Gen AI SDK |
| Auth | Firebase Auth (Google Sign-In, Apple Sign-In) |
| Hosting | Cloud Run (API + web container) |
| Payments | In-App Purchases (Apple/Google) |
| CI/CD | Codemagic (mobile), Cloud Build (API + web) |
Decoder now uses a strict environment split:
| Environment | Firebase Project | Purpose |
|---|---|---|
| Staging | text-decoder-app |
TestFlight builds, shared QA/demo accounts, seeded fake conversation data |
| Production | device-streaming-34aa2596 |
App Store / released builds and real users only |
| Local | Firebase emulators | Safe local development with no live-user impact |
Rules:
- TestFlight uses staging, not production.
- App Store / released builds use production.
- Fake/demo seed data belongs in staging only.
- Production may contain one empty internal QA account for smoke testing, but no demo corpus.
decoder/
├── app.py # Flask API backend
├── tests/ # Python backend tests
├── flutter_app/ # Flutter frontend (iOS/Android/Web/Desktop)
│ ├── lib/ # Dart source code
│ │ ├── screens/ # UI screens (auth, conversation, library, profile)
│ │ ├── services/ # API, auth, storage services
│ │ ├── models/ # Data models
│ │ ├── providers/ # State management (Provider)
│ │ ├── widgets/ # Reusable UI components
│ │ └── utils/ # Utilities
│ └── test/ # Flutter tests
├── data/ # Behaviour library data
├── scripts/ # Dev workflow scripts
├── firebase.json # Firebase config + emulators
├── firestore.rules # Firestore security rules
├── cloudbuild.yaml # Cloud Build - API deployment
├── cloudbuild-web.yaml # Cloud Build - Web deployment
├── codemagic.yaml # Codemagic CI/CD for mobile builds
├── Dockerfile # API container
└── requirements.txt # Python dependencies
# Create virtual environment
python -m venv .venv
source .venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Run locally
flask runcd flutter_app
# Get dependencies
flutter pub get
# Run on your platform
flutter run \
--dart-define=APP_ENV=local \
--dart-define=API_BASE_URL=http://localhost:8080 \
--dart-define=FIREBASE_WEB_API_KEY=... \
--dart-define=FIREBASE_WEB_APP_ID=... \
--dart-define=FIREBASE_WEB_MESSAGING_SENDER_ID=... \
--dart-define=FIREBASE_WEB_PROJECT_ID=... \
--dart-define=FIREBASE_WEB_AUTH_DOMAIN=... \
--dart-define=FIREBASE_WEB_STORAGE_BUCKET=... \
--dart-define=GOOGLE_WEB_CLIENT_ID=... # Default device
flutter run -d chrome --dart-define=APP_ENV=local --dart-define=API_BASE_URL=http://localhost:8080
flutter run -d ios --dart-define=APP_ENV=local --dart-define=API_BASE_URL=http://localhost:8080
flutter run -d android --dart-define=APP_ENV=local --dart-define=API_BASE_URL=http://localhost:8080# All tests
./scripts/test_all.sh
# Backend only
pytest tests/ -v --cov=app
# Flutter only
cd flutter_app && flutter testUse the guarded admin seeder to create/update Firebase Auth users plus synced Firestore data:
# Staging fake/demo data only
python scripts/seed_firebase.py \
--env staging \
--input scripts/seeds/staging_demo.seed.json
# Production internal QA account only
python scripts/seed_firebase.py \
--env production_qa \
--input scripts/seeds/production_qa.seed.jsonSafety rules:
stagingseeds can contain fake/demo users and fake conversation data.production_qaseeds requireallow_production=trueand reject demo data by default.production_qamanifests are limited to a single internal QA account unless you explicitly opt into production conversation seeding.
Text Decoder maintains an auditable record of user consent and safety events in Firestore, tied to each user's Firebase Auth UID. This data can be used to demonstrate duty of care and regulatory compliance.
| Record Type | Firestore Collection | What It Proves |
|---|---|---|
| AI Disclaimer Acknowledgement | user_consents |
User confirmed AI analysis is not a replacement for professional psychological, medical, or legal advice |
| Safety Concern Triggers | safety_events |
User's conversation triggered the compassionate safety response and they were shown emergency service details and helpline numbers |
| Age Verification | user_consents |
User confirmed they are 16 years of age or older (age_confirmed: true, minimum_age_requirement: 16) |
user_consents — One record per user consent event:
{
uid: "firebase-auth-uid",
consent_id: "sha256-hash",
terms_version: "1.0.0",
terms_accepted: true,
ai_disclaimer_acknowledged: true,
age_confirmed: true,
minimum_age_requirement: 16,
consent_timestamp: "2026-03-05T10:30:00Z",
ip_hash: "truncated-sha256",
user_agent_hash: "truncated-sha256",
created_at: <server timestamp>
}
safety_events — One record per safety concern detection:
{
uid: "firebase-auth-uid",
severity: "moderate" | "severe",
categories: ["physical violence", "threats to life", ...],
safety_resources_shown: true,
emergency_numbers_shown: true,
timestamp: <server timestamp>
}
No conversation text is stored in safety events (privacy by design). Only the category of concern and severity are recorded.
Firebase Console:
- Go to Firestore Database in the Firebase Console
- Select the
user_consentsorsafety_eventscollection - Filter by
uidto find records for a specific user
gcloud CLI — All consents for a specific user:
gcloud firestore documents list \
--collection-group=user_consents \
--filter="uid=USER_FIREBASE_UID" \
--project=YOUR_PROJECT_IDgcloud CLI — All safety events for a specific user:
gcloud firestore documents list \
--collection-group=safety_events \
--filter="uid=USER_FIREBASE_UID" \
--project=YOUR_PROJECT_IDCloud Logging (redundant log-based audit trail):
# Consent records
gcloud logging read 'resource.type="cloud_run_revision" AND textPayload:"Consent persisted to Firestore"' \
--project=YOUR_PROJECT_ID --limit=100 --format=json
# Safety concern detections
gcloud logging read 'resource.type="cloud_run_revision" AND textPayload:"Safety concerns detected"' \
--project=YOUR_PROJECT_ID --limit=100 --format=json- IP addresses are stored as truncated SHA-256 hashes (not raw IPs)
- User agents are stored as truncated SHA-256 hashes
- Safety events record concern categories only — no conversation text is stored
- All collections are protected by Firestore security rules: users can only read their own records, writes are restricted to the backend (Firebase Admin SDK)
| Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/api/v1/legal/consent |
POST | Required | Record user consent (persisted to Firestore) |
/api/v1/legal/terms |
GET | None | Get current terms version and consent requirements |
/api/v1/legal/safety-resources |
GET | None | Get helpline numbers and emergency service details |
| Platform | Method | Command |
|---|---|---|
| API | Cloud Build | gcloud builds submit --config=cloudbuild.yaml |
| Web | Cloud Build | gcloud builds submit --config=cloudbuild-web.yaml |
| iOS | Codemagic | Triggered via Codemagic dashboard or push |
| Android | Codemagic | Triggered via Codemagic dashboard or push |
The API expects these runtime secrets from Cloud Run / Secret Manager:
| Variable | Purpose |
|---|---|
APP_ENV |
Deployment environment (staging or production) |
FIREBASE_PROJECT_ID |
Firebase / GCP project the backend must use for the current environment |
GEMINI_API_KEY |
Google Gemini API access |
ENCRYPTION_KEY |
Fernet key for sync encryption |
APP_SECRET_KEY |
Flask session secret |
RATE_LIMIT_STORAGE_URI |
Redis connection for multi-instance rate limiting |
CORS_ALLOWED_ORIGINS |
Semicolon- or comma-separated allowed web origins |
Guardrail:
- API deploys now validate the configured
gemini-api-keysecret against the Gemini models endpoint before Cloud Run is updated. A Firebase web API key or any other invalid key should fail deploy instead of reaching runtime.
Release builds for mobile and web use --dart-define for public client config:
APP_ENVAPI_BASE_URLGOOGLE_WEB_CLIENT_IDGOOGLE_IOS_CLIENT_IDAPPLE_SERVICE_IDFIREBASE_PROJECT_IDFIREBASE_WEB_API_KEYFIREBASE_WEB_APP_IDFIREBASE_WEB_MESSAGING_SENDER_IDFIREBASE_WEB_PROJECT_IDFIREBASE_WEB_AUTH_DOMAINFIREBASE_WEB_STORAGE_BUCKETFIREBASE_WEB_MEASUREMENT_ID
Do not ship backend-only secrets in Flutter builds.
Important distinction:
FIREBASE_WEB_API_KEYis public client configuration and will appear in built web/mobile clients.GEMINI_API_KEY,APP_SECRET_KEY,ENCRYPTION_KEY, andRATE_LIMIT_STORAGE_URIare backend-only secrets and must never be passed to Flutter builds or bundled into client artifacts.
Native Firebase files are environment-specific:
- staging iOS / Android configs must target
text-decoder-app - production iOS / Android configs must target
device-streaming-34aa2596 - use
flutter_app/scripts/install_firebase_config.pyin CI to install the correct native config before building
- iOS staging: TestFlight via
ios-testflight-staging - iOS production: App Store via
ios-release-production - Android: Firebase App Distribution or Google Play
- Web: Cloud Run static web container
Deployment helpers:
scripts/deploy_api_staging.shscripts/deploy_api_production.shscripts/deploy_web_staging.shscripts/deploy_web_production.sh
Security helpers:
python scripts/validate_gemini_secret.py --project text-decoder-apppython scripts/validate_gemini_secret.py --project device-streaming-34aa2596python scripts/audit_client_artifacts.py --path flutter_app/build/web
See archived/TESTER_DISTRIBUTION.md for detailed tester onboarding instructions.