An Android app that turns screenshots into Google Calendar events using on-device OCR and an AI model (Groq by default, Gemini as fallback).
- Take a screenshot — when the floating preview appears in the corner, tap Share and select Captur (recommended). You can also share from the gallery later, or pick any image from the in-app gallery picker.
- On-device OCR (Google ML Kit) extracts the text — no image ever leaves the device
- Extracted text is sent to the AI model (Groq / Gemini) with the current date and timezone injected
- The model returns structured event JSON (title, time, location, recurrence, reminders)
- Review and edit the pre-filled form, then save to Google Calendar
- Framework: Expo 54 (bare workflow) + React Native 0.81 + TypeScript
- Navigation: Expo Router (file-based)
- OCR: Google ML Kit Text Recognition v2 via a custom Expo Module — fully on-device
- LLM: Groq (
llama-3.3-70b-versatile) by default; Gemini (gemini-3.1-flash-lite) as fallback — switched viaEXPO_PUBLIC_AI_PROVIDERat build time - Calendar: Google Calendar API v3 via OAuth 2.0 (PKCE with
expo-auth-session) - State: Zustand (with
expo-secure-storepersistence for settings) - Styling: NativeWind v4 (Tailwind CSS)
- Package manager: pnpm
app/
(tabs)/
index.tsx Home — how-to guide + gallery picker
settings.tsx Settings — Google auth, API key, screenshot deletion
capture.tsx OCR + AI pipeline (loading states)
review.tsx Editable event form + save to calendar
src/
services/
ocr.ts Calls the native expo-ocr module
ai.ts Provider dispatcher — reads EXPO_PUBLIC_AI_PROVIDER
groq.ts Groq REST client (llama-3.3-70b-versatile)
gemini.ts Gemini REST client with Zod response validation
calendar.ts Google Calendar API + OAuth token management
prompt.ts Shared prompt builder (date/tz injection, few-shot examples)
store/index.ts Zustand store (auth, settings) with secure persistence
types/index.ts Shared TypeScript types
utils/timezone.ts Device timezone via expo-localization
modules/
expo-ocr/ Custom Expo Module — ML Kit OCR + screenshot deletion (Kotlin)
android/ Native Android project (bare workflow)
- Node.js 20+, pnpm
- Android Studio with an emulator or a physical Android device (API 24+)
pnpm install- Go to Google Cloud Console and create a new project (or select an existing one).
- Enable the Google Calendar API:
- Go to APIs & Services → Enabled APIs & services
- Click Enable APIs & services
- Search for Google Calendar API and enable it
Before creating OAuth credentials you need to configure the consent screen.
- Go to APIs & Services → OAuth consent screen
- Fill in the app name (e.g.
Captur), user support email, and developer contact email, then save - Under Data Access → Add or remove scopes, add:
https://www.googleapis.com/auth/calendar.events.owned - Under Audience → Test users, add the Google account(s) you'll use to test the app
- Go to APIs & Services → Credentials → Create credentials → OAuth client ID
- Create a client — Android type:
- Package name:
com.zawaer.captur - SHA-1 certificate fingerprint (run from the repo root):
Password:
keytool -keystore android/app/debug.keystore -list -v
android. Copy the SHA-1 line from the output.
- Package name:
- Create a second client — Web application type:
- Under Authorized redirect URIs, add:
http://localhost
- Under Authorized redirect URIs, add:
- Copy the Web client ID into a new
.env.localfile at the repo root:EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID=your_web_client_id.apps.googleusercontent.com
Both clients are required. The Android client ID is needed by the Google Sign-In SDK on the device — without it you'll get a "non-recoverable sign in failure". The Web client ID is used by
expo-auth-sessionfor the PKCE authorization code flow; the Android client ID alone does not support this flow.
pnpm androidTriggers a full native build, compiles the expo-ocr ML Kit module, and installs the app.
The app defaults to Groq (llama-3.3-70b-versatile) for faster, more consistent inference. Gemini is available as a fallback. Set EXPO_PUBLIC_AI_PROVIDER to switch at build time.
- Go to console.groq.com/keys → Create API key
- Make sure
llama-3.3-70b-versatileis enabled at both the organization and project level in your Groq Cloud settings - Add the key to
.env.local:EXPO_PUBLIC_AI_PROVIDER=groq EXPO_PUBLIC_GROQ_API_KEY=your_groq_api_key
- Go to Google AI Studio → API Keys → Create API key
- Add it to
.env.local:EXPO_PUBLIC_AI_PROVIDER=gemini EXPO_PUBLIC_GEMINI_API_KEY=your_gemini_api_key
The recommended workflow is to share directly from the screenshot preview that pops up in the corner right after taking a screenshot — tap it, hit Share, and select Captur. This is faster than going to the gallery later.
You can also open any image in your gallery → tap Share → select Captur.
After capturing, Captur can automatically delete the source screenshot from your gallery so you don't end up with duplicates.
- Delete after capturing — toggle on/off in Settings (on by default)
- Silent auto-delete — when enabled (requires granting the Manage Media permission), the system confirmation dialog is skipped and the screenshot is deleted immediately
| Permission | Why |
|---|---|
INTERNET |
Gemini API and Google Calendar API calls |
READ_MEDIA_IMAGES |
Accessing images shared from the gallery |
MANAGE_MEDIA |
Silent screenshot deletion without a system confirmation dialog |
POST_NOTIFICATIONS |
(reserved for future use) |
-
Clarifying questions — when the AI is uncertain about key details (e.g. ambiguous timezone, missing year, unclear start time), it could ask the user a short follow-up question before pre-filling the form rather than guessing or failing silently.
-
Timezone inference — currently the device timezone is injected into the prompt and used when saving. This breaks when the screenshot contains times that are in a different timezone than the device (e.g. you're in the USA but the WhatsApp group is in Finland and the message says "meet at 18:00"). Some ideas, roughly ordered by effort:
- Gemini infers it — extend the Gemini response schema with an optional
timeZone: stringfield (IANA, e.g."Europe/Helsinki"). Gemini can often infer timezone from context clues: city/country names, language of the message, phone number prefixes, institution names, explicit UTC offset strings (+03:00). Fall back to device timezone when absent or unrecognised. - Show & let user edit timezone — add a timezone row to the Review screen (already has the globe icon placeholder). If Gemini returned one, pre-fill it; otherwise default to device. The user can tap to change it before saving.
- Clarifying question — if the AI is uncertain about timezone (no explicit clue in the text), this falls under the clarifying-questions TODO above: ask "Which timezone is this event in?" before pre-filling the form.
- Gemini infers it — extend the Gemini response schema with an optional
- OCR runs entirely on-device via Google ML Kit — no image data is sent to any server
- AI provider and API keys are configured via
EXPO_PUBLIC_AI_PROVIDER,EXPO_PUBLIC_GROQ_API_KEY, andEXPO_PUBLIC_GEMINI_API_KEYin.env.local - OAuth refresh tokens are stored in the Android Keystore via
expo-secure-store - Settings (deletion toggles) are persisted across restarts via
expo-secure-store - When the AI infers a date from relative text ("next Friday", "tomorrow"), the Review screen shows an amber Inferred date badge so you can verify it before saving
- If the screenshot contains no recognisable event, the app shows a friendly message rather than an error