Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ CircuitQuEEst is an offline-first Android app that teaches electrical engineerin

| Quest map | Lesson | Quiz feedback | Result | Review mode | Blueprint mode |
|:---:|:---:|:---:|:---:|:---:|:---:|
| <img src="docs/img/1-quest-map.webp" width="160" alt="Quest map with XP bar, search and the Circuit Fundamentals quests"> | <img src="docs/img/2-lesson.webp" width="160" alt="Lesson screen"> | <img src="docs/img/3-quiz-feedback.webp" width="160" alt="Quiz question answered correctly, with explanation"> | <img src="docs/img/4-result.webp" width="160" alt="Result screen: 85%, +160 XP, pass"> | <img src="docs/img/5-review.webp" width="160" alt="Review mode replaying a missed numeric question"> | <img src="docs/img/6-blueprint.webp" width="160" alt="Quest map in blueprint mode"> |
| <img src="docs/img/1-quest-map.webp" width="160" alt="Quest map with XP bar, study streak, search and the Circuit Fundamentals quests"> | <img src="docs/img/2-lesson.webp" width="160" alt="Lesson screen"> | <img src="docs/img/3-quiz-feedback.webp" width="160" alt="Quiz question answered correctly, with explanation"> | <img src="docs/img/4-result.webp" width="160" alt="Result screen: 85%, +160 XP, pass"> | <img src="docs/img/5-review.webp" width="160" alt="Review mode replaying a missed numeric question"> | <img src="docs/img/6-blueprint.webp" width="160" alt="Quest map in blueprint mode"> |
| Categories · search · XP bar | PCB hero · formulas | Instant explanation | Score ring · up-next | Replays missed questions | Alternate palette |

## Features
Expand All @@ -35,6 +35,7 @@ CircuitQuEEst is an offline-first Android app that teaches electrical engineerin
- **XP system** — +50 XP per lesson, 10 XP per point above your best quiz score, +100 bonus on your first pass
- **Dual question types** — multiple choice and numeric input with tolerance-aware grading
- **Review mode** — every missed question goes into a review queue (⟲ chip on the quest map, "Review mistakes" after a quiz); replay until you get them right
- **Study streaks** — days in a row with a lesson, quiz or review, plus your best run, shown under the XP bar (no XP bonus)
- **Blueprint mode** — toggleable alternate palette (deep navy + white-28% borders)
- **Shared-element transitions** — topic cards morph into the lesson hero
- **Offline-first** — all content and progress in a local Room database; no account needed
Expand Down
31 changes: 27 additions & 4 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,10 @@ The only class that writes progress. Hilt provides a singleton; inject it, don't
(tests construct it directly with DAOs from an in-memory database).

```kotlin
class ProgressRepository(progressDao: ProgressDao, quizResultDao: QuizResultDao, missedQuestionDao: MissedQuestionDao) {
class ProgressRepository(
progressDao: ProgressDao, quizResultDao: QuizResultDao, missedQuestionDao: MissedQuestionDao,
studyDayDao: StudyDayDao, today: () -> Long = Streaks::today
) {
fun getAllProgress(): Flow<List<TopicProgress>>
fun getProgress(topicId: String): Flow<TopicProgress?>
fun getTotalXp(): Flow<Int>
Expand All @@ -141,9 +144,15 @@ class ProgressRepository(progressDao: ProgressDao, quizResultDao: QuizResultDao,
suspend fun recordAnswer(topicId: String, questionId: String, correct: Boolean) // wrong → queue, right → clear
suspend fun getMissedQuestions(topicId: String? = null): List<MissedQuestion> // null = all topics, oldest first
fun getMissedCount(): Flow<Int>

// Streaks: lesson completion, every quiz result and every answer also record today's StudyDay
fun getStudyDays(): Flow<List<Long>> // epoch days, ascending
}
```

`Streaks.compute(days, today)` (`util/Streaks.kt`) turns those days into a `StreakState(current, best,
studiedToday)`. A streak stays alive through the day after the last study day. Streaks award no XP.

