Repository navigation
feat(import): Dedicated offline Import Notes screen with multi-app format auto-detection #62
Description
Goal
Add a dedicated Import Notes page inside Encly, where users see which other apps are supported and can import their exported notes entirely offline, with automatic file/format detection. This is a feature implementation ticket, not only an investigation: research each exporter, implement its adapter, wire up the UI and verify against real export fixtures.
Only user-provided local files/folders via Android Storage Access Framework (SAF). No remote APIs, OAuth, internet access, or background uploads. Keep Encly usable without android.permission.INTERNET.
Existing code / integration points (checked on main)
pasichDev/MyNotes/app/src/main/java/com/pasich/mynotes/utils/backup/otherApp/GoogleKeepImportService.javaalready parses Google Keep Takeout ZIPs. It can serve as reference for format detection and extraction. Its currentGoogleKeepNote.javahandles basic title/text/first label/timestamp/trash; do not blindly copy it: it does not map checklists, multiple labels, pinned/archived/color metadata, and accumulates strings in memory. Port/refactor the useful parsing logic into bounded Kotlin code with richer coverage.- Encly already supports encrypted local backups through
data/backup/BackupManager.kt,BackupImporter.kt,BackupPayload,VaultDataStore, and an independent My Notes hand-off (Import notes from My Notes (one-way hand-off) #46). Do not overwrite or regress either. Reuse note/block mapping and existing transactional storage contracts where safe. - Encly's
AndroidManifest.xmldoes not request INTERNET;.github/workflows/android.ymlalready fails CI if the merged fdroid release manifest contains the permission. Preserve this gate. - Existing
BackupImporteruses a whole validatedBackupPayloadand UID-based merge. Arbitrarily large migrations need a separate bounded incremental import/staging design, not an unsafe assumption that it already streams.
Dedicated Import Notes page (required UX)
Entry point: Settings → Data / Import & Export → Import Notes, separate from Restore Encrypted Encly Backup and separate from My Notes app-to-app hand-off.
- Show an attractive, easy-to-scan Supported apps list with icon/name, supported export formats, brief "How to export" instructions and any known losses/limitations (attachments, colors, pinning, etc.). Only call an app Supported after the parser and tests work.
- Initial entries: Google Keep, Standard Notes, Joplin, Notesnook, and Markdown / TXT (Obsidian, Markor, other apps). Where appropriate, label the latter as generic file import rather than pretending to support every proprietary Obsidian plugin.
- Primary Choose file(s) action opens SAF
ACTION_OPEN_DOCUMENT/ Activity ResultOpenDocument(multi-select as appropriate); Choose folder usesACTION_OPEN_DOCUMENT_TREE/OpenDocumentTree. Optionally provide app-specific cards that launch the same picker filtered to useful MIME types. - After selection automatically detect content/format, not only extension or picker MIME. Display e.g. "Detected: Google Keep · 237 notes" or a clear ambiguity/error message with manual choice as fallback. Never silently reinterpret an unknown JSON/ZIP as an unrelated format.
- Display a read-only preview before any write: source, note/tag/checklist counts, sample titles when authorized, duplicates, unsupported records/attachments/metadata and the chosen destination (currently active vault).
- Offer Import / Cancel. Default mode Merge, not Replace; show progress, cancelability and a final summary of imported/skipped/failed counts. Keep the original source untouched.
- Preserve existing vault and notes on failed parsing/validation; no partial broken notes or hidden overwrite. Make atomicity explicit (bounded transaction batches vs all-or-nothing) before implementation, and communicate cancel semantics. Prefer a verified, rollback-capable import plan for small imports; for large imports use documented resumable or batch-atomic behavior with idempotency and clear user feedback.
- Localize all UI, exporter instructions and validation failures into all existing Encly locales.
Sources / supported export input matrix
| Source | Initial input to support | Data mapping / restrictions |
|---|---|---|
| Google Keep | Google Takeout ZIP (Takeout/Keep/*.json), JSON note files, selected Takeout/Keep folder |
title, textContent, listContent with isChecked, labels, timestamps in microseconds (convert safely), trash/archive; show lost pin/color if Encly has no equivalent. Support notes with no textContent, including checklist-only. Map multiple labels within actual Encly model's limitations; explain losses. |
| Standard Notes | Decrypted backup/export JSON, possibly a ZIP containing it: inspect current real examples/schema | notes from items/content_type=Note, title/text/content, tag references where present; explicitly reject unsupported encrypted backups with instructions. Format/version variations must be verified with real fixtures. |
| Joplin | JEX (TAR container), or RAW directory; optionally Markdown exports | note .md files, metadata, notebook/category/tag references, timestamps; distinguish JEX/RAW internal representations from generic Markdown. Handle JEX safely without extracting an untrusted TAR to disk. |
| Notesnook | ordinary export ZIP containing Markdown, Markdown + Frontmatter, HTML, or TXT; direct export files as appropriate | parse text, headings/lists/checklists and frontmatter where supported. Do not claim .nnbackup encrypted backups are importable; display clear guidance to export into a portable format instead. Inspect current Notesnook export structure and create fixtures. |
| Obsidian / Markor / other | selected folder or .md / .txt files (optionally ZIP collections) |
Markdown headings/lists/task checkboxes, YAML frontmatter (safe subset), filename as title when no metadata, timestamps if unambiguous, directory structure optionally → tags/notebooks depending on available data model. Never execute plugins, scripts or active content. |
Research work to perform before writing each parser: consult the latest vendor export docs and sanitized real exports; record format versions, container structure, escaping, UTF-8/HTML behavior, cross-file references, ordering, limitations and fixture provenance. Do not guess an unsupported format from its extension.
Official starting references:
- Joplin: https://joplinapp.org/help/apps/import_export/
- Notesnook: https://notesnook.com/help/export-notes-from-notesnook
- Standard Notes: https://standardnotes.com/help/14/how-do-i-create-and-import-backups-of-my-standard-notes-data
- Google Keep implementation: https://github.com/pasichDev/MyNotes/blob/main/app/src/main/java/com/pasich/mynotes/utils/backup/otherApp/GoogleKeepImportService.java
Suggested implementation structure (adapt to the actual codebase)
interface NoteImporter {
val source: ImportSource
// Examine a bounded, replayable prefix/signature plus optionally archive metadata.
fun detect(probe: ImportProbe): Detection
// Incrementally emit validated records; caller owns cancellation and stream lifetime.
fun parse(input: ImportInput): Flow<ImportedRecord>
}
// ImportSource: id, display name, formats, user-facing export instructions.
// ImportInput: ContentResolver-backed URI(s) / document tree; bounded reading.
// ImportedRecord: note, tags, task/checklist items, metadata, provenance and warnings.- FormatDetector/ImporterRegistry with independent
GoogleKeepImporter,StandardNotesImporter,JoplinImporter,NotesnookImporter,MarkdownTextImporter. Explicit source confidence; transparent fallback on ambiguity. - Use
ContentResolver.openInputStreamand SAF document URIs. Non-seekable inputs require bounded prefix buffering and a safe replay/reopen approach; do not assume every provider supports filesystem paths, seeking or reliablesize. - Parse JSON incrementally, and ZIP/TAR entries sequentially with per-entry and aggregate limits; do not load the whole archive or all notes into RAM. Reject zip bombs, path traversal, links, unsafe archive entries, pathological nesting, oversized notes/record counts, bad UTF-8 and decompression ratios.
- Conversion pipeline: SAF source → content-based detection → bounded parser → normalized
ImportedRecord→ preview and dedupe → encrypted vault persistence. - Normalize title/body/block structure, checklists, relevant dates, tags and trash status only when Encly supports them. Provide a per-source loss report; do not silently drop content or inject raw untrusted HTML into the editor.
- Deduplicate against the active encrypted vault using deterministic content fingerprints (e.g. normalized title + textual blocks/checklist state) plus a stable source/record identity when available. Do not use text hash alone: identical bodies with different titles can be valid distinct notes. Avoid cross-vault dedupe leaks; keep indexes/hashes inside the vault, not plaintext SharedPreferences.
- Respect Encly's current DB/backup model: avoid building a huge
BackupPayloadorList<ImportedNote>in RAM. ReuseBackupImporterfor small bounded data or introduce a separate transaction-safe incremental merge layer for large imports; do not change encrypted-backup semantics to accommodate third-party exports. - Never stage plaintext notes in
cacheDir, external storage, temp SQLite DB, logs, crash reports, telemetry, clipboard, notifications or app backups. If preview requires a second pass, reopen user-supplied SAF URIs / re-parse or use a bounded in-memory preview; never serialize the data plaintext. Encrypt promptly on commit. - Be honest in the threat model: Kotlin/Java immutable
Stringand GC mean guaranteed RAM zeroization is impossible. Minimize plaintext residency and zeroize only mutable buffers on a best-effort basis. No internet permission/dependency added. - Imports are allowed only after successful unlock of the destination vault; no source file content is exposed to the lock screen, deep links or other vault.
Implementation plan
- Audit existing Encly import/export paths and My Notes Google Keep code; collect public/synthetic export fixtures for all specified apps and document schemas/limitations.
- Build ImporterRegistry, safe SAF reader, bounded JSON/ZIP/TAR/MD/HTML parsers, safe auto-detection and error contracts.
- Implement all listed format adapters with mapping tests, including checklist/tags/date conversion and missing-field edge cases.
- Implement dedicated Import Notes Compose page, supported-app cards, export instructions, file/folder picker, preview, dedupe choices, confirmation, progress and results.
- Integrate with encrypted active-vault storage and explicit transaction/cancellation strategy. Regression-test Import notes from My Notes (one-way hand-off) #46 and native encrypted Encly backup/restore.
- Update
SECURITY.md,docs/architecture.md,PRIVACY.md,CONTRIBUTING.md, CHANGELOG, translation resources and user help.
Definition of done / tests
- User can discover supported apps on a separate page and import matching local export files without knowing the format.
- All 5 source families in the matrix above work against verified and malformed test fixtures; individual support badges reflect actual implementation.
- No
INTERNETpermission in the merged release manifest; existing workflow gate continues to pass. - Imported notes remain encrypted at rest; no plaintext temporary disk artifacts, even on cancellation/parse failure.
- Re-import is idempotent when appropriate; intentionally distinct notes are not incorrectly merged; original existing notes never silently overwritten.
- SAF reads work with
content://providers, multi-selects, read-only URIs, tree URIs and non-seekable streams. - Tests: missing/unknown formats, false positive detection, encrypted exports, oversized JSON, zip/tar bombs, traversal, nested archives, malformed HTML/YAML, invalid UTF-8, high record counts, metadata bounds, timestamps, cancelled import, process death, low memory, low disk space, duplicate content/identity.
- All errors and unsupported metadata are reported clearly, with source files untouched; UI remains responsive.
- Documentation and all supported translations updated; existing My Notes handoff (Import notes from My Notes (one-way hand-off) #46) and encrypted backup unchanged.
Related
- Import notes from My Notes (one-way hand-off) #46 My Notes one-way handoff; do not reimplement app-to-app migration here.
docs/architecture.mddocuments offline encrypted backups and existing import constraints.
Activity
feat(import): add an offline Import notes screen for other apps
pasichDev commentedon Oct 14, 2026
Implemented in #65 (c5819be): Settings → Import notes reads Google Keep Takeout, decrypted Standard Notes backups, Joplin JEX/RAW, Notesnook Markdown/TXT exports and Markdown/TXT folders (Obsidian, Markor) offline, with content-based detection, bounded in-memory ZIP/TAR reading, a review screen to pick notes, and one-transaction merge into the open vault. Parsers are tested against real exports of each app. Encrypted Standard Notes and Notesnook backups are refused with guidance. Ships with the next release.
pasichDev commentedon Oct 14, 2026
Checklist updated against c5819be. Still open:
fix(import): keep media out of the limits and fix review findings