Skip to content

Rewrite to Kotlin + MVVM + Room with Material 3 redesign - #1

Merged
SultanZhalifa merged 4 commits into
masterfrom
claude/strange-payne-04496b
May 9, 2026
Merged

SultanZhalifa merged 4 commits into
masterfrom
claude/strange-payne-04496b

Conversation

@SultanZhalifa

Copy link
Copy Markdown
Owner

Full architectural overhaul of the app. Replaces the Java/SQLite prototype with a clean MVVM stack and a Material 3 user interface, plus adds onboarding, dashboard, PDF export, JSON backup/restore, theme toggle, and Google Books ISBN auto-fill.

Architecture

  • Single-Activity host (MainActivity) + Navigation Component graph for Dashboard, Library, Detail, Add/Edit, Settings. Auth + Onboarding live in their own activities.
  • Repository pattern between ViewModels and Room. Reactive reads via Flow/StateFlow.
  • Hand-rolled ServiceLocator for DI; ViewModelFactory wires constructors.

Data layer

  • Room database with UserEntity + BookEntity (cover URI, status, rating, progress, notes, dateAdded, lastModified). DAOs use suspend + Flow.
  • PreferencesManager: typed wrapper around SharedPreferences for session, theme, last-used sort/filter.
  • Salted SHA-256 password hashing replaces plaintext storage.

UI

  • Material 3 theme tokens (light + dark) with brand palette and typed text styles.
  • Onboarding (3-slide ViewPager2 with dot indicators), Login/Register with inline validation, Dashboard with status breakdown + recent rail, Library with search / sort / filter (persisted) and swipe-to-delete + Snackbar UNDO, Detail with rating
    • reading progress + Share, Add/Edit form with cover picker and ISBN auto-fill, Settings with theme toggle + PDF export + JSON backup/restore + sign-out.
  • 25+ Material vector icons and gradient drawables.

Features

  • Cover image picker via the modern Photo Picker (no permission on Android 13+); bytes copied to internal storage so they survive app restarts.
  • Reading status (Want / Reading / Finished) with progress tracking (current / total pages) for "Currently Reading" books.
  • 5-star rating with sortable ranking on the dashboard average.
  • Sort by title / date / rating; filter by reading status; preferences persisted.
  • PDF export via platform PdfDocument API (no third-party PDF lib).
  • JSON backup/restore via org.json, written through MediaStore on Q+, FileProvider on older devices.
  • Google Books ISBN auto-fill: pre-populates title, author, year, category, and page count from a 10/13-digit ISBN. Network done with HttpURLConnection only — no new dependencies. User-typed input is preserved (never overwritten).
  • Theme toggle: System / Light / Dark. AppCompatDelegate-driven.
  • Splash screen with brand gradient (no white flash on cold launch).

Tooling

  • Kotlin 2.0.21, AGP 8.11, Gradle 8.13, JDK 17. compileSdk=35, minSdk=21, targetSdk=34.
  • ViewBinding, KSP, Navigation Safe Args, Coil 2.7.
  • Coroutines + Flow. lifecycle-runtime-ktx for repeatOnLifecycle.

Testing

  • 8 unit-test classes covering PasswordHasher, enum storage values, both repos, all ViewModels (auth/list/edit), and the Google Books parser. JUnit + MockK + Turbine
    • kotlinx-coroutines-test.

Project hygiene

  • LICENSE (MIT) added.
  • GitHub Actions CI: build + unit tests + debug APK artifact on every push/PR.
  • screenshots/ directory with capture instructions for the README placeholders.
  • .gitignore extended to exclude Claude session metadata and keystore files.
  • README rewritten with architecture diagram, full feature list, tech stack table, module map, test inventory, screenshots placeholders, and roadmap.

SultanZhalifa and others added 4 commits May 9, 2026 14:45
Full architectural overhaul of the app. Replaces the Java/SQLite prototype with a
clean MVVM stack and a Material 3 user interface, plus adds onboarding, dashboard,
PDF export, JSON backup/restore, theme toggle, and Google Books ISBN auto-fill.

Architecture
- Single-Activity host (MainActivity) + Navigation Component graph for Dashboard,
  Library, Detail, Add/Edit, Settings. Auth + Onboarding live in their own activities.
- Repository pattern between ViewModels and Room. Reactive reads via Flow/StateFlow.
- Hand-rolled ServiceLocator for DI; ViewModelFactory wires constructors.

Data layer
- Room database with UserEntity + BookEntity (cover URI, status, rating, progress,
  notes, dateAdded, lastModified). DAOs use suspend + Flow.
- PreferencesManager: typed wrapper around SharedPreferences for session, theme,
  last-used sort/filter.
- Salted SHA-256 password hashing replaces plaintext storage.

