{"id":35209994,"url":"https://github.com/foxbiz/better-keep","last_synced_at":"2026-04-13T01:45:39.027Z","repository":{"id":329942385,"uuid":"1121047464","full_name":"foxbiz/better-keep","owner":"foxbiz","description":"Notes app with rich text, PIN lock, E2E encryption, sync \u0026 offline support.","archived":false,"fork":false,"pushed_at":"2026-04-07T17:52:26.000Z","size":4910,"stargazers_count":21,"open_issues_count":3,"forks_count":5,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-07T18:22:07.366Z","etag":null,"topics":["e2ee","flutter","notes","notes-app","notes-tool","private","to-do-app"],"latest_commit_sha":null,"homepage":"https://betterkeep.app/welcome","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/foxbiz.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-22T10:58:43.000Z","updated_at":"2026-04-07T17:52:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/foxbiz/better-keep","commit_stats":null,"previous_names":["foxbiz/better-keep"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/foxbiz/better-keep","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxbiz%2Fbetter-keep","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxbiz%2Fbetter-keep/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxbiz%2Fbetter-keep/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxbiz%2Fbetter-keep/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/foxbiz","download_url":"https://codeload.github.com/foxbiz/better-keep/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxbiz%2Fbetter-keep/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31736723,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-12T22:19:12.206Z","status":"ssl_error","status_checked_at":"2026-04-12T22:18:33.088Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["e2ee","flutter","notes","notes-app","notes-tool","private","to-do-app"],"created_at":"2025-12-29T17:30:40.748Z","updated_at":"2026-04-13T01:45:39.006Z","avatar_url":"https://github.com/foxbiz.png","language":"Dart","readme":"# Better Keep ![Users](https://img.shields.io/endpoint?url=https://us-central1-better-keep-notes.cloudfunctions.net/getPublicStats\u0026style=flat\u0026logo=firebase\u0026logoColor=white)\n\n[![GitHub](https://img.shields.io/badge/GitHub-181717?style=flat\u0026logo=github\u0026logoColor=white)](https://github.com/foxbiz/better-keep) ![Android](https://img.shields.io/badge/Android-3DDC84?style=flat\u0026logo=android\u0026logoColor=white) ![iOS](https://img.shields.io/badge/iOS-000000?style=flat\u0026logo=apple\u0026logoColor=white) ![macOS](https://img.shields.io/badge/macOS-000000?style=flat\u0026logo=apple\u0026logoColor=white) ![Windows](https://img.shields.io/badge/Windows-0078D6?style=flat\u0026logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMyAyMyI+PHBhdGggZmlsbD0iI2YxZjFmMSIgZD0iTTAgMGgxMXYxMUgweiIvPjxwYXRoIGZpbGw9IiNmMWYxZjEiIGQ9Ik0xMiAwaDExdjExSDEyeiIvPjxwYXRoIGZpbGw9IiNmMWYxZjEiIGQ9Ik0wIDEyaDExdjExSDB6Ii8+PHBhdGggZmlsbD0iI2YxZjFmMSIgZD0iTTEyIDEyaDExdjExSDEyeiIvPjwvc3ZnPg==\u0026logoColor=white) ![Web](https://img.shields.io/badge/Web-4285F4?style=flat\u0026logo=googlechrome\u0026logoColor=white)\n\n[![Google Play](https://img.shields.io/badge/Google_Play-414141?style=for-the-badge\u0026logo=google-play\u0026logoColor=white)](https://play.google.com/store/apps/details?id=io.foxbiz.better_keep) [![Web App](https://img.shields.io/badge/Web_App-4285F4?style=for-the-badge\u0026logo=googlechrome\u0026logoColor=white)](https://betterkeep.app)\n\n \u003c!-- [![App Store](https://img.shields.io/badge/App_Store-0D96F6?style=for-the-badge\u0026logo=app-store\u0026logoColor=white)](https://apps.apple.com/app/id\u003cYOUR_APP_ID\u003e) --\u003e\n\n[![Microsoft Store](https://img.shields.io/badge/Microsoft_Store-0078D4?style=for-the-badge\u0026logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMyAyMyI+PHBhdGggZmlsbD0iI2YxZjFmMSIgZD0iTTAgMGgxMXYxMUgweiIvPjxwYXRoIGZpbGw9IiNmMWYxZjEiIGQ9Ik0xMiAwaDExdjExSDEyeiIvPjxwYXRoIGZpbGw9IiNmMWYxZjEiIGQ9Ik0wIDEyaDExdjExSDB6Ii8+PHBhdGggZmlsbD0iI2YxZjFmMSIgZD0iTTEyIDEyaDExdjExSDEyeiIvPjwvc3ZnPg==\u0026logoColor=white)](https://apps.microsoft.com/detail/9PHT5C6WK6Q1)\n\nBetter Keep is my take on the notes app I always wanted Google Keep to be. It keeps the familiar card-based experience, then layers on richer writing, better organization, and privacy controls while staying lightning fast and offline friendly.\n\n## Why build it?\n\n- Google Keep is great but misses power features I rely on for project planning and journaling.\n- I wanted rich-text notes, better bulk actions, and real locking with encryption without leaving the Keep workflow.\n- Flutter lets me reach mobile, desktop, and web with one codebase, so the app can live everywhere I take notes.\n\n## Highlights for everyone\n\n- **Rich-text editor** powered by `flutter_quill` with headings, lists, formatting, and color-coded backgrounds.\n- **True offline mode** backed by SQLite across desktop, mobile, and web (where supported).\n- **Secure notes**: lock individual notes with a PIN; content is encrypted before hitting disk.\n- **End-to-end encryption**: notes and attachments are encrypted on your device before syncing. The server never sees your plaintext data.\n- **Organize faster**: labels, quick filtering, instant search, and a masonry layout that keeps pinned notes up front.\n- **Smart views \u0026 folders**: switch between grid, list, and folder views. Organize notes hierarchically by labels or colors with breadcrumb navigation.\n- **AI audio transcription**: record voice memos and get automatic transcription using on-device Whisper AI. Your audio stays private while being converted to searchable text.\n- **Share to app**: receive shared text and files from other apps to quickly capture content.\n- **Stay tidy**: archive or trash in bulk, restore when needed, or delete forever with one tap.\n- **Full sync**: notes, attachments, and labels sync across devices with live updates.\n- **Sketch with images**: the sketch page now supports adding images for annotation.\n- **Audio transcription**: record audio notes and get automatic transcription.\n\n## Platforms\n\nAvailable on **Android**, **iOS**, **macOS**, **Windows**, and **Web**.\n\n## Under the hood (developer notes)\n\n- Flutter 3.10+ with a lightweight global `AppState` pub/sub instead of heavy state frameworks.\n- Persistent storage via `sqflite` (and `sqflite_common_ffi` for desktop) with thin model layers in `lib/models`.\n- Rich editor and previews courtesy of `flutter_quill`; read-only rendering reuses the same deltas.\n- Simple XOR + SHA-256 based encryption (`lib/utils/encryption.dart`) for locked notes, keeping secrets out of the database.\n- Responsive masonry grid (`lib/pages/home/notes.dart`) that adapts to any screen width and remembers scroll position.\n\n## Screenshots\n\n| Login                                 | Home                                  | Editor                                |\n| ------------------------------------- | ------------------------------------- | ------------------------------------- |\n| ![Home screen](web/screenshots/1.jpg) | ![Rich editor](web/screenshots/2.jpg) | ![Unlock note](web/screenshots/3.jpg) |\n\nWatch a quick walkthrough of creating and completing a reminder:\n\n- [Youtube Short](screenshots/recording.mp4)\n\n## Try it quickly\n\n```bash\ngit clone https://github.com/foxbiz/better-keep.git\ncd better-keep\nflutter pub get\n```\n\n### Running the app\n\nThe app requires environment variables defined in a `.env` file. Create one at the project root with your configuration:\n\n```bash\n# .env example\n# Add your Firebase and app configuration here\n# FIREBASE_API_KEY=your_api_key\n# FIREBASE_PROJECT_ID=your_project_id\n#\n# Web storage encryption key (64-char hex string, 256 bits)\n# Generate with: openssl rand -hex 32\n# WEB_STORAGE_KEY=\u003cyour-64-char-hex-key\u003e\n```\n\n**Using VS Code:**\n\nOpen the project in VS Code and use the pre-configured launch configurations in `.vscode/launch.json`:\n\n- `better_keep` – Debug mode\n- `better_keep (Profile)` – Profile mode for performance analysis\n- `better_keep (Release)` – Release mode\n- `better_keep (Web Server)` – Run as web server on port 63630\n\nAll configurations automatically load environment variables from `.env` via `--dart-define-from-file`.\n\n**Using the terminal:**\n\n```bash\nflutter run --dart-define-from-file=.env\n```\n\n- Use `flutter run -d windows`, `flutter run -d macos`, `flutter run -d ios`, etc. to target a specific platform.\n- Desktop builds require `sqflite_common_ffi`; the app auto-initializes it on Windows/macOS.\n\n### Building the app\n\nBuild release versions for distribution:\n\n**Android:**\n\n```bash\n# APK (universal)\nflutter build apk --dart-define-from-file=.env\n\n# App Bundle (recommended for Play Store)\nflutter build appbundle --dart-define-from-file=.env\n```\n\n**iOS:**\n\n```bash\nflutter build ios --dart-define-from-file=.env\n```\n\nThen open `ios/Runner.xcworkspace` in Xcode to archive and distribute.\n\n**macOS:**\n\n```bash\nflutter build macos --dart-define-from-file=.env\n```\n\nThe app will be at `build/macos/Build/Products/Release/better_keep.app`.\n\n**Windows:**\n\n```bash\nflutter build windows --dart-define-from-file=.env\n```\n\nThe app will be at `build/windows/x64/runner/Release/`.\n\n\u003c!-- Linux build disabled - Firebase doesn't support Linux yet\n**Linux:**\n\n```bash\nflutter build linux --dart-define-from-file=.env\n```\n\nThe app will be at `build/linux/x64/release/bundle/`.\n--\u003e\n\n**Web:**\n\n```bash\nflutter build web --dart-define-from-file=.env\n```\n\nThe output will be in `build/web/`. Deploy to any static hosting service.\n\n## Project layout\n\n```text\nlib/\n  app.dart               # MaterialApp, localization, theming\n  config.dart            # App configuration and constants\n  main.dart              # DB bootstrapping and platform init\n  state.dart             # Global event-driven state store\n  models/\n    base_model.dart      # Base class for all models\n    note.dart            # Note schema with sync support\n    label.dart           # Label schema\n    note_attachment.dart # Base attachment model\n    note_image.dart      # Image attachment model\n    note_recording.dart  # Audio recording with transcription\n    sketch.dart          # Sketch/drawing data\n    reminder.dart        # Reminder/alarm model\n    *_sync_track.dart    # Sync tracking for notes, labels, files\n  pages/\n    home/                # Masonry feed, sidebar, labels, search\n    note_editor/         # Rich-text editor, toolbar, actions\n    sketch_page.dart     # Sketch editor with image support\n    image_viewer.dart    # Full-screen image viewer\n    login_page.dart      # Authentication UI\n    user_page.dart       # User profile and settings\n    settings.dart        # App settings\n    nerd_stats_page.dart # Usage statistics\n  services/\n    database.dart        # SQLite database management\n    auth_service.dart    # Firebase authentication\n    note_sync_service.dart   # Note sync with Firestore\n    label_sync_service.dart  # Label sync with Firestore\n    file_system.dart     # Cross-platform file handling\n    alarm_id_service.dart    # Reminder/alarm management\n  components/            # Reusable UI (note card, animated icons, etc.)\n  dialogs/               # Prompt, confirm, color picker, label manager\n  themes/                # Dark theme configuration\n  ui/                    # UI utilities and widgets\n  utils/                 # Encryption, helpers, utilities\nassets/\n  sounds/                # Audio files for alarms/notifications\n  ...                    # Fonts, images, lottie, etc. (see `pubspec.yaml`)\n```\n\n## Local data model\n\n- **`note`** table: title, rich-text JSON content, labels (comma separated), color, archival flags, timestamps, lock metadata, sync status.\n- **`label`** table: user-managed labels with conflict-safe upserts and sync tracking.\n- **`note_image`** table: image attachments linked to notes with local/remote paths.\n- **`note_recording`** table: audio recordings with transcription text and duration.\n- **`sketch`** table: drawing data with stroke information and background images.\n- **`reminder`** table: scheduled reminders with alarm support.\n- **`*_sync_track`** tables: track sync state for notes, labels, and files.\n- Locked notes store encrypted content; unlocking decrypts in-memory only.\n\n## Sync \u0026 Conflict Resolution\n\nBetter Keep implements a robust **Local-First** sync strategy using Firebase Firestore and Storage with **live syncing** capabilities.\n\n### What Syncs\n\n- **Notes**: Full note content, metadata, and settings.\n- **Labels**: User-created labels sync across all devices.\n- **Attachments**: Images, audio recordings, and sketches.\n\n### Live Sync\n\nThe app listens to Firestore in real-time. Changes made on one device appear on other devices within seconds, without requiring manual refresh.\n\n### Sync Policy: \"Newest Wins\"\n\nTo ensure data integrity across devices, the app uses a timestamp-based conflict resolution strategy:\n\n1.  **Pushing Changes**:\n    - Before pushing a local update to the cloud, the app fetches the current remote version.\n    - **Comparison**: It compares the `updatedAt` timestamp of the local item against the remote.\n    - **Resolution**:\n      - If **Remote is Newer**: The push is aborted. The local item is immediately updated with the newer remote data (effectively a \"pull\" operation).\n      - If **Local is Newer**: The local changes are pushed to Firestore, overwriting the older remote version.\n\n### Duplicate Prevention\n\n- **Local IDs**: Each note and label maintains a `local_id` which is synced to Firestore.\n- **Re-installation**: If the app is re-installed or the local database is cleared, the sync process uses the `local_id` from Firestore to map remote items back to the correct local records, preventing duplicate entries.\n\n### Storage \u0026 Attachments\n\n- **Full Attachment Sync**: Images, audio recordings (with transcriptions), and sketches are synced to Firebase Storage.\n- **Recursive Deletion**: When a note is deleted, a recursive cleanup process ensures all associated files are permanently removed from Firebase Storage.\n- **Offline Support**: Attachments are downloaded locally. The app prefers local files when available and syncs new attachments in the background.\n- **File Sync Tracking**: A dedicated tracking system ensures attachments are properly synced and handles retries for failed uploads.\n\n## Development workflows\n\n### Firebase Setup (Required for Sync)\n\nThis project uses Firebase for sync and authentication. Since `firebase_options.dart` is git-ignored for security, you must configure your own Firebase project:\n\n1.  Install the Firebase CLI: `npm install -g firebase-tools`\n2.  Log in: `firebase login`\n3.  Activate FlutterFire CLI: `dart pub global activate flutterfire_cli`\n4.  Configure the app:\n    ```bash\n    flutterfire configure\n    ```\n\n    - Select your Firebase project (or create a new one).\n    - Select the platforms you want to support (Android, iOS, Web, macOS, Windows).\n    - This will generate `lib/firebase_options.dart`.\n5.  Enable **Authentication** (Google Sign-In) and **Firestore Database** in your Firebase Console.\n\n- `flutter pub get` – install dependencies.\n- `flutter analyze` – static analysis.\n- `dart format lib test` – keep style consistent.\n- `flutter test` – run widget and unit tests (extend coverage as features grow).\n\n## Roadmap\n\n- [x] End-to-end encryption (E2EE) for notes and attachments. See [E2EE Documentation](docs/E2EE.md).\n- [x] Light and dark theme support.\n- [x] Fix alarm notifications on iOS.\n- [x] Optimize sketch saving (reduce file size by lowering precision).\n- [x] Revenue model implementation.\n- [x] Smart views \u0026 folders (grid, list, folder views with hierarchical organization).\n- [x] AI audio transcription using on-device Whisper.\n- [x] Share to app (receive shared content from other apps).\n- [x] Adaptive toolbar with responsive icon sizing.\n- [ ] Calendar-grade reminders and recurring nudges.\n- [ ] Widgets and quick actions on mobile/desktop.\n- [ ] Theme editor (custom colors).\n\n## Contributing \u0026 feedback\n\n- Issues and feature requests welcome via GitHub.\n- Fork, branch (`git checkout -b feature/\u003cname\u003e`), add tests, and open a PR once `flutter analyze` and tests pass.\n- Reach out in Discussions if you want to coordinate on a larger feature.\n\n## License\n\nThis project is licensed under the **Creative Commons Attribution-NonCommercial 4.0 International Public License (CC BY-NC 4.0)**.\n\nYou are free to:\n\n- **Share** — copy and redistribute the material in any medium or format.\n- **Adapt** — remix, transform, and build upon the material.\n\nUnder the following terms:\n\n- **Attribution** — You must give appropriate credit, provide a link to the license, and indicate if changes were made.\n- **NonCommercial** — You may not use the material for commercial purposes.\n\nSee the [LICENSE](LICENSE) file for details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoxbiz%2Fbetter-keep","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffoxbiz%2Fbetter-keep","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoxbiz%2Fbetter-keep/lists"}