mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 22:47:01 +01:00
Some checks failed
📎 now picks any file (Android: Camera / Gallery / File; desktop: an
unfiltered dialog) and the desktop page accepts any dropped file. Photo and
screenshot formats up to 64 MB are still re-encoded and shown inline;
everything else is stored byte for byte on dufs beside the images and linked
as [name](url), shown as a row with its size that opens in the platform's
app (Android) or a new browser tab (desktop). BACKLOG.md rewrites file links
to ~/data/cloud/todo-images/ like image links.
There is no size cap, so no layer holds a file in memory: uploads and
downloads stream through DufsImageClient, writes land in a .tmp renamed when
whole (an interrupted download no longer leaves a truncated file that looks
done), the desktop page POSTs the browser's own Blob with fetch, and Android's
"File" uses file_picker, because file_selector's Android side reads the whole
file into memory on the UI thread. Verified with a 1 GB file end to end on
desktop (peak RSS +36 MB) and on an API 36 emulator.
The wrapper serves attachments from the origin that holds the GitHub token
and the notes, so only script-free types open inline (nosniff), the rest
download; writes carrying another site's Origin are refused (this also
closes cross-site overwrites of the backup files); and attachment routes run
concurrently so a long upload or a playing video cannot stall the page.
Also: cupertino_icons 2.0.0 (unused, but the freshness gate blocks the
pubspec otherwise), open_file 4.0.0 and file_picker 13.1.0 pinned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
2.8 KiB
Dart
69 lines
2.8 KiB
Dart
/// The [CaptureScreen] widget's public surface: its parameters and their
|
|
/// contracts. The state that drives them lives in `capture_screen.dart`.
|
|
///
|
|
/// A `part` rather than a separate library because `createState` returns a
|
|
/// private class, so the widget and its State cannot be split across
|
|
/// libraries.
|
|
part of 'capture_screen.dart';
|
|
|
|
/// The landing screen: an always-focused text box for jotting an idea.
|
|
///
|
|
/// Per the product goal "no interruptions, immediate", text is persisted
|
|
/// to local storage on *every* keystroke. A note row is created lazily on
|
|
/// the first character typed, then updated in place. The "new note" action
|
|
/// finalises the current idea and clears the field for the next one.
|
|
class CaptureScreen extends StatefulWidget {
|
|
/// Creates a [CaptureScreen] backed by [repository].
|
|
const CaptureScreen({
|
|
required this.repository,
|
|
required this.appSettings,
|
|
this.analytics,
|
|
this.httpClient,
|
|
this.localBackup,
|
|
this.firebaseFactory,
|
|
this.stateStore,
|
|
this.attachmentPicking,
|
|
super.key,
|
|
});
|
|
|
|
/// The store the captured note is persisted to on every keystroke.
|
|
final NoteRepository repository;
|
|
|
|
/// App-wide preferences, currently just `advancedMode`: whether the
|
|
/// priority/status row and the routine save/sync status line are shown.
|
|
/// A sync failure is always surfaced regardless of this flag.
|
|
final ValueNotifier<AppSettings> appSettings;
|
|
|
|
/// Interaction-only usage analytics (taps, navigation, sync outcomes —
|
|
/// never note text). Null in tests that don't exercise it, so the prefs
|
|
/// round-trip `logEvent` does on every call never has to be awaited by a
|
|
/// test that didn't ask for it.
|
|
final AnalyticsService? analytics;
|
|
|
|
/// Builds the Firebase backend. Injected so tests can supply a fake, or
|
|
/// null to assert the pre-migration GitHub-only path still works.
|
|
final Future<FirebaseRestClient?> Function()? firebaseFactory;
|
|
|
|
/// Revision cache. Injected so tests need no application-support directory.
|
|
final SyncStateStore? stateStore;
|
|
|
|
/// Injectable HTTP client for the sync path. Production leaves this null
|
|
/// (the GitHubClient creates its own); tests pass a mock so the configured
|
|
/// sync flow can be exercised without real network access.
|
|
final http.Client? httpClient;
|
|
|
|
/// Injectable local-disk backup. Production leaves this null (a platform
|
|
/// file-backed instance is created); tests pass a fake with in-memory IO.
|
|
final LocalBackup? localBackup;
|
|
|
|
/// The 📎 picker; null uses the platform one. Injected by tests.
|
|
final AttachmentPicking? attachmentPicking;
|
|
|
|
/// How long focus must stay lost before a background sync fires. Public so
|
|
/// tests can pump exactly past it.
|
|
static const autoSyncDebounce = Duration(seconds: 5);
|
|
|
|
@override
|
|
State<CaptureScreen> createState() => _CaptureScreenState();
|
|
}
|