`recordQuizResult` inserts a `QuizResult`, then updates the topic's `TopicProgress`:
`bestScore = max(best, score)`; `quizCompleted` becomes `true` only on a passing attempt (and never
reverts); XP is added per `QuizScoring.calculateXp`. It **returns the XP awarded**, which the quiz
Expand All @@ -159,15 +168,19 @@ Writes are serialized with a `Mutex`, so concurrent updates to the same topic ca

```kotlin
@Database(
entities = [TopicProgress::class, QuizResult::class, MissedQuestion::class],
version = 2,
entities = [TopicProgress::class, QuizResult::class, MissedQuestion::class, StudyDay::class],
version = 3,
exportSchema = true,
autoMigrations = [AutoMigration(from = 1, to = 2)] // v2.5: adds missed_questions
autoMigrations = [
AutoMigration(from = 1, to = 2), // v2.5: missed_questions
AutoMigration(from = 2, to = 3, spec = AppDatabase.BackfillStudyDays::class) // v2.6: study_days
]
)
abstract class AppDatabase : RoomDatabase() {
abstract fun progressDao(): ProgressDao
abstract fun quizResultDao(): QuizResultDao
abstract fun missedQuestionDao(): MissedQuestionDao
abstract fun studyDayDao(): StudyDayDao
companion object { const val DATABASE_NAME = "circuitqueest_db" }
}
```
Expand Down Expand Up @@ -208,6 +221,11 @@ data class MissedQuestion(
val questionId: String,
val missedAt: Long = System.currentTimeMillis()
)

// A local day (LocalDate.toEpochDay) with a finished lesson, a quiz result or an answer. The 2→3
// migration backfills it from quiz_results and each topic's lastAccessedTimestamp.
@Entity(tableName = "study_days")
data class StudyDay(@PrimaryKey val epochDay: Long)
```

### DAOs (`data/db/dao/`)
Expand All @@ -234,6 +252,11 @@ data class MissedQuestion(
suspend fun getForTopic(topicId: String): List<MissedQuestion>
fun count(): Flow<Int>
}

@Dao interface StudyDayDao {
@Insert(onConflict = IGNORE) suspend fun insert(day: StudyDay)
fun getDays(): Flow<List<Long>> // ORDER BY epochDay
}
```

---
Expand Down
5 changes: 4 additions & 1 deletion docs/documentation.html
Original file line number Diff line number Diff line change
Expand Up @@ -210,16 +210,18 @@ <h2 id="architecture">Architecture</h2>
<h3 id="layers">Layer overview</h3>
<div class="arch-box"><span class="arch-layer">┌─ UI Layer (Jetpack Compose) ──────────────────────────────────┐</span>
│ HomeScreen · LessonScreen · QuizScreen · ResultScreen │
│ Components: TopicCard · CategoryHeader · XpBar │
│ Components: TopicCard · CategoryHeader · XpBar · StreakRow │
<span class="arch-layer">├─ ViewModel Layer (@HiltViewModel) ────────────────────────────┤</span>
│ HomeViewModel · LessonViewModel · QuizViewModel │
│ StateFlow → collectAsStateWithLifecycle() → recompose UI │
<span class="arch-layer">├─ Repository Layer ────────────────────────────────────────────┤</span>
│ ProgressRepository (single source of truth) │
│ DAOs: ProgressDao · QuizResultDao · MissedQuestionDao │
│ StudyDayDao │
<span class="arch-layer">├─ Data Layer ──────────────────────────────────────────────────┤</span>
│ Room DB (AppDatabase, provided by Hilt) │
│ Entities: TopicProgress · QuizResult · MissedQuestion │
│ StudyDay (streaks) │
<span class="arch-layer">├─ Content Layer (compile-time) ────────────────────────────────┤</span>
│ Kotlin singleton objects — one file per topic │
│ TopicCategories — maps topicId → category (UI-only) │
Expand Down Expand Up @@ -279,6 +281,7 @@ <h3 id="progress">Progress system</h3>
</ul>
<p>
Wrong answers are queued for <strong>review mode</strong> (no XP; a correct answer clears them).
Finishing a lesson, submitting a quiz or answering a question marks the day as studied; the quest map shows your <strong>study streak</strong> (days in a row) and your best (no XP; see <code>util/Streaks</code>).
A quiz passes at <strong>60%</strong>. Only a passing attempt sets <code>TopicProgress.quizCompleted = true</code>, which unlocks the next topic. Unlock order follows the categories on the quest map, top to bottom; each topic's <code>order</code> equals its position in that sequence. Any topic you've already started stays unlocked. The rules live in <code>QuizScoring</code>; the lock logic is in <code>HomeViewModel</code>.
</p>