UI
- Material 3 theme tokens (light + dark) with brand palette and typed text styles.
- Onboarding (3-slide ViewPager2 with dot indicators), Login/Register with inline
  validation, Dashboard with status breakdown + recent rail, Library with search /
  sort / filter (persisted) and swipe-to-delete + Snackbar UNDO, Detail with rating
  + reading progress + Share, Add/Edit form with cover picker and ISBN auto-fill,
  Settings with theme toggle + PDF export + JSON backup/restore + sign-out.
- 25+ Material vector icons and gradient drawables.

Features
- Cover image picker via the modern Photo Picker (no permission on Android 13+);
  bytes copied to internal storage so they survive app restarts.
- Reading status (Want / Reading / Finished) with progress tracking (current / total
  pages) for "Currently Reading" books.
- 5-star rating with sortable ranking on the dashboard average.
- Sort by title / date / rating; filter by reading status; preferences persisted.
- PDF export via platform PdfDocument API (no third-party PDF lib).
- JSON backup/restore via org.json, written through MediaStore on Q+, FileProvider
  on older devices.
- Google Books ISBN auto-fill: pre-populates title, author, year, category, and
  page count from a 10/13-digit ISBN. Network done with HttpURLConnection only —
  no new dependencies. User-typed input is preserved (never overwritten).
- Theme toggle: System / Light / Dark. AppCompatDelegate-driven.
- Splash screen with brand gradient (no white flash on cold launch).

Tooling
- Kotlin 2.0.21, AGP 8.11, Gradle 8.13, JDK 17. compileSdk=35, minSdk=21, targetSdk=34.
- ViewBinding, KSP, Navigation Safe Args, Coil 2.7.
- Coroutines + Flow. lifecycle-runtime-ktx for repeatOnLifecycle.

Testing
- 8 unit-test classes covering PasswordHasher, enum storage values, both repos, all
  ViewModels (auth/list/edit), and the Google Books parser. JUnit + MockK + Turbine
  + kotlinx-coroutines-test.

Project hygiene
- LICENSE (MIT) added.
- GitHub Actions CI: build + unit tests + debug APK artifact on every push/PR.
- screenshots/ directory with capture instructions for the README placeholders.
- .gitignore extended to exclude Claude session metadata and keystore files.
- README rewritten with architecture diagram, full feature list, tech stack table,
  module map, test inventory, screenshots placeholders, and roadmap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`android.util.Patterns.EMAIL_ADDRESS` is a static field that's null in JVM unit
tests — the Android stub jar doesn't initialize it, and AGP's
`isReturnDefaultValues = true` only handles method calls, not field access.
That made every UserRepository test that exercised the register() path NPE
before even reaching the assertion.

Switching to a hand-rolled Kotlin Regex makes the validation work identically
on the device and in JVM tests, with no extra dependency. Pattern is
practical RFC 5322-ish — sufficient to bounce obvious typos at the form layer
and matches the spirit of what `Patterns.EMAIL_ADDRESS` does anyway.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Safe Args' XML parser requires the L suffix for long defaultValues:
  error: Failed to parse defaultValue '0' as long

The earlier audit "fix" that stripped the suffix was wrong. The original
defaultValue="0L" was correct. Reverting to that form.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…, silence Room warning

Three fixes surfaced by running ./gradlew testDebugUnitTest assembleDebug locally
against AGP 8.11.0 / Gradle 8.13 / JDK 21 (Android Studio jbr):

1. mipmap-anydpi/ic_launcher{,_round}.xml moved to mipmap-anydpi-v26/
   AAPT rejects <adaptive-icon> on minSdk 21 unless the resource is gated by the
   v26 qualifier — adaptive icons are an API 26+ feature. The v26 qualifier tells
   the resource system to only use these XMLs on Android 8+, while older devices
   keep falling back to the raster mipmap-*dpi/*.webp variants.

2. testImplementation org.json:json:20240303
   GoogleBooksParser uses org.json.JSONObject. On-device that's the platform impl,
   but the JVM unit-test android.jar stub leaves it unimplemented, so calls
   silently returned defaults and the parser always emitted null. Adding the
   reference impl as a test dep makes the parser execute the same code path in
   tests as on a real device.

3. exportSchema = false on the Room database
   The "Schema export directory was not provided" warning is noisy and we don't
   ship a v2 schema yet. Comment in the source explains how to flip it back on.

Verification: 51/51 unit tests pass; assembleDebug produces a valid app-debug.apk.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@SultanZhalifa
SultanZhalifa merged commit e24f8e2 into master May 9, 2026
1 check passed
@SultanZhalifa
SultanZhalifa deleted the claude/strange-payne-04496b branch May 9, 2026 18:40
SultanZhalifa added a commit that referenced this pull request Oct 12, 2026
Rewrite to Kotlin + MVVM + Room with Material 3 redesign
SultanZhalifa added a commit that referenced this pull request Oct 12, 2026
Rewrite to Kotlin + MVVM + Room with Material 3 redesign
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant