Skip to content

An experiment into Calm Technology - Kids Reading Assistant is a cross-platform Expo/React Native app to track kids’ reading sessions: daily goals, streaks, per-kid totals, i18n (EN/EL/HI), local reminders & more.

License

Notifications You must be signed in to change notification settings

dmsfiris/kids-reading

Repository files navigation

Kids Reading — Expo + React Native

Kids Reading helps families build healthy reading habits. Create kid profiles, set daily goals, track progress & streaks, and (optionally) get gentle daily reminders — all stored on‑device (no accounts, no cloud).

Built with Expo, React Native, Expo Router, and Zustand. Offline‑first by design.


Table of Contents


Features

  • Multi‑Kid Profiles — Add, rename, make primary, remove (cap configurable).
  • Reading Timer — Start / Pause / Resume / Hold to Save & End.
  • Daily Goal Ring — Progress toward the current child’s goal.
  • Totals & Streaks — Today / Week / Month totals, current & best streaks.
  • Recent Sessions — Review and delete with confirmation.
  • Celebration UX — Confetti + (native) haptics when the goal is reached (once/kid/day).
  • Local Notifications
    • “Goal reached” instant notification (iOS/Android).
    • Optional daily reminder at HH:MM (iOS/Android).
    • Web falls back to a short chime (no scheduled notifications).
  • Multilingual — English, Ελληνικά, हिन्दी.
    • Digit localization (e.g., Hindi numerals where it makes sense).
    • Smart Greek uppercase (accent stripping in headings).
  • Offline‑first — All data lives on device (AsyncStorage / localStorage).
  • Consistent UI — Small design‑system components (Button, Card, Pill, etc.).

Tech Stack

Layer Technology
Framework Expo SDK 54, React Native 0.81, React 19
Navigation Expo Router v6 (typed routes, tabs)
State Zustand v5
i18n i18next + react‑i18next (+ helpers)
Notifications expo‑notifications
Haptics / Celebration expo‑haptics, react‑native‑confetti‑cannon
Animations / SVG react‑native‑reanimated, react‑native‑svg
Storage AsyncStorage (native) / localStorage (web)
Lint / Types ESLint, TypeScript 5
Tests Jest + jest‑expo

Project Structure

kids-reading/
├─ app/
│  ├─ (tabs)/
│  │  ├─ _layout.tsx        # Tab bar: Home, Kids, Dashboard, Settings, About
│  │  ├─ index.tsx          # Home/Timer (confetti, haptics, goal notifications, web chime)
│  │  ├─ kids.tsx           # Manage kids
│  │  ├─ dashboard.tsx      # Totals, streaks, recent sessions
│  │  ├─ settings.tsx       # Per‑kid goal, language, daily reminder
│  │  └─ explore.tsx        # “About” tab (UI label = About)
│  ├─ onboarding.tsx        # First‑run setup
│  └─ modal.tsx             # Example modal
│
├─ src/
│  ├─ config/               # App constants & theme
│  │  └─ constants.ts
│  ├─ hooks/
│  │  └─ useFocusSession.ts # Keep‑awake + end‑reminder scheduling (with channel name sync)
│  ├─ lib/                  # Utilities
│  │  ├─ i18n.ts            # i18next init
│  │  ├─ i18n-helpers.ts    # useLabel(), tSafe(), etc.
│  │  ├─ notifications.ts   # Daily reminder + notifyGoalReached()
│  │  ├─ time.ts            # Time helpers (windows, formatters)
│  │  ├─ text.ts            # Localized digits, Greek uppercase
│  │  ├─ sound.ts           # Web chime (prepareChime/playChime)
│  │  ├─ appInit.ts         # One‑time app init (migrations + Android channel bootstrap)
│  │  └─ storage.ts         # AsyncStorage/localStorage abstraction
│  ├─ store/
│  │  ├─ useReadingStore.ts # Kids, sessions, parent prefs, active timer
│  │  └─ useCelebration.ts  # Goal celebration guard
│  ├─ components/
│  │  ├─ CelebrationOverlay.tsx
│  │  ├─ timer/TimerRing.tsx
│  │  └─ ui/{Button,Card,Pill,Row,SectionTitle,ConfirmDialog,ActionMenu}.tsx
│  └─ locales/              # en/el/hi translations
│
├─ assets/
│  ├─ images/{icon.png, favicon.png, splash-icon.png, android-icon-*.png, notification-icon.png}
│  └─ sounds/chime.mp3
│
├─ app.json                 # Expo config
├─ package.json             # Scripts, deps, Jest config
├─ tsconfig.json            # Path aliases (@"/*")
├─ babel.config.js          # Module resolver for "@/"
├─ .eslintrc.*              # ESLint config
└─ README.md

Getting Started

1) Requirements

  • Node.js ≥ 20.19, npm ≥ 10
  • Android Studio / Xcode for emulators (or real device + Expo Go)

2) Install

npm install

3) Quick health check

npx expo-doctor

4) Run

npm run android   # Android
npm run ios       # iOS
npm run web       # Web (no scheduled notifications; chime fallback)

On first launch with no kids, you’ll be redirected to /onboarding. After completion (first kid + language + reminder), the app goes to the tab bar.


Configuration

Expo config (app.json) — important bits

{
  "expo": {
    "ios": {
      "infoPlist": {
        "NSUserNotificationUsageDescription": "We use notifications to remind you about reading goals."
      }
    },
    "android": {
      "permissions": ["POST_NOTIFICATIONS"],
      "edgeToEdgeEnabled": true,
      "predictiveBackGestureEnabled": false
    },
    "notification": {
      "icon": "./assets/images/notification-icon.png",
      "color": "#3B82F6"
    },
    "plugins": [
      "expo-router",
      ["expo-splash-screen", { "image": "./assets/images/splash-icon.png", "imageWidth": 200 }],
      "expo-notifications"
    ],
    "experiments": {
      "typedRoutes": true,
      "reactCompiler": true
    }
  }
}

Assets

  • Android status‑bar notification icon (assets/images/notification-icon.png): solid white on transparent, ~96×96.
  • Adaptive icon (Android): android-icon-foreground.png, android-icon-background.png, android-icon-monochrome.png.
  • Splash: splash-icon.png; Favicon: favicon.png.
  • Web chime: assets/sounds/chime.mp3.

First‑Run & Navigation

  • app/_layout.tsx declares the root stack and calls ensureAppInit() once (migrations + Android channel bootstrap).
  • If the store has no kids, app/index.tsx redirects to /onboarding.
  • Tabs live in app/(tabs)/_layout.tsx and render Home, Kids, Dashboard, Settings, About.

Notifications

Goal reached (iOS/Android)

  • Triggered in Home when progress hits the daily goal:
    • Haptic feedback → Confetti overlay → notifyGoalReached().
    • Tapping the notification routes back to Home.

Daily reminder (iOS/Android)

  • Toggle + time in Settings; saved to local state then scheduled via setupDailyReminder(HH:MM).
  • Cancelling uses cancelDailyReminders().

Web fallback

  • Web can’t schedule local notifications; when a goal is reached we play a short chime (playChime()).
    Make sure to call prepareChime() on a user gesture first (e.g., Start button) to unlock audio.

Android channel name localization

  • useFocusSession.syncSessionChannelName(label("channel_sessions_name", "Reading Sessions")) keeps the “sessions” channel display name in sync with the current language.
  • Called after language changes in Settings.

Internationalization

  • All UI strings go through useLabel() or tSafe() to ensure strong fallbacks.
  • To add a key:
    1. Add to src/locales/{en,el,hi}.json.
    2. Reference via label("your_key", "Fallback").
  • Greek headings use toUpperLocalized() to remove accents where appropriate.
  • Numbers shown to the user can use formatNumberForLang(value, lang) to localize digits (e.g., Hindi).

Testing & Linting

Scripts

npm run test       # Jest
npm run lint       # ESLint
npm run lint:fix   # ESLint with --fix
npm run typecheck  # TypeScript
npm run check      # typecheck + lint

Jest globals in ESLint

If you see describe/test/expect “not defined”, ensure your ESLint has a Jest override:

// .eslintrc.json (example)
{
  "extends": ["expo", "plugin:react-hooks/recommended", "plugin:@typescript-eslint/recommended"],
  "overrides": [
    {
      "files": ["**/__tests__/**/*.{js,jsx,ts,tsx}"],
      "env": { "jest": true }
    }
  ]
}

(Alternatively install and enable plugin:jest/recommended.)


Known Warnings

  • React Native Web: props.pointerEvents is deprecated. Use style.pointerEvents
    We’ve updated overlays (e.g., CelebrationOverlay) to use style={{ pointerEvents: "none" }}.
  • expo-notifications on web: “Listening to push token changes is not yet fully supported on web” — informational only.
  • Audio on web: Browsers block autoplay — call prepareChime() from a user gesture before playChime().

Troubleshooting

  • Doctor: npx expo-doctor to spot version/config mismatches.
  • Notification icon: Must be solid white on transparent (Android), or notifications may render as a gray square.
  • Multiple React copies / invalid hooks: Ensure only one react is installed and hoisted; clear caches with npm run reset-project (see scripts/reset-project.js).

Privacy

  • All data is stored locally on the device. No accounts. No analytics. No tracking.

License

MIT — see LICENSE. Contributions welcome!

About

An experiment into Calm Technology - Kids Reading Assistant is a cross-platform Expo/React Native app to track kids’ reading sessions: daily goals, streaks, per-kid totals, i18n (EN/EL/HI), local reminders & more.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published