feat: user authentication, reading plans, settings & streak sync (#6)#10
Merged
Conversation
…e; restore MainActivity
…deps Adds http, flutter_secure_storage, and google_sign_in packages. Creates core/api/api_client.dart (typed HTTP wrapper with Bearer token support), core/auth/auth_storage.dart (secure JWT persistence), and core/auth/user_profile.dart (UserProfile model). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AuthRepository wraps all /auth/* endpoints including Google Sign-In (idToken flow). AuthNotifier auto-inits on first read: reads stored token, validates via GET /profile, and resolves to authenticated or unauthenticated. Logout is best-effort on the server and always clears the token locally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Amharic-first UI matching app design language. Includes email/password form with validation, show/hide password toggle, locked-account error handling, Google sign-in button, forgot password and register links. Register and ForgotPassword screens are stubs pending Steps 4 and 5. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rse quote Adds app logo header with language switcher, remember-me checkbox, Google + Apple side-by-side social buttons, and bible verse footer quote. Apple sign-in shows coming-soon snackbar pending backend support. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Register: name/email/phone/password form with 2-step progress bar, live password strength indicator (4 bars), and terms checkbox. OTP: 6 individual digit boxes with auto-advance/back focus, 60s countdown timer, resend, and masked contact display. Both match app design language — parchment bg, burgundy primary, Geez labels. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Forgot password: email/phone tab switcher, key icon header, sends POST /forgot-password, navigates to reset screen. Phone tab shows coming-soon snackbar. Reset password: token field (from email), new/confirm password fields, live requirements checklist with animated check circles (length/uppercase/number/special char), green success snackbar on completion, redirects to login. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ProfileScreen with avatar, stats (streak/bookmarks/progress), achievements section, and logout/delete-account menu - Update MeScreen to ConsumerStatefulWidget; show real name+email when authenticated and navigate to ProfileScreen on tap - Show guest card with login prompt when unauthenticated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SyncRepository: HTTP calls to POST/PUT/DELETE /bookmarks, /notes, /highlights, and POST /progress/log-reading - SyncService: processes pending (pendingCreate/Update/Delete) items against the API, updates local sync_status and remote_id on success; errors are silently skipped and retried on next sync - AppDatabase: add soft-delete variants (mark pendingDelete when remote_id exists) and getPending* / updateAnnotationSync helpers - Auth: trigger syncAll() fire-and-forget after every login path (login, verifyOtp, signInWithGoogle, token restore on cold start) - Annotations: use soft-delete for removes; trigger sync after every bookmark/note/highlight mutation - Reader: fire-and-forget logReadingProgress after qualifying dwell Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Google login: show profile picture (NetworkImage) with initials fallback on error - Email/password login: show first letter of user name in primary- color circle - Guest: keep existing placeholder 'ን' circle - Greeting updates to 'ሰላም, [firstName]' / 'Hello, [firstName]' when authenticated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows 'Facebook Sign In — በቅርብ ይመጣል' snackbar on tap. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t sync Highlights were silently rejected by the backend because 'teal' and 'red' are not valid color names. Backend accepts: yellow, green, pink, blue, red, purple. - 0xFF80CBC4 (teal) → 'green' - 0xFFEF9A9A (pink-red) → 'pink' Add 'book' field alongside 'bookId' on both bookmark and highlight create/update payloads, matching the web FE format. The backend uses 'book' for verse reference lookup; without it bookmarks showed 'verse not found' on the web. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix wrong webClientId in AuthRepository (was pointing to a different Google Cloud project entirely, preventing idToken from being issued) - Add pullAll() to SyncService and wire it before syncAll() on auth - Add upsertServer* methods to AppDatabase for conflict-safe pulls - Add fetch methods to SyncRepository to pull bookmarks/highlights/notes - Trigger pullAll in SavedScreen on open when authenticated - Align highlight palette colors with backend accepted color names Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 60 new keys across four new sections in app_strings.dart, am_strings.dart, and en_strings.dart: profile screen, saved/collection screen, forgot password, and reset password. Update all nine affected screens to use L10n.of(context) instead of inline Amharic/English literals. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When no user is signed in the header now shows a pill-shaped "Sign In" button that navigates to the login screen, removing the stray 'ን' initial. _Avatar is now non-nullable so it only renders for authenticated users. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tons - Add flutter_svg dependency and declare assets/signin/ in pubspec - Replace hand-drawn Google logo painter with the official web_light_sq_na / web_dark_sq_na SVG assets, switching on theme brightness - Set Facebook button background to the official Facebook blue (#1877F2) instead of the generic dark color Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign out of any cached Google session before calling signIn() so the account chooser sheet is always presented instead of silently reusing the last account. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ge, logout
Data layer:
- UserProfile gains `provider` field ('email'|'google') + isGoogleUser helper
- ApiClient gains multipart uploadFile() method
- AuthRepository gains updateProfile(), changePassword(), uploadAvatar()
- AuthNotifier exposes the three new actions and dart:io import
UI:
- ProfileScreen rewritten as ConsumerStatefulWidget with inline editing:
- Avatar section shows camera badge and 'Change Photo' link for email users;
Google users see avatar read-only
- Account Info section with First Name, Last Name, Email fields;
email field is locked with 🔒 badge for Google accounts + info notice
- Security section (email users only) — Change Password row opens a
bottom sheet with current/new/confirm password fields
- Preferences section with language chips and night-mode toggle
- Save Changes button appears only when there are unsaved edits
- Logout button rendered as a subtle pill at the bottom of the screen;
the ⋯ menu now only contains Delete Account
- MeScreen (Settings tab) gains a Logout button below Reminders,
visible only when the user is authenticated
- 18 new l10n keys in app_strings / am_strings / en_strings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Accept main's saved screen files (PR #8 reviewed version with ids, delete support, and i18n) - Keep auth branch's sync service call in SavedScreen._doLoad() - Remove auth branch's duplicate saved-screen string keys from app_strings / am_strings (superseded by PR #8 keys) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DB v4 migration: mark synced items with title-case bookId as
pendingUpdate so they are re-pushed in the correct kebab-case format
- Rewrite upsertServer* to do three-tier upsert: match by remoteId,
merge by verse ref, or insert as new — prevents silent drops when a
local pendingCreate item occupies the same verse as a server item
- Add _toApiBookId/_normalizeBookId helpers so push uses kebab-case
("genesis") and pull normalises server keys back to local bookNameEn
- Fix createNote/updateNote to send flat fields (bookId, chapter, ...)
that the server actually requires — nested verseRef format caused 400
- Add 404 fallback in syncBookmarks/Notes/Highlights: stale remoteId
on PUT triggers a fresh POST instead of silently failing
- Handle null remoteId on pendingUpdate by falling back to create
- Surface HTTP status codes and exception types in sync error logs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ReadingPlan/DailyReading models and ReadingPlanRepository (GET /reading-plans, PATCH day complete) - Add readingPlansProvider (AsyncNotifierProvider) that fetches when authenticated, returns [] otherwise - Add patch() method to ApiClient - Replace hardcoded plan stubs with live data from backend; show auth prompt with login/dismiss when unauthenticated - Add readingPlansSyncPrompt and continueWithoutAccount l10n strings (AM + EN) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract BookCover to lib/core/widgets/book_cover.dart with coverColor/size params; remove duplicate from continue_reading_section - Add testamentColor() helper — OT burgundy, NT navy, deuterocanonical olive - Add startBook to ReadingPlan model for testament detection - Redesign carousel cards: upright book cover + name + progress, colour from testament - Add ReadingPlansScreen (full list, same book cover + horizontal card layout) - Wire View All button to ReadingPlansScreen Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Parse DailyReadingItem (book, startChapter, endChapter) from each day's readings - Add plan_position table to AppDatabase (v5 migration) with get/save methods - openReadingPlan() resolves saved position → first incomplete day → first day, maps kebab book name to BookIndexEntry, saves position, navigates to ReaderScreen - Wire tap on carousel card and View All list card to openReadingPlan() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Settings sync: - Lift ValueNotifier<AppSettings> out of Settings widget into a Riverpod settingsNotifierProvider so AuthNotifier can read/write it without BuildContext - Parse RemoteSettings (theme, fontSize) from UserProfile.fromJson - On login, apply remote values only for fields still at their default (local wins) - Start a ValueNotifier listener after login to push settings to PUT /auth/profile on every change; stop listener on logout/delete Streak sync: - Parse RemoteStreak (current, longest) from UserProfile.fromJson - After login, merge with local streak — keep whichever value is higher per field - Invalidate readingStreakStateProvider so the UI reflects the merged count immediately Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This was referenced May 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
GET /reading-planswith book-style cards coloured by testament (OT burgundy, NT navy, deuterocanonical olive)theme/fontSizeon login (local wins on conflict), push on every change viaPUT /auth/profilemax(local, remote)for both current and longest streak after loginWhat is deferred
Test plan
Changes
See
PR_CHANGES.mdin the repo root for a full breakdown.🤖 Generated with Claude Code