fix/feat: misc app fixes, cancel flow, permissions, sync & firmware#6204
Conversation
…stall On Android, Firebase auth is cleared on uninstall so users sign in through OnboardingWrapper. The auth callback and initState both routed directly to HomePageWrapper, bypassing the _PermissionsGate. This adds a permissions check before routing so the interstitial page is shown when permissions haven't been granted.
The notificationsEnabled SharedPreferences flag was only set during onboarding. Users who granted notification permission via Settings > Permissions never had the flag set, so the notifyOnKill service was never registered and no notification was shown on app kill. Now checks Permission.notification.isGranted directly and syncs the pref flag, so the service is registered regardless of how the user granted permission.
Add swipe-to-delete on individual WAL items and a "Manage Storage" button with options to clear synced files, pending files, or all files, matching the functionality already present in the offline sync page.
_buildManualFirmwareFlash expects a DeviceProvider but was being passed the DeveloperModeProvider from the Consumer. Now reads DeviceProvider from context via watch<DeviceProvider>().
Adds CancelSubscriptionRequest with optional reason and reason_details. Stores feedback on the user document in Firestore before Stripe cancel. Fully backwards compatible.
Tracks flow started, reason selected, confirmed, kept plan, and abandoned with step number and reason for full funnel analysis.
Full-screen page flow replacing the old confirmation dialog: 1. Reason selection (7 options with icons) 2. Dynamic feedback (title/subtitle change based on selected reason) 3. Consequences + billing info + confirm Stores reason in Firestore and Mixpanel. Backend receives the reason key (e.g. too_expensive) while UI shows localized labels.
Adds reason labels, dynamic feedback titles/subtitles, consequence texts, and billing info strings. Regenerated localizations.
startLegacyDfu was hardcoding the firmware path to the app documents directory, ignoring the user-picked file. Since isLegacySecureDFU defaults to true and getLatestVersion is never called for manual flash, it always took the legacy path — causing "DFU firmware not found".
Manual flash always provides a modern firmware ZIP (manifest.json + image files). NordicDfu (legacy) cannot parse this format, causing "could not dfu zip file". Now calls startMCUDfu directly.
Greptile SummaryThis PR delivers six targeted bug fixes: Android permissions gate on sign-in, NotifyOnKill service registration, auto-sync WAL management UI, developer settings build error, subscription cancellation flow (replaced with a new 3-step full-screen flow with Mixpanel/Firestore tracking), and manual firmware flash ignoring user-picked ZIP files. Key changes:
Confidence Score: 4/5Mostly safe to merge; one P1 UX bug in the new cancellation flow lets the system back button skip to close instead of going back a step. A P1 logic bug in cancel_subscription_sheet.dart means PopScope(canPop: !_isCancelling) closes the entire cancellation flow when the user is on pages 1 or 2 and presses the system back button. The onPopInvokedWithResult callback is effectively dead code due to the invariant between canPop and !_isCancelling. All other fixes are correct and well-guarded. app/lib/pages/settings/widgets/cancel_subscription_sheet.dart (PopScope canPop logic) and app/lib/pages/onboarding/wrapper.dart (unawaited async call in onSignIn). Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User taps Cancel Subscription] --> B[CancelSubscriptionFlow.show]
B --> C[Step 1: Pick Reason]
C -->|Reason selected + Continue| D[MixpanelManager subscriptionCancelReasonSelected]
D --> E[Step 2: Feedback]
E -->|Continue or Skip| F[Step 3: Confirm]
F -->|Keep My Plan| G[MixpanelManager subscriptionCancelKeptPlan]
G --> H[pop false]
F -->|Confirm and Cancel| I[_confirmCancel]
I --> J[MixpanelManager subscriptionCancelConfirmed]
J --> K[UsageProvider cancelUserSubscription]
K --> L[DELETE /v1/payments/subscription with JSON body]
L --> M[backend: store cancellation_feedback in Firestore]
M --> N[fetchSubscription + loadAvailablePlans]
N --> O[pop true]
C -->|AppBar back on page 0| P[MixpanelManager subscriptionCancelAbandoned]
P --> Q[pop false]
C -->|System back P1 bug| R[Entire flow closes immediately - pages 1 and 2 also affected]
Reviews (1): Last reviewed commit: "feat: add translations for cancel flow k..." | Re-trigger Greptile |
Use datetime.now(timezone.utc) instead of time.time() so Firestore stores it as a proper Timestamp type, consistent with other timestamp fields like speaker_embedding_updated_at.
…tails - Fix canPop logic so system back navigates to previous cancel flow step instead of closing the entire flow - Await _routeWithPermissionsCheck in onSignIn callback - Add Optional type annotation to reason_details parameter - Store empty string instead of null when reason_details not provided
Shows battery optimization exemption row on Android, matching the onboarding and interstitial permission flows.
…asedHardware#6204) ## Summary - **Permissions interstitial on Android**: On fresh install, users signing in through OnboardingWrapper were routed directly to HomePageWrapper, bypassing the permissions gate. Now checks permissions before routing. - **NotifyOnKill service**: The `notificationsEnabled` SharedPreferences flag was only set during onboarding, so users who granted notification permission via Settings > Permissions never had the notifyOnKill service registered. Now checks actual system permission state. - **Auto sync page**: Added swipe-to-delete on individual WAL items and a "Manage Storage" bottom sheet (clear synced/pending/all) to match the offline sync page. - **Developer settings build fix**: `_buildManualFirmwareFlash` expected a `DeviceProvider` but was passed `DeveloperModeProvider`. Now reads `DeviceProvider` from context. - **Subscription cancel flow**: 3-step full-screen cancellation flow (reason → dynamic feedback → consequences) replacing the old confirmation dialog. Stores reason in Firestore and Mixpanel. Full funnel tracking (started/reason/confirmed/kept/abandoned). - **Manual firmware flash fix**: `startLegacyDfu` was ignoring the user-picked zip file path, always looking at the app documents directory. Now passes `zipFilePath` through to both legacy and MCU DFU paths. ## Test plan - [ ] Fresh install on Android → sign in → verify permissions interstitial shows - [x] Enable notifications via Settings > Permissions → kill app → verify "Omi disconnected" notification appears - [x] Auto sync page → verify swipe-to-delete and "Manage Storage" button work - [x] Developer settings with device connected → verify no build errors - [x] Cancel subscription → verify 3-step flow with dynamic feedback titles - [x] Cancel subscription → verify reason stored in Firestore and Mixpanel - [x] Manual firmware flash → pick ZIP → verify flash starts without "firmware not found" error 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Summary
notificationsEnabledSharedPreferences flag was only set during onboarding, so users who granted notification permission via Settings > Permissions never had the notifyOnKill service registered. Now checks actual system permission state._buildManualFirmwareFlashexpected aDeviceProviderbut was passedDeveloperModeProvider. Now readsDeviceProviderfrom context.startLegacyDfuwas ignoring the user-picked zip file path, always looking at the app documents directory. Now passeszipFilePaththrough to both legacy and MCU DFU paths.Test plan
🤖 Generated with Claude Code