Skip to content

saf 2.0.0: single-class SAF API - #44

Merged
jvoltci merged 30 commits into
masterfrom
saf-v2
Jul 18, 2026
Merged

saf 2.0.0: single-class SAF API#44
jvoltci merged 30 commits into
masterfrom
saf-v2

Conversation

@jvoltci

@jvoltci jvoltci commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Implements the approved 2.0.0 spec.

One Saf class, 22 methods: pickers + persisted permissions, file management with recursive walk, byte/stream read-write with progress, local-file bridge, and hidden-folder copy. Sealed SafException hierarchy. Legacy API preserved as deprecated LegacySaf. Native package com.jvoltci.saf.

Verification: analyze clean, full unit suite, dart doc 0 warnings, pana 150/150, example APK builds, and a full on-device run (Motorola Edge 40, Android 15).

jvoltci added 30 commits July 18, 2026 09:25
Reconciles master with the full v1.0.5 doc changes that PR #42's squash missed
(README rebrand to jvoltci, #24 read-bytes usage) and adds:
- GitHub Actions: CI (format+analyze+test) and dartdoc -> GitHub Pages
- Homepage/documentation -> https://jvoltci.github.io/saf/ (replaces dead wiki links)
- Type-annotate releasePersistableUriPermission; clear all analyze + dartdoc lints
- example: print -> debugPrint; permission_handler ^12.0.3
- Result: pana 150/150, dart doc 0 warnings, flutter analyze clean, 12/12 tests
Reformat 11 v2 lib/test files with dart format so the whole-repo
dart format --set-exit-if-changed gate passes under Dart 3.11.5.
Formatting-only changes (line wrapping, trailing commas); no logic
or public API changes.
… event-channel handler leak

Found by pre-merge adversarial audit:
- copyOrMove: accumulate bytes across files so directory-copy progress is a
  running total instead of resetting to 0 per file
- pickFile/pickFiles: persist READ only (was READ|WRITE, which threw on
  read-only providers and silently persisted nothing)
- SessionManager: unregister the per-session EventChannel handler on cancel
saf-v2 already contains all of master's v1.0.6 content plus the 2.0.0 work;
recorded as an ours-merge to resolve the squash-history divergence so the PR
is conflict-free. No file content changes.
….saf

De-brands the abandoned ivehement domain. Internal Kotlin package + Gradle
namespace + AndroidManifest + pubspec plugin.package only; the Dart import
stays package:saf and the method-channel strings are unchanged, so runtime
behavior is identical (APK builds; full device test already passed).
…rite

Resolves the two deferred audit findings:
- readFileStream is now pull-based (openReadSession/readSessionChunk/
  closeReadSession) so native never reads ahead of the Dart consumer — no
  unbounded buffering / OOM on very large files.
- writeFileBytes/startWriteStream/pasteLocalFile best-effort truncate the
  output on overwrite so shorter content leaves no stale trailing bytes on
  providers that ignore the 'wt' flag.

Device-verified on Motorola Edge 40 (Android 15): pull read returns correct
chunks; overwrite produces correct file sizes.
…o the app dir

Restores the classic legacy cache() workflow (e.g. pulling WhatsApp .Statuses
out to save/share) on the v2 primitives (list + copyToLocalFile). Adds an
example 'Copy->local' button + docs. Device-verified on Motorola Edge 40:
copies land in the app external dir.
@jvoltci
jvoltci merged commit 0f43c1a into master Jul 18, 2026
1 check passed
@jvoltci
jvoltci deleted the saf-v2 branch July 18, 2026 11:47
@jvoltci jvoltci changed the title saf 2.0.0: single-class SAF API (replaces saf_stream + saf_util, and beyond) saf 2.0.0: single-class SAF API Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant