feat(app): permissions settings page and fresh install interstitial#6159
Conversation
Permissions page accessible from Settings showing status of Notifications, Location, Bluetooth, and Microphone with enable/disable actions and system settings deep-linking. Closes #6131
Shows a permissions screen with Omi logo when onboarding was completed on backend but permissions not granted on this device. Includes Skip for now option and requests both notification and location permissions on Continue.
Check permissions after onboarding restore from backend. If already granted, auto-mark as done. Otherwise show the permissions interstitial before home.
Add Permissions item below Phone Calls in the settings drawer with shield icon and Mixpanel tracking.
Separate flag from onboardingCompleted to track whether permissions have been granted on this device install.
Mark permissions as completed when user goes through the full onboarding flow (which includes the permissions step).
Track permissions settings opened, permission changed, interstitial shown/completed/skipped.
Add permissions, permissionEnabled, permissionEnable, location, microphone, permissionsPageDescription, permissionsSetupTitle, permissionsSetupDescription, skipForNow, permissionsChangeAnytime, permissionsRequired, permissionsRequiredDescription with translations for all 33 locales.
Greptile SummaryThis PR cleanly introduces a permissions settings page and a fresh-install interstitial, with solid routing logic, proper Key changes:
Confidence Score: 4/5Safe to merge after fixing the build()-time analytics call; remaining findings are minor. The routing logic and preference-gating are sound. The one P1 bug — the Mixpanel event firing on every rebuild — inflates analytics data but does not affect user-facing behaviour or data integrity, keeping this at 4 rather than lower. The two P2 findings are a missing
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[App Launch] --> B{isSignedIn?}
B -- No --> C[Auth Screen]
B -- Yes --> D{onboardingCompleted?}
D -- No --> E[OnboardingWrapper\nSets both onboardingCompleted\nand permissionsCompleted=true]
D -- Yes --> F{permissionsCompleted?}
F -- Yes --> G[HomePageWrapper]
F -- No --> H[_PermissionsGate\nasync permission check]
H -- All granted --> I[Set permissionsCompleted=true\nShow HomePageWrapper]
H -- Not granted --> J[PermissionsInterstitialPage\nfire permissionsInterstitialShown]
J -- Continue --> K[Request notification +\nlocation permissions\nSet permissionsCompleted=true]
J -- Skip --> L[Set permissionsCompleted=true]
K --> G
L --> G
G --> M[Settings Drawer\n→ PermissionsPage\nShows all 4 permission statuses]
|
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| return Container( | ||
| margin: const EdgeInsets.only(bottom: 16), | ||
| padding: const EdgeInsets.fromLTRB(20, 16, 20, 16), | ||
| decoration: BoxDecoration( | ||
| color: Colors.grey[900], | ||
| borderRadius: BorderRadius.circular(16), | ||
| border: Border.all(color: Colors.grey[700]!, width: 1), | ||
| ), | ||
| child: Row( | ||
| children: [ | ||
| Expanded( | ||
| child: Column( | ||
| crossAxisAlignment: CrossAxisAlignment.start, | ||
| children: [ | ||
| Text( | ||
| title, | ||
| style: const TextStyle( |
There was a problem hiding this comment.
BuildContext used across async gap without
mounted check
showDialog(context: context, ...) is called after await provider.askForLocationPermissions(). If the widget is removed from the tree during that async gap, context is no longer valid and this will throw or produce a no-op overlay entry. A guard on context.mounted should wrap the showDialog call:
if (!serviceStatus) {
provider.updateLocationPermission(false);
if (context.mounted) {
showDialog(
context: context,
builder: (ctx) {
return getDialog(
context,
() => Navigator.of(context).pop(),
() => Navigator.of(context).pop(),
context.l10n.locationServiceDisabled,
context.l10n.locationServiceDisabledDesc,
singleButton: true,
);
},
);
}
}| } | ||
| final status = await Permission.locationWhenInUse.request(); | ||
| if (status.isGranted) { | ||
| await Permission.locationAlways.request(); | ||
| } else if (status.isPermanentlyDenied) { | ||
| await openAppSettings(); | ||
| } | ||
| await _checkPermissions(); | ||
| MixpanelManager().permissionChanged(permission: 'location', granted: _locationGranted); | ||
| } | ||
| } |
There was a problem hiding this comment.
Analytics not fired when location service is disabled
In _handleLocationTap, when the location service is disabled, the method calls openAppSettings() and returns early — before _checkPermissions() and the MixpanelManager().permissionChanged(...) call. This creates an inconsistency: every other permission tap fires the analytics event, but this particular path silently skips it.
if (await Permission.location.serviceStatus.isDisabled) {
await openAppSettings();
await _checkPermissions();
MixpanelManager().permissionChanged(permission: 'location', granted: _locationGranted);
return;
}…asedHardware#6159) ## Summary - **Permissions settings page** — accessible from Settings (below Phone Calls), shows status of Notifications, Location, Bluetooth, and Microphone with tap-to-enable and system settings deep-linking. Refreshes automatically when returning from system settings. - **Permissions interstitial** — shown when onboarding was completed on the backend but permissions haven't been granted on this device (fresh install). Displays Omi logo, permission checkboxes, Continue (requests both permissions), and a Skip for now option. - **Routing gate** — `_PermissionsGate` in `mobile_app.dart` checks if permissions are already granted before showing the interstitial. Auto-marks as done if granted. - **`permissionsCompleted` preference** — separate from `onboardingCompleted` to track per-device permission state. Set during normal onboarding completion and after the interstitial. - **Mixpanel events** — `permissionsSettingsOpened`, `permissionChanged(permission, granted)`, `permissionsInterstitialShown`, `permissionsInterstitialCompleted`, `permissionsInterstitialSkipped` - **l10n** — all new strings translated across 33 locales Closes BasedHardware#6131 ## Test plan - [ ] Fresh install with existing account (onboarding completed on backend) → permissions interstitial should appear - [ ] Tap Continue on interstitial → notification and location permission prompts shown, then goes to home - [ ] Tap Skip for now → goes to home without requesting permissions - [ ] Re-open app after skipping → should go straight to home (interstitial only shows once) - [ ] New user going through full onboarding → no interstitial shown after completion - [ ] Settings > Permissions → shows correct enabled/disabled status for all 4 permissions - [ ] Tap a disabled permission → system prompt or opens settings - [ ] Return from system settings → status refreshes automatically - [ ] Verify Mixpanel events fire for all actions 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Summary
_PermissionsGateinmobile_app.dartchecks if permissions are already granted before showing the interstitial. Auto-marks as done if granted.permissionsCompletedpreference — separate fromonboardingCompletedto track per-device permission state. Set during normal onboarding completion and after the interstitial.permissionsSettingsOpened,permissionChanged(permission, granted),permissionsInterstitialShown,permissionsInterstitialCompleted,permissionsInterstitialSkippedCloses #6131
Test plan
🤖 Generated with Claude Code