Skip to content

johnsaurabh/Flashforge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flashforge

An iOS app that turns anything you read into flashcards.

Share text, screenshots, or web pages from any app into a study deck. Vision OCR extracts the text on-device. The Claude API generates the flashcards. SM-2 spaced repetition handles the rest.


Demo

Share Sheet Card Generation Study Mode Stats
Share Sheet Generating Study Stats

Features

  • Share Extension: shows up in the iOS share sheet of every app. Accepts text selections, images, and URLs.
  • On-device OCR: Vision framework extracts text from images locally. Nothing leaves the device until you confirm.
  • AI card generation: the Claude API reads the extracted text and produces concise question/answer pairs.
  • SM-2 spaced repetition: the algorithm used by Anki. Schedules each card based on your ratings, not a fixed interval.
  • 4-button rating system: Again / Hard / Good / Easy maps to SM-2 quality scores 0-5.
  • WidgetKit widget: small and medium sizes show today's due-card count on the Home Screen and Lock Screen.
  • Session summary: accuracy percentage, correct count, and streak after each session.
  • Stats tab: lifetime card count, due today, day streak, and ease-factor distribution chart (Swift Charts).
  • Keychain storage: the API key is stored in the iOS Keychain, not UserDefaults or source code.
  • Accessibility: VoiceOver labels on every interactive element, Dynamic Type throughout, Reduce Motion respected by the card flip animation.

Architecture

Three targets share one data layer through an App Group container.

+----------------------------------------------------------+
|                   App Group Container                    |
|           group.com.yourname.flashforge                  |
+----------+------------------+---------------------------+
           |                  |                  |
      +----+----+       +-----+------+     +-----+---------+
      |  Main   |       |   Share    |     |   WidgetKit   |
      |  App    |       | Extension  |     |   Extension   |
      +---------+       +------------+     +---------------+

The App Group is the only shared state. Each target reads and writes the same JSON files through PersistenceService, a Swift actor that serializes concurrent access.

Full architecture documentation: docs/ARCHITECTURE.md


Tech Stack

Area Technology
UI SwiftUI (iOS 17)
Charts Swift Charts
OCR Vision framework (VNRecognizeTextRequest)
AI Anthropic Messages API (claude-opus-4-7)
Networking URLSession with async/await
Persistence JSON + FileManager (App Group container)
Secure storage iOS Keychain (SecItemAdd / SecItemCopyMatching)
Extensions Share Extension (UIKit), WidgetKit
Concurrency Swift actors, @MainActor, structured concurrency
Testing XCTest + URLProtocol stubbing
Project config XcodeGen (project.yml)

Project Structure

Flashforge/
├── project.yml                    # XcodeGen config (generates .xcodeproj)
│
├── Flashforge/                    # Main app target
│   ├── App/
│   │   ├── FlashforgeApp.swift    # Entry point, TabView scaffold
│   │   └── AppState.swift         # @MainActor ObservableObject, single source of truth
│   ├── Models/
│   │   ├── Card.swift             # SM-2 fields, isDue computed property
│   │   ├── Deck.swift
│   │   ├── StudySession.swift     # Review audit log and accuracy calculation
│   │   └── GeneratedCard.swift    # DTO returned by Claude before persistence
│   ├── Services/
│   │   ├── SpacedRepetitionService.swift  # Pure SM-2 algorithm (enum, no I/O)
│   │   ├── ClaudeService.swift            # actor, Anthropic API calls
│   │   ├── OCRService.swift               # enum, Vision framework wrapper
│   │   ├── PersistenceService.swift       # actor, atomic JSON persistence
│   │   └── KeychainService.swift          # Keychain read/write/delete
│   ├── ViewModels/
│   │   └── StudyViewModel.swift           # Session state machine
│   └── Views/
│       ├── Home/       HomeView, DeckRowView
│       ├── Deck/       DeckDetailView
│       ├── Study/      StudyView, FlashCardView, RatingBar, SessionSummaryView
│       └── Settings/   SettingsView, StatsView
│
├── FlashforgeShare/               # Share Extension target
│   └── ShareViewController.swift  # UIKit: OCR -> Claude -> persist
│
├── FlashforgeWidget/              # WidgetKit target
│   └── FlashforgeWidget.swift     # Small and medium widget, 9 AM refresh
│
├── FlashforgeTests/               # Unit tests
│   ├── SpacedRepetitionTests.swift # 11 tests covering SM-2 edge cases
│   └── ClaudeServiceTests.swift    # URLProtocol-stubbed API parsing tests
│
└── docs/
    ├── ARCHITECTURE.md
    └── SETUP.md

SM-2 Algorithm

The scheduling logic is a pure function with no dependencies. Input is a card and a quality rating; output is a mutated copy with updated scheduling fields.

static func processReview(card: Card, quality: Quality) -> Card {
    var updated = card
    if quality.rawValue >= 3 {
        switch updated.repetitions {
        case 0:  updated.interval = 1
        case 1:  updated.interval = 6
        default: updated.interval = Int((Double(updated.interval) * updated.easeFactor).rounded())
        }
        updated.repetitions += 1
    } else {
        updated.repetitions = 0
        updated.interval    = 1
    }
    let newEF = updated.easeFactor + (0.1 - Double(5 - quality.rawValue) * (0.08 + Double(5 - quality.rawValue) * 0.02))
    updated.easeFactor     = max(1.3, newEF)
    updated.nextReviewDate = Calendar.current.date(byAdding: .day, value: updated.interval, to: .now)!
    return updated
}

Reference: SuperMemo SM-2


Tests

# Xcode shortcut
cmd+U

# xcodebuild
xcodebuild test -scheme Flashforge -destination 'platform=iOS Simulator,name=iPhone 15'

SpacedRepetitionTests (11 tests): pure algorithm, no I/O.

  • New card is due immediately
  • First review sets interval to 1 day, second to 6
  • Failed review resets repetitions and interval to 1
  • Ease factor never drops below 1.3 (SM-2 invariant)
  • Due card filter and sort order

ClaudeServiceTests (3 tests): URLProtocol-stubbed, no network calls.

  • Parses a valid Claude response envelope into [GeneratedCard]
  • Throws ClaudeError.invalidResponse on HTTP 429
  • Throws ClaudeError.missingAPIKey when Keychain is empty

Getting Started

See docs/SETUP.md for the full setup guide.

brew install xcodegen
git clone https://github.com/johnsaurabh/Flashforge
cd Flashforge
xcodegen generate
open Flashforge.xcodeproj

Add your Claude API key in the Settings tab after first launch.


What I Would Add Next

  • CloudKit sync: the data model is already Codable; CloudKit private database is the logical next step
  • PDF import: PDFKit for direct document ingestion without the Share Extension
  • Configurable card count: let the user choose how many cards Claude generates per share
  • Review history chart: per-deck accuracy over time using Swift Charts
  • Live Activity: show study session progress on the Dynamic Island

License

MIT

About

iOS app that turns anything you read into flashcards using OCR and the Claude API

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages