A privacy-respecting bookmarking app for students — built with Flutter for Android and iOS. Save links and text snippets, organise them into collections, set reminders, and lock the app behind biometrics. Everything works offline; signing in only adds cross-device sync.
SBA is a Flutter port of an earlier React Native app. Most Dart files keep a
// Ported from src/...header pointing at their RN origin.
- Package / bundle id:
com.kevin.sba - Flutter: 3.44.2 stable · Dart SDK:
^3.12.2 - State:
provider(ChangeNotifier) · Local:shared_preferences· Cloud: Supabase (Postgres + Auth)
Every feature works without an account. Bookmarks live in shared_preferences and are fully
usable offline. Signing into Supabase only adds cross-device sync — it is never a gate. Any
Supabase write is guarded by if (_session != null) in lib/providers/bookmark_provider.dart.
- Save URLs (auto-fetches page title, favicon, and description) and plain-text snippets
- Save straight from any app via the share sheet (
receive_sharing_intent) - Collections — group bookmarks; empty collections persist until deleted
- Reminders — schedule a local notification per bookmark
- Filter: All · Text · Websites · Scheduled · full-text search
- Dark / Light / System theme, persisted
- Biometric app lock (Face ID / Touch ID / fingerprint), re-locks on background
- Cloud sync via Supabase (sign up / in / out) with offline-first merge
flutter pub get # first run, or after pulling new deps
flutter run # pick a device interactivelySupabase credentials are baked in as safe defaults (the publishable/anon key — RLS protects rows), so the app runs out of the box with sync enabled. To point at your own project, override at build time:
flutter run \
--dart-define=SUPABASE_URL=https://<project-id>.supabase.co \
--dart-define=SUPABASE_ANON_KEY=<publishable-or-anon-key>flutter devices # list what's connected
flutter emulators # list Android emulators / iOS simulators
flutter emulators --launch Pixel_10_Pro_XL # boot one
flutter run -d Pixel_10_Pro_XL # run on itWhile running: r hot reload · R hot restart · q quit.
See the run-app skill for device gotchas and
release-apk for building a shippable build.
lib/
├── main.dart # Entry: providers, splash → lock → home, share + deep-link wiring
├── models/bookmark.dart # Bookmark + sealed BookmarkCategory (text | url | webPage)
├── providers/ # ChangeNotifier state
│ ├── bookmark_provider.dart # CRUD, collections, reminders, Supabase sync, auth
│ ├── theme_provider.dart # dark / light / system
│ └── lock_provider.dart # biometric app lock
├── storage/bookmark_storage.dart # shared_preferences CRUD + Supabase row mapping
├── services/ # supabase, notifications, sharing
├── screens/ # home, add-bookmark sheet, auth, lock, settings, splash
├── widgets/ # bookmark_card, search_bar, empty_state, skeleton
├── utils/ # web-metadata fetch, date/reminder/collection helpers
└── design/tokens.dart # Spacing, Radii, AppType, AppColors, kPrimary (#0891B2)
Deeper detail: .claude/docs/ARCHITECTURE.md.
Build/test/ship workflow: .claude/docs/WORKFLOW.md.
dart format <changed files>
flutter analyze # must be clean — no new issues
flutter test # smoke tests in test/widget_test.dart must passIf you touched UI, run it on a real device — don't claim it works untested.
- Design tokens only. Pull spacing, radii, colors, and text styles from
lib/design/tokens.dart. No magic numbers or hardcoded hex in widgets. Brand primary is cyan#0891B2(kPrimary). - Theme-aware. Read colors via
context.watch<ThemeProvider>().colorsso light/dark both work. - Models are immutable.
BookmarkandBookmarkCategoryusecopyWith; never mutate in place. - Don't rename storage keys. They use a legacy
@lynk/prefix (@lynk/bookmarks,@lynk/collections). Renaming orphans existing users' local data.
Kalisa Ngabo Kevin — designed & built SBA.
- Email: kalisangabokevin@gmail.com
- LinkedIn: linkedin.com/in/kalisangabokevin
Private project — publish_to: 'none'.