Expand Down
Binary file modified docs/img/1-quest-map.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/6-blueprint.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
<div class="lp__hero-text">
<div class="lp__eyebrow">
<span class="lp__eyebrow-dot"></span>
v2.5.1 · Android 8.0+ · Free &amp; open source
v2.6 · Android 8.0+ · Free &amp; open source
</div>
<h1 class="lp__title">
Master <span class="lp__title-grad">electrical engineering</span> through quests.
Expand All @@ -100,7 +100,7 @@ <h1 class="lp__title">
learn the math that makes electrons behave — offline, on Android.
</p>
<div class="lp__cta-row">
<a class="lp__btn lp__btn--primary" href="https://github.com/HighviewOne/CircuitQueest/releases/download/v2.5.1/CircuitQueest-v2.5.1.apk">
<a class="lp__btn lp__btn--primary" href="https://github.com/HighviewOne/CircuitQueest/releases/download/v2.6/CircuitQueest-v2.6.apk">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="M10 2 V13 M5 8.5 L10 13.5 L15 8.5 M4 17 H16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
Expand All @@ -125,7 +125,7 @@ <h1 class="lp__title">
<div class="lp__phone-glow"></div>
<div class="lp__phone">
<img class="lp__phone-shot" src="img/1-quest-map.webp" width="540" height="897"
alt="The CircuitQuEEst quest map: XP bar at Level 2, search box, and the Circuit Fundamentals quests with scores like 7/7.">
alt="The CircuitQuEEst quest map: XP bar at Level 2, a 1-day study streak, search box, and the Circuit Fundamentals quests with scores like 7/7.">
</div>
<div class="lp__readout" aria-hidden="true">
<div class="lp__readout-row"><span>V</span><span>9.00 V</span></div>
Expand Down Expand Up @@ -161,8 +161,8 @@ <h3>Offline-first</h3>
</div>
<div class="lp__feature">
<div class="lp__feature-glyph lp__feature-glyph--gold">↑</div>
<h3>XP you actually earn</h3>
<p>+50 XP per lesson, +100 the first time you pass a quiz, and +10 for every point you beat your best score. Retaking an easy quiz earns nothing.</p>
<h3>XP and streaks</h3>
<p>+50 XP per lesson, +100 the first time you pass a quiz, and +10 for every point you beat your best score. Retaking an easy quiz earns nothing. A daily streak counts the days in a row you study.</p>
</div>
<div class="lp__feature">
<div class="lp__feature-glyph lp__feature-glyph--cyan">⊞</div>
Expand Down Expand Up @@ -264,7 +264,7 @@ <h2 class="lp__section-title">Finished the quests? Keep listening.</h2>
<h2>Download CircuitQu<i>EE</i>st for Android.</h2>
<p>Free. Open source (MIT). No ads, no accounts, no telemetry.</p>
<div class="lp__cta-row">
<a class="lp__btn lp__btn--primary" href="https://github.com/HighviewOne/CircuitQueest/releases/download/v2.5.1/CircuitQueest-v2.5.1.apk">Download APK <span class="lp__btn-meta">v2.5.1 · 2 MB</span></a>
<a class="lp__btn lp__btn--primary" href="https://github.com/HighviewOne/CircuitQueest/releases/download/v2.6/CircuitQueest-v2.6.apk">Download APK <span class="lp__btn-meta">v2.6 · 2 MB</span></a>
<a class="lp__btn lp__btn--ghost" href="https://github.com/HighviewOne/CircuitQueest/releases/latest">Release notes</a>
</div>
<ol class="lp__steps">
Expand Down
Loading