Skip to content

feat(app): permissions settings page and fresh install interstitial#6159

Merged
mdmohsin7 merged 10 commits into
mainfrom
feat/permissions-settings-and-interstitial
Mar 29, 2026
Merged

feat(app): permissions settings page and fresh install interstitial#6159
mdmohsin7 merged 10 commits into
mainfrom
feat/permissions-settings-and-interstitial

Conversation

@mdmohsin7
Copy link
Copy Markdown
Member

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 eventspermissionsSettingsOpened, permissionChanged(permission, granted), permissionsInterstitialShown, permissionsInterstitialCompleted, permissionsInterstitialSkipped
  • l10n — all new strings translated across 33 locales

Closes #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

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-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 29, 2026

Greptile Summary

This PR cleanly introduces a permissions settings page and a fresh-install interstitial, with solid routing logic, proper permissionsCompleted preference gating, and full l10n coverage across 33 locales.

Key changes:

  • _PermissionsGate in mobile_app.dart checks permission state on launch and routes to either home or the interstitial.
  • PermissionsInterstitialPage in permissions_checker.dart requests notification + location; both "Continue" and "Skip" set permissionsCompleted = true to prevent a repeat showing.
  • PermissionsPage in permissions_page.dart lists all four permissions with live status and tap-to-enable behaviour; auto-refreshes via WidgetsBindingObserver on app resume.
  • permissionsCompleted = true is also set during normal onboarding completion so new users are never routed to the interstitial.
  • 33-locale l10n strings added for all new UI copy.

Confidence Score: 4/5

Safe 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 mounted guard and an inconsistent analytics path, neither of which causes a crash or data loss in normal operation.

app/lib/mobile/mobile_app.dart (analytics in build), app/lib/pages/settings/permissions_page.dart (inconsistent analytics path)

Important Files Changed

Filename Overview
app/lib/mobile/mobile_app.dart Added _PermissionsGate widget to route fresh-install users to the permissions interstitial; Mixpanel analytics event is incorrectly fired on every build() call instead of once.
app/lib/pages/onboarding/permissions/permissions_checker.dart New file: arePermissionsGranted helper and PermissionsInterstitialPage; minor issue with missing context.mounted guard after async gap in the location tile handler.
app/lib/pages/settings/permissions_page.dart New settings page showing notification/location/bluetooth/microphone status with tap-to-enable and auto-refresh on app resume; analytics not fired when location service is disabled (inconsistent path).
app/lib/backend/preferences.dart Added permissionsCompleted SharedPreferences key to track per-device permission grant state separately from onboardingCompleted.
app/lib/utils/analytics/mixpanel.dart Added five new permission-related Mixpanel tracking methods; the rest of the diff is purely whitespace/indentation reformatting.
app/lib/pages/settings/settings_drawer.dart Added Permissions entry to the settings drawer below Phone Calls, with Mixpanel event on open.
app/lib/pages/onboarding/wrapper.dart Sets permissionsCompleted = true at normal onboarding completion so the interstitial is never shown to users who completed full onboarding; also contains cosmetic indentation fixes.
app/lib/pages/home/page.dart Sets permissionsCompleted = true when the home page loads during the normal flow; also contains minor indentation-only formatting changes.

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]
Loading

Comments Outside Diff (1)

  1. app/lib/mobile/mobile_app.dart, line 86-88 (link)

    P1 Analytics event fires on every widget rebuild

    MixpanelManager().permissionsInterstitialShown() is called directly inside build(), which means it fires every time the widget tree is rebuilt (e.g., theme change, ancestor state update, device rotation). This will cause the event to be recorded multiple times for a single user view.

    The call should instead be made exactly once when the interstitial is first determined to be visible — move it into _check() after confirming granted == false:

    Future<void> _check() async {
      final granted = await arePermissionsGranted();
      if (granted) {
        SharedPreferencesUtil().permissionsCompleted = true;
      } else {
        MixpanelManager().permissionsInterstitialShown(); // fire once here
      }
      if (mounted) {
        setState(() => _permissionsGranted = granted);
      }
    }

    Then remove the call from build():

    // in build():
    if (_permissionsGranted!) {
      return const HomePageWrapper();
    }
    return const PermissionsInterstitialPage(); // no analytics call here

Reviews (1): Last reviewed commit: "feat(app): add l10n keys for permissions..." | Re-trigger Greptile

Comment on lines +261 to +280

@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(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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,
        );
      },
    );
  }
}

Comment on lines +110 to +120
}
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);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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;
}

@mdmohsin7 mdmohsin7 merged commit d62f917 into main Mar 29, 2026
2 checks passed
@mdmohsin7 mdmohsin7 deleted the feat/permissions-settings-and-interstitial branch March 29, 2026 11:50
Glucksberg pushed a commit to Glucksberg/omi-local that referenced this pull request Apr 28, 2026
…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)
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.

Fresh installs skip permission requests when onboarding already completed on backend

1 participant