A card-based learning game for iPhone and Android, built with Expo (React Native). It pulls your iNaturalist observations and quizzes you on the species you’ve seen.
Plus a Lexicon (browse/search every species you’ve observed, filter by how well you know them, tap through to a detail page) and a Statistics page (lifetime accuracy, most-missed and best-known species).
Most of the app runs in the Expo Go app:
npm start
npx expo start --tunnel.)For a standalone build on a device, this is a CNG project — run
npx expo prebuild then npx expo run:ios (or run:android).
Pure logic (gestures, cache/sync, quiz, lexicon) is unit-tested:
npm test
App.js # screen state machine + data orchestration
index.js # Expo entry point
src/
api.js # iNaturalist v2 API client + helpers
storage.js # AsyncStorage: username, stats, prefs, obs cache
cache.js # local photo-cache size / clear (expo-file-system)
prefetch.js # preloads upcoming card images
quiz.js # multiple-choice distractor logic (pure, tested)
lexicon.js # Lexicon filtering/status logic (pure, tested)
gestures.js # gesture decision helpers (pure, tested)
theme.js # colors + monotone icon mapping
components/ # Icon, ScreenHeader, PhotoViewer
screens/ # Menu, Study, PickImage, Custom, Settings,
# Results, Stats, Lexicon, Detail
hooks/ # (none currently)
scripts/ # node test runners (npm test)
assets/ # app icon + splash (placeholder green)
assets/*.png are solid-green placeholders — swap in a
real 1024×1024 icon and splash before publishing.npx eas build.Errors are reported via Sentry — but only when a DSN is
configured. Set it in app.json → expo.extra.sentryDsn (or the SENTRY_DSN
env var). With no DSN, Sentry is a no-op, so it stays silent in Expo Go and
local dev. Reporting is crash/error-only (no performance tracing, no PII).
The @sentry/react-native config plugin is intentionally not enabled yet —
it adds a build step that uploads source maps to Sentry, which fails without a
Sentry org + auth token. Re-add "@sentry/react-native" to expo.plugins once
you’ve set up the account and a SENTRY_AUTH_TOKEN (so crash stack traces map
back to source). Until then, errors are still captured; their traces just point
at the minified bundle.
Copyright (C) 2026 turbolya.
Licensed under the GNU Affero General Public License v3.0 — see LICENSE. You may use, modify, and share this code, but any derivative work or networked service built from it must also be released in full under the AGPL-3.0. It may not be incorporated into closed-source or proprietary software.
One additional permission applies, in LICENSE-EXCEPTION: the app may be distributed through the Apple App Store, Google Play and comparable platforms despite the store terms that would otherwise conflict with the AGPL-3.0 (device limits, store-signed binaries). It covers distribution only — every copyleft obligation above stays in force.
Contributions are accepted under the AGPL-3.0 plus that permission, so it keeps covering the whole work as the codebase grows — see CONTRIBUTING.md.