shake_context 0.2.0
shake_context: ^0.2.0 copied to clipboard
Dual-mode, privacy-first, shake-triggered bug reporting and user feedback engine for Flutter.
Changelog #
0.2.0 — 2026-05-25 #
- Network capture for the developer overlay. New
ShakeDioInterceptor(package:shake_context/dio.dart) andShakeHttpClient(package:shake_context/http.dart) record every HTTP request/response/error as aNetworkLogthat surfaces in the developer overlay's Network panel (filterable by failed-only) and inReportMetadata.networkLogs. Both entry points are separate library imports sodio/httpare tree-shaken when unused. Hosts on other clients can push entries directly viaShakeContext.recordNetwork(NetworkLog(...)). RedactionConfig— privacy guardrails for captured network/log data. Headers and bodies are masked and truncated before being stored: conservative defaults redact common auth/session header keys (authorization,cookie,x-api-key, …) and body keys (password,token,secret, …), truncate bodies to 2 KB and log messages to 8 KB. Override per-interceptor via theredaction:parameter. See README → "Capturing network traffic".ShakeSensitivity— tunable shake trigger.ShakeContext(shakeSensitivity: …)acceptslow()/medium()(default) /high()presets or a fully customthreshold/minSpikes/window/cooldownprofile. Changeable at runtime.ReportTheme— per-surface color overrides. Pass toDeveloperConfig.theme/ProductionConfig.themeto recolor the report sheet without supplying a fullThemeData; every field is nullable and falls back to the inheritedcolorScheme.- macOS gallery picker now works in the example app. Adds
com.apple.security.files.user-selected.read-onlyto bothDebugProfile.entitlementsandRelease.entitlements. Without it the sandbox silently blocksimage_picker_macos'sNSOpenPanel— the "Add image" button in the production sheet appeared to do nothing. README's "Platform setup" section now documents the requirement for downstream apps. - Platform support audit + README rewrite. The "Platform support" table is now split into Tier 1 (first-party, verified) and Tier 2 (community plugins, untested) so users can tell what the maintainer has actually run end-to-end from what the dependencies merely advertise. Tier 1 (Android, iOS, macOS, Chrome, Safari) has been walked through the example app: shake /
triggerReport/ screenshot capture / device info /package_info_plusapp version / gallery picker / submit / retry-queue no-op on web. Web verification covers both the Blink (Chrome, Brave, Edge) and WebKit (Safari) engines viaflutter run -d web-server. Firefox (Gecko) hasn't been driven through the example — flagged in the "Web caveats" subsection. Windows and Linux are explicitly framed as "untested but expected to work." - Persistent retry queue for failed submissions. Opt in via
ShakeContext.guard(enableRetryQueue: true, retryQueueMaxAge: …, retryQueueMaxEntries: …). WhenonReportSubmittedthrows, the payload is serialised (including images) to<applicationSupportDirectory>/shake_context/queue/before the SnackBar fires, then replayed 5 s after the next app launch. Successful replays delete the file; failures stay queued for the launch after that, with FIFO eviction beyondmaxEntriesand age-based eviction beyondmaxAge(default 20 entries / 7 days). New static API:ShakeContext.queuedReportCount(),ShakeContext.replayQueuedReports(),ShakeContext.clearQueuedReports()for "Pending reports" badges, manual retry, and logout hooks. ConcurrentreplayQueuedReports()calls collapse onto the same in-flight future — no double-sends. ReportPayload.fromJson(+ReportMetadata.fromJson,LogEntry.fromJson,NetworkLog.fromJson). Round-trips cleanly withtoJson(includeImages: true). Defensive parsing — missing or wrong-typed fields fall back to safe defaults (unknownmode→production); individually broken nested entries are skipped rather than throwing the whole payload out.ProductionStrings— localizable copy for the production sheet. Every user-facing string inProductionView(header, hint, button labels, privacy line, tooltips, "Sent with your report" disclosure, submission-failure SnackBar — ~25 strings in total) is now overridable via a singleProductionStringsvalue object passed throughProductionConfig(strings: …). Wire yourAppLocalizations(or any localization layer) into it per locale. The legacy top-leveltitle/hintText/submitLabel/privacyNoteparameters onProductionConfigstill work for source compatibility but are soft-deprecated. See README → "Localization" for a worked example.InspectMode.resolve({String? flavor, Set<String> productionFlavors, bool? isReleaseBuild})— ergonomic helper for picking the right mode at startup. Works without flavors (falls back tokReleaseMode ? production : developer) and with flavors (returnsproductiononly when the flavor is inproductionFlavorsand the build is release-signed). Fixes the common TestFlight pitfall wherekReleaseModealone would hide the diagnostic overlay from QA on internal builds.DeveloperConfig.screenshotPixelRatioandProductionConfig.screenshotPixelRatio(both default3.0) — lets hosts dial down screenshot resolution when upload size matters. A 3.0x screenshot on a 1080p phone is 5–8 MB; 1.5–2.0 cuts that significantly.- App version auto-captured. Adds
package_info_plusdep. Every report'smetadata.deviceInfonow includesappName,appPackageName,appVersion, andappBuildNumber— the version is the field every bug tracker asks for first, and you no longer need to plumb it in by hand. ReportPayload.extras+ShakeContext(extras: …)— host-provided context attached to every payload. Use it forinstallationId,userId,releaseChannel, feature-flag state, etc. Included intoJson()only when non-empty, participates in equality.- Submission failures now surface to the user. When
onReportSubmittedthrows, the sheet stays open, the typed description and attachments are preserved, and a SnackBar tells the user the report couldn't be sent. Developer mode shows the raw error string for diagnosis; production mode shows a friendly generic message. Previously the spinner just cleared and the user got no signal. ShakeListenerspike buffer switched fromList<DateTime>toListQueue<DateTime>so the per-sample eviction inside_onSampleis O(1) instead of O(n). Imperceptible per sample, but the sensor runs at ~20 Hz × 3 axes — small savings add up.- README:
- "Picking the mode" + "Using with flavors" guide: full mode matrix,
main_dev.dart/main_prod.dartbootstrap pattern, build commands, gotchas (TestFlight, bundle-ID inference, symbol obfuscation). - "Host context" section showing the
extraspattern (installation ID, user ID, release channel). - "Sending the report" section: four concrete transport recipes (multipart, JSON webhook, email via share_plus, Sentry user feedback) + a documented retry-queue pattern for offline-friendly submission.
- "Screenshot size" section with the
screenshotPixelRatioknob and apackage:imagerecompression recipe. - "Crash recovery" section explaining the
persistLogs: trueflow. - "Platform setup" section calling out the iOS
NSPhotoLibraryUsageDescriptionrequirement whenallowGalleryUploadis on. - "Capturing network traffic" section documenting
ShakeDioInterceptor/ShakeHttpClient/ShakeContext.recordNetwork, plus theRedactionConfigdefaults and how to override them. - "Shake sensitivity" and "Theming the report sheet" sections for the new
ShakeSensitivityandReportThemeAPIs. - "Sending the report" expanded with a copy-paste dev-vs-production endpoint routing snippet (
--dart-definebase URL, dispatch onpayload.mode) and a backend contract describing both wire formats (multipart and JSON-with-inline-images), the JSON shape the server receives, and the minimum server requirements. - Primary
Usageexample now wrapsrunAppinShakeContext.guard(...)to match the 99% case (full log + uncaught-error capture).
- "Picking the mode" + "Using with flavors" guide: full mode matrix,
- Example app updated to use
InspectMode.resolve()instead of the manualkReleaseModeconditional, and now POSTs each report to a bundled local receiver (test_backend/, a zero-dependency Dart server with a live web dashboard) so the full capture → serialize → transport → render path can be verified end-to-end. The example's macOS network-client entitlement and iOS local-network / cleartext-HTTP allowances were added for that upload path.
0.1.0 — First public release #
- Telemetry & media pipeline (
ContextCapturer):- Real screenshot capture via a
RepaintBoundarywrapped around the app tree. device_info_plus-backed device snapshot, curated per platform (Android / iOS / macOS / Windows / Linux / Web).- Best-effort current-route detection (non-destructive
popUntilinspection). - Rolling
debugPrintlog buffer with eviction (DeveloperConfig.logBufferSize), installed inShakeContext.initStateand restored ondispose. image_picker-backed gallery picker for the production sheet's+button.
- Real screenshot capture via a
ShakeContextnow wrapschildin aRepaintBoundaryand feeds captured data into the overlay views asynchronously — the modal opens instantly and rebuilds as each capture resolves.DeveloperViewandProductionViewaccept optionalscreenshotFuture/deviceInfoFuture/initialScreenshotFutureparameters and reconcile bytes viasetState.- Multi-page example app (home / settings / nested
/checkoutroute) demonstrates the master toggle and route capture. - MIT license, pub.flutter-io.cn-ready
README, dartdoc on every public symbol.
0.0.4 — Dual-mode presentation factory #
ProductionView— Material bottom-sheet form with description field, removable image row, optional gallery picker callback, privacy reassurance line, and submit/loading states.DeveloperView— diagnostic dashboard with route, device info, log tail, screenshot preview, optional note, cancel/send actions. Honors allDeveloperConfigcapture toggles.UnifiedOverlay.show()— picks the correct view based onInspectMode, mounts it viashowModalBottomSheet, and pops itself after successful submission.ShakeContextnow launches the overlay on shake. AddednavigatorKeyparameter so the widget can sit aboveMaterialAppand still resolve aNavigator.- Tests cover each view in isolation, the overlay router, and a full shake → overlay-opens integration path for both modes plus the navigatorKey-above-MaterialApp placement.
0.0.3 — Shake detection engine #
- Added
sensors_plus: ^6.0.0dependency. ShakeListener(lib/src/core/shake_listener.dart) — g-force spike detector with rolling time window, debounce cool-down, and injectable clock + stream for tests.ShakeContextnow starts/stops aShakeListenerbased onisShakeEnabled, reacts to runtime flips indidUpdateWidget, and tears down cleanly indispose.- New optional
onShakeDetectedhook exposes the raw trigger before the UI lands in Plan 04. - Test coverage extended to spike-count, sub-threshold rejection, window aging, cool-down, restart, and idempotent
start().
0.0.2 — Core models & public API surface #
InspectModeenum (developer,production).ProductionConfigandDeveloperConfigvalue classes withcopyWithand value equality.ReportPayload+ReportMetadataunified output models with immutable collections.ShakeContextwidget shell — constructor takesmode,isShakeEnabled,productionConfig,developerConfig,onReportSubmitted,child. Renders the child; sensor and presentation layers land in upcoming plans.- Public barrel now exports the full Plan 02 surface.
0.0.1 — Scaffolding #
- Initial project skeleton.
lib/src/folder layout established formodels/,core/, andpresentation/.- Public barrel
lib/shake_context.dartready for milestone exports. - Execution plan broken down under
plans/.