Skip to content

feat(import): Dedicated offline Import Notes screen with multi-app format auto-detection #62

Closed

Description

@pasichDev

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.java already parses Google Keep Takeout ZIPs. It can serve as reference for format detection and extraction. Its current GoogleKeepNote.java handles 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.xml does not request INTERNET; .github/workflows/android.yml already fails CI if the merged fdroid release manifest contains the permission. Preserve this gate.
  • Existing BackupImporter uses a whole validated BackupPayload and 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 Result OpenDocument (multi-select as appropriate); Choose folder uses ACTION_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:

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.openInputStream and 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 reliable size.
  • 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 BackupPayload or List<ImportedNote> in RAM. Reuse BackupImporter for 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 String and 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

  1. 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.
  2. Build ImporterRegistry, safe SAF reader, bounded JSON/ZIP/TAR/MD/HTML parsers, safe auto-detection and error contracts.
  3. Implement all listed format adapters with mapping tests, including checklist/tags/date conversion and missing-field edge cases.
  4. Implement dedicated Import Notes Compose page, supported-app cards, export instructions, file/folder picker, preview, dedupe choices, confirmation, progress and results.
  5. 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.
  6. 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 INTERNET permission 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

Activity

  1. pasichDev commented on Oct 14, 2026

    @pasichDev
    OwnerAuthor

    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.

  2. pasichDev commented on Oct 14, 2026

    @pasichDev
    OwnerAuthor

    Checklist updated against c5819be. Still open:

    • Reading is bounded but not streaming: a selection is read into memory up to 64 MiB (JSON exports up to 16 MiB) and parsed as a whole, and the payload is built in memory before the single transaction.
    • Not verified on a device: real content:// providers, read-only and tree URIs from different file managers.
    • Not automated: process death, low memory and low disk space during an import.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions