feat: note export/import, Android backup, Firebase distribution - #78
feat: note export/import, Android backup, Firebase distribution#78enlorik wants to merge 10 commits into
Conversation
…distribution Adds Settings → Export notes (share as JSON via Android share picker) and Import notes (merge by ID keeping the newer version, or Replace all with a double-confirmation). The export format is versioned so we can detect future schema changes at import time. Malformed files are rejected with a clear error. Adds Android Auto Backup rules (backup_rules.xml, data_extraction_rules.xml) so grepink.db survives device transfers; flutter_secure_storage entries are excluded from backup. Replaces the debug signingConfig in build.gradle with a persistent keystore loaded from android/key.properties locally or ANDROID_PREVIEW_* environment variables in CI. The build fails with a clear message rather than producing an APK silently signed with the wrong key. Adds a manually triggered firebase-distribute workflow that runs analyze + tests, builds a signed release APK (versionCode = run number), uploads to Firebase App Distribution, and also keeps the APK as a GitHub artifact. Adds docs/android_signing.md and docs/firebase_setup.md with the exact steps needed to complete setup (keystore generation, secrets, Firebase project registration). Includes 36 new tests covering round-trip, malformed-file, merge, replace, schema stability, and update-over-install scenarios.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ac9a1e691e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Decode the keystore to android/app/preview.jks so Gradle's file() resolves it correctly relative to the :app project directory. Force embeddingPending=true on every note written during import (both merge and replace-all paths) so restored notes are re-embedded by the next indexing pass and appear in semantic search.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9016f98f2a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The previous loop iterated over output.notes (all merged notes) and wrote every note with clearEmbedding:true, silently destroying embeddings on notes that were skipped (existing was same age or newer). Rewrite the merge path to iterate over incoming only and skip notes where the existing version is already newer or equal, leaving untouched notes intact.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d15f0eccfd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Pass FIREBASE_APP_ID and RELEASE_NOTES through environment variables rather than interpolating GitHub Actions expressions directly into the shell script. This closes a path where a crafted release-notes value could execute arbitrary commands inside the upload step.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f35893c54e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Replace ref.read(notesProvider).valueOrNull ?? [] with DatabaseService.instance.getAllNotes() in both _exportNotes and _importNotes. The provider state is null while loading; falling back to [] would export an empty file or treat every incoming note as new during a merge.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3d48a3a0b7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Wrap the replace-all clear+inserts in a database transaction via DatabaseService.replaceAll() so a mid-import storage error cannot leave the user with no notes and no backup (the old data is kept if the transaction rolls back). Add NotesNotifier.reindexPendingNotes() which embeds only notes that have embeddingPending=true, triggered at the end of both import paths so restored notes appear in semantic search automatically without needing a manual reindex.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b388e42232
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…lity Replace the temp-file-write export path with XFile.fromData so the export works without dart:io or path_provider. Simplify the import bytes path to require bytes (FilePicker always provides them when withData:true), removing the File(path).readAsString() fallback. Settings screen now compiles on web.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 086788c618
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| recommended immediate recovery method. | ||
| --> | ||
| <full-backup-content> | ||
| <include domain="database" path="grepink.db" /> |
There was a problem hiding this comment.
Include SQLite WAL files in backups
When Android backups run after recent note writes, this rule backs up only grepink.db; Android's Auto Backup rules state that adding an <include> limits backup to the listed paths, and Android 9+ can keep committed SQLite changes in WAL sidecars by default. Because the matching grepink.db-wal/grepink.db-shm files are not included here or in data_extraction_rules.xml, a cloud or device-transfer restore can come back stale or inconsistent for recently saved notes; include the whole database directory/sidecars or force checkpointing before relying on backup.
Useful? React with 👍 / 👎.
| buildTypes { | ||
| release { | ||
| signingConfig signingConfigs.debug | ||
| signingConfig signingConfigs.release |
There was a problem hiding this comment.
Preserve the update path for existing installs
For any tablet that already has an APK built before this commit, release builds were signed with Gradle's debug config, but this switches future releases to the new preview keystore. Android rejects over-installs signed by a different certificate, so those users cannot follow the documented data-preserving update path unless they first export/uninstall/import or the CI secret is seeded with the same prior signing key; call out this one-time migration or keep the signing key compatible for existing testers.
Useful? React with 👍 / 👎.
| firebase appdistribution:distribute \ | ||
| build/app/outputs/flutter-apk/app-release.apk \ | ||
| --app "$FIREBASE_APP_ID" \ | ||
| --release-notes "$RELEASE_NOTES" \ | ||
| --token "$FIREBASE_TOKEN" |
There was a problem hiding this comment.
Target a tester group during distribution
In the inspected Firebase App Distribution workflow, the upload command only passes the APK, app id, release notes, and token. Firebase's CLI docs describe --testers/--groups as the options used to invite testers or tester groups, so manual runs will create/upload a release but won't assign it to the tester group created in the setup checklist unless someone does that later in the console; add a group/testers secret or workflow input to the command.
Useful? React with 👍 / 👎.
…tester groups Include grepink.db-wal and grepink.db-shm in backup_rules.xml and data_extraction_rules.xml so Android Auto Backup captures SQLite WAL sidecars alongside the main database file. Add a 'First build from a previously debug-signed install' section to docs/android_signing.md describing the one-time export-uninstall-import migration for existing testers. Add optional FIREBASE_TESTER_GROUPS secret support to the distribution workflow so the uploaded release is assigned to a tester group automatically. Update docs/firebase_setup.md to list the new optional secret.
Enable WAL journal mode via onConfigure so Android Auto Backup always captures a consistent DB snapshot. Without it, DELETE-mode mid-write could produce a corrupt restore image despite the WAL sidecar rules. Add DatabaseService.mergeNotes() wrapping the merge loop in a single transaction, matching the atomicity guarantee already present in replaceAll(). Replace the non-atomic insertNote/updateNote loop in _importNotes() with a single mergeNotes() call. Fix NoteExportService.merge() to apply copyWith(embeddingPending:true) on added/updated notes so any caller gets notes that will be queued for re-embedding rather than silently remaining unembedded. Consolidate _importNotes() error handling: utf8.decode(), getAllNotes(), and preview() now share a single try/catch so non-UTF-8 files and DB errors show a SnackBar instead of silently propagating unhandled. Move both reindexPendingNotes() calls to before their !mounted guards so navigating away during a slow import does not leave all imported notes permanently stuck with embeddingPending=true.
…get safe Split the pre-dialog try/catch in _importNotes() so that file parsing errors show "Invalid backup file" and database read errors show "Could not read notes", rather than both showing the file message. Move loadNotes() outside the write try/catch so a transient read error after a successful replaceAll()/mergeNotes() does not incorrectly report the import as failed when the data was already written. Wrap the outer body of reindexPendingNotes() (settingsProvider read and getNotesWithPendingEmbeddings) in a top-level try/catch, matching the pattern of _triggerEmbedding, so callers can safely fire-and-forget without risking unhandled Future errors from infrastructure failures.
|
@codex review |
Export/import notes via share picker (versioned JSON), Android backup rules for grepink.db, persistent release signing via CI secrets, Firebase distribution workflow. 601 to 637 tests, flutter analyze clean.