Skip to content

Leak-Shield-Technologies-LLC/medlingo-showcase

Repository files navigation

MedLingo — Medical Spanish for Healthcare Workers

A production Android app, shipped on Google Play, that teaches US healthcare workers Medical Spanish through branching patient-dialogue scenarios — fully offline, with an encrypted local database, on-device speech recognition, and Google Play Billing.

📱 Live on Play Store: com.medlingo.app

This repository is the public portfolio build. See SHOWCASE.md for the exact differences between this repo and the production app. The short version: the engineering is unchanged; the curated content (scenarios, phrases) is reduced to a 4-scenario demo subset that still exercises the entire codebase end-to-end.


The problem MedLingo solves

20 million US healthcare workers serve a population that includes ~29 million Spanish-speaking patients with limited English proficiency. Phone interpreter services take 3–8 minutes to connect; nurses, EMTs, and pharmacists have 10. The result is patient encounters where the first two minutes — the ones that build trust and surface chief complaints — happen in broken English or not at all.

MedLingo is a learning tool for that gap. Not a translator (a single misinterpreted word once cost a hospital $71M in a malpractice settlement, so the app is deliberately positioned away from clinical use), but a practice environment where a nurse can rehearse her actual intake questions on the bus to her shift.

The full product breakdown lives in docs/USER_PERSONAS.md and docs/APP_DESIGN.md. They are worth a skim — they show the engineering was built around five concrete users, not around abstract features.


Features

Feature Description
Branching patient dialogues Scripted multi-node conversations with choice quality (ideal/acceptable) and scoring on path coverage + time
Spaced repetition Leitner 5-box flashcard system with automatic review scheduling
On-device pronunciation feedback SpeechRecognizer (es_MX) + Levenshtein word matcher with Spanish normalisation (silent-H, accent folding)
Text-to-speech TextToSpeech (Mexican Spanish) on phrases, flashcards, and chat messages
Vocabulary quiz Multiple-choice quizzes generated from learned words
Phrase toolkit Role-filtered quick-reference phrases with safety-critical flagging
Daily goals & streaks Streak tracking with grace period and 1-per-week freeze
Achievements 8 milestones with celebration animations (Konfetti)
Home widget Glance-based 2×2 widget showing streak and due reviews
Adaptive difficulty Level-up suggestion card after sustained performance
Google Play Billing One-time premium unlock with restore-on-launch (handles reinstall / device switch)
100% offline No network dependencies. network_security_config.xml blocks cleartext.

Tech stack

  • Language: Kotlin (JVM 17)
  • UI: Jetpack Compose, Material 3, light theme only
  • Architecture: MVVM, package-by-feature
  • DI: Hilt
  • Persistence: Room with SQLCipher encryption (passphrase wrapped by Android Keystore, AES/GCM)
  • Async: Coroutines + Flow (StateFlow / SharedFlow)
  • Billing: Google Play Billing Library 7.x
  • Background work: WorkManager (periodic reminders)
  • Widget: Glance
  • Speech: Android SpeechRecognizer + custom Levenshtein matcher
  • Min SDK: 26 (Android 8.0) — Target SDK: 35 (Android 15)
  • Build: Gradle 8 + version catalog (gradle/libs.versions.toml), R8 full mode + resource shrinking on release
  • CI: GitHub Actions (.github/workflows/ci.yml) — runs tests, builds debug APK, builds release AAB, uploads artifacts

Engineering decisions worth scrutinising

These are the calls a reviewer is most likely to ask about. Files referenced are real and clickable on GitHub.

1. SQLCipher passphrase derived via Android Keystore, not hardcoded

DatabaseKeyManager.kt generates a 32-byte random passphrase on first launch, encrypts it with an AES-256 GCM key bound to Android Keystore (AndroidKeyStore), and stores the wrapped bytes in SharedPreferences. The plaintext passphrase never persists; the key never leaves the Keystore. This pattern survives backup-extraction attacks because Android Keystore keys are non-extractable.

2. Billing manager is the single source of premium truth — restore is idempotent

BillingManager.kt connects, queries premium_unlock, handles purchase flow, acknowledges purchases, and exposes productDetails / purchaseSuccess / purchaseError flows. restorePurchases runs on app start (handles reinstall / device switch) and uses a one-way guarantee: once premium is set true, a delayed sync that returns no purchases will not overwrite it. The defaults are isPremium = false everywhere, never true — a deliberate audit choice after an early build accidentally unlocked content for free.

3. Speech feedback runs on-device and tolerates Spanish-specific spelling

WordMatcher.kt and SpeechManager.kt wrap the platform SpeechRecognizer (es_MX locale) and grade output via Levenshtein distance after normalising silent-H (holaola), accent stripping (asíasi), and lowercase folding. The recogniser is Activity-scoped (one of the documented gotchas of SpeechRecognizer) rather than singleton, to avoid lifecycle leaks.

4. Room migrations are explicit; destructive fallback is debug-only

The schemas live under app/schemas/ and migrations are committed (v6 → v7 added indices without data loss). fallbackToDestructiveMigration is guarded behind BuildConfig.DEBUG so release builds never wipe a user's vocabulary history.

5. Adaptive UI without a god-object ViewModel

Each Compose screen has its own ViewModel (ChatViewModel.kt, HomeViewModel.kt, etc.) exposed via StateFlow. The home screen's level-up suggestion logic, scenario recommendation scoring, and streak grace-period calculation all live in their respective ViewModels rather than leaking into composables. The repository layer is plain Kotlin objects — testable without Robolectric.

6. Accessibility is treated as a feature

Every meaningful icon has a contentDescription. Touch targets are ≥ 48dp. Text uses sp for system scaling. Three Espresso a11y tests in app/src/androidTest/ run accessibility scanner checks against the home, phrases, and settings screens.


Project structure

app/src/main/java/com/medlingo/app/
├── data/
│   ├── local/        # Room DB, DAOs, encryption, DataStore preferences
│   └── repository/   # Scenarios, scripts, phrases, progress
├── domain/
│   ├── model/        # Domain types (@Immutable for Compose stability)
│   ├── BillingManager.kt
│   ├── SpeechManager.kt
│   ├── TtsManager.kt
│   ├── WordMatcher.kt
│   ├── DailyChallengeManager.kt
│   ├── StreakFreezeManager.kt
│   └── ReviewPromptManager.kt
├── di/               # Hilt modules
├── ui/
│   ├── chat/         # Scenario dialogue screen
│   ├── home/         # Home + recommendation cards + daily goal
│   ├── onboarding/   # 4-step flow
│   ├── disclaimer/   # Required-on-launch legal disclaimer
│   ├── paywall/      # Google Play Billing UI
│   ├── phrases/      # Role-filtered phrase toolkit
│   ├── progress/     # Stats, streaks, achievements
│   ├── quiz/         # Multiple-choice vocab quiz
│   ├── review/       # Leitner flashcard review
│   ├── settings/     # User preferences
│   ├── components/   # Reusable composables (Konfetti, mic UI, etc.)
│   ├── theme/        # Material 3 + brand green
│   └── widget/       # Glance home-screen widget
├── notification/     # WorkManager workers + NotificationHelper
└── widget/           # Widget receiver

docs/
├── APP_DESIGN.md            # Product IA, screen mockups, AI prompt strategy
├── USER_PERSONAS.md         # Five healthcare-worker personas the product is built for
├── play-store-listing.md    # Play Store listing copy
├── privacy-policy.html      # Privacy policy (offline app, no data collection)
└── screenshots/             # 8 Play Store screenshots

Build & run

./gradlew assembleDebug                # Debug APK
./gradlew assembleRelease              # Signed release AAB (requires keystore + local.properties)
./gradlew test                         # Unit tests
./gradlew connectedAndroidTest         # Instrumented tests (incl. a11y)
maestro test .maestro/                 # E2E flows
./gradlew assembleDebug && adb install -r app/build/outputs/apk/debug/app-debug.apk

The release build expects a keystore at medlingo-release.keystore and credentials in local.properties (MEDLINGO_STORE_PASSWORD, MEDLINGO_KEY_PASSWORD). Both are gitignored and not shipped in this showcase.


Testing

  • 54 unit tests — ViewModels (Chat, Home, Progress, Review, Quiz), repositories, WordMatcher (11 tests covering Levenshtein + Spanish normalisation), DailyChallengeManager
  • 3 instrumented tests — Espresso accessibility scanner against home, phrases, settings
  • 13 Maestro E2E flows — onboarding, navigation, chat, scoring, settings, persistence, empty states
  • CI — every push and PR to main: unit tests + debug APK + release AAB

Security & privacy

  • SQLCipher database encryption with Android Keystore-wrapped passphrase
  • No network traffic (cleartext blocked at the manifest level)
  • No patient data ever leaves the device
  • android:allowBackup="false", sensitive data excluded from extraction rules
  • ProGuard / R8 full mode + resource shrinking on release
  • Mandatory disclaimer on every app start
  • No third-party analytics, advertising, or tracking SDKs

Full policy: docs/privacy-policy.html.


Screenshots

Home Chat start Tip Score Toolkit Toolkit expanded Progress Review


Disclaimer

MedLingo is a language learning tool, not a medical interpreter, translation service, or clinical decision support tool. Real patient encounters always require qualified medical interpreters.


Built by

Nico Schöneburg — independent software engineer.

License

Released under the PolyForm Noncommercial License 1.0.0.

This is a source-available license, not an open-source license in the OSI sense. In plain English:

  • ✅ You may read, clone, run locally, and learn from this code — including in interviews, code reviews, and personal study.
  • ✅ Charitable organizations, educational institutions, public research / health / safety organizations, and government institutions may use the code for their non-commercial purposes.
  • ❌ You may not use this code, in whole or in part, for any commercial purpose — including shipping a derivative product, integrating it into a paid service, or using it inside a for-profit company's workflow.

The full text is in LICENSE. The canonical version lives at https://polyformproject.org/licenses/noncommercial/1.0.0.

For commercial licensing of this code or the underlying product (MedLingo on Google Play), contact license@nico-ai.de.

Releases

No releases published

Packages

 
 
 

Contributors

Languages