Skip to content

feat: interactive device onboarding tutorial#6124

Draft
aaravgarg wants to merge 50 commits intomainfrom
worktree-interactive-device-onboarding
Draft

feat: interactive device onboarding tutorial#6124
aaravgarg wants to merge 50 commits intomainfrom
worktree-interactive-device-onboarding

Conversation

@aaravgarg
Copy link
Copy Markdown
Collaborator

Summary

  • Adds a 4-step interactive device tutorial accessible from Settings → Device Tutorial (when device connected)
  • Step 0: Real-time transcription demo — user speaks, sees live transcript, "Good job" after 5+ words
  • Step 1: Ask a question — single press to enter voice mode, speak question, get AI response
  • Step 2: Power cycle — long press to turn off (detects disconnect), press to turn on (detects reconnect)
  • Step 3: Double tap config — choose between End Conversation / Mute-Unmute / Star Conversation, try it live
  • Intercepts button presses and transcription in CaptureProvider during onboarding
  • Suppresses disconnect notification during power cycle step
  • Mixpanel analytics for each step and completion
  • Non-skippable: PopScope(canPop: false), no back/skip buttons

Test plan

  • Connect Omi device → open Settings → verify "Device Tutorial" appears under Device Settings
  • Tap "Device Tutorial" → verify 4-step flow launches full-screen
  • Step 0: Speak into device → verify live transcript → verify "Good job" after a few words → tap Continue
  • Step 1: Press button → verify "Listening" → speak → press again → verify AI response → tap Continue
  • Step 2: Long press → verify "Device is off" → press to turn on → verify "Connected" → tap Continue
  • Step 3: Select a double-tap action → double-tap device → verify detection → tap Finish
  • Verify you're back in settings after finishing
  • Verify double-tap preference was saved (test the action)
  • Disconnect device → open Settings → verify "Device Tutorial" is hidden

🤖 Generated with Claude Code

aaravgarg and others added 13 commits March 28, 2026 17:07
… tutorial

State management for the 4-step interactive device onboarding flow:
transcription demo, ask-a-question, power cycle, and double-tap config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ce tutorial

Routes button events and transcript segments to DeviceOnboardingProvider
when interactive onboarding is active. Single-tap during ask-a-question
step falls through to normal voice command handling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Notifies DeviceOnboardingProvider on device disconnect and reconnect.
Suppresses the 30-second disconnect notification during active onboarding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OnboardingStepScaffold provides consistent layout with progress dots,
title, subtitle, content area, and bottom action. OnboardingContinueButton
is the standard white pill-shaped button used across all steps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Simplified real-time transcript view for the onboarding context.
Shows segment text without speaker avatars or timestamps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
User speaks into the device and sees live transcript appear.
Shows pulsing mic animation while waiting, then "Good job!" with
checkmark after 5+ words detected. Continue button appears after delay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Guides user through pressing the button, speaking a question, and
pressing again to submit. Shows state transitions: waiting → listening →
processing → AI response. Observes MessageProvider for the response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Teaches long press to power off (detects BLE disconnect) and single
press to turn back on (detects reconnect). Shows 30-second hint if
user hasn't powered off yet. Animated status indicators for each state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three selectable cards: End Conversation, Mute/Unmute, Star Conversation.
User picks their preference and tries a double tap to confirm. Saves
selection to SharedPreferences. Finish button completes onboarding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Main orchestrator with non-dismissible PageView of 4 steps.
Creates DeviceOnboardingProvider locally and wires it into CaptureProvider.
Tracks analytics for each step completion via Mixpanel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows "Device Tutorial" item under Device Settings when device is
connected. Tapping launches the interactive device onboarding flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Track: deviceOnboardingStarted, deviceOnboardingStepCompleted,
deviceOnboardingCompleted, deviceOnboardingDoubleTapConfigured.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 28, 2026

Greptile Summary

This PR adds a 4-step interactive device onboarding tutorial (transcription demo → ask a question → power cycle → double-tap config) accessible from Settings when a device is connected. The overall architecture is solid — DeviceOnboardingProvider cleanly encapsulates step state, the wrapper correctly caches the CaptureProvider reference per earlier review feedback, and all AnimationControllers are properly disposed.

Key findings:

  • Future.delayed inside Consumer build body (transcription_demo_step.dart, power_cycle_step.dart): a new timer is created on every rebuild while the condition holds; move the delay into a provider listener in initState.
  • Mutable list alias in onTranscriptSegments (device_onboarding_provider.dart): demoSegments = segments risks silent mutations from _processNewSegmentReceived; use List.from(segments).
  • onDeviceDisconnected fires before the _manualDisconnect guard (device_provider.dart): swap the two blocks so an app-initiated disconnect doesn't incorrectly advance the power-cycle step.
  • live_transcript_display.dart is unused — dead file from an earlier iteration, should be removed.

Confidence Score: 4/5

Safe to merge after addressing the Future.delayed-in-build timer accumulation and the onDeviceDisconnected ordering issue.

Two P1/P2 logic issues remain that should be fixed before shipping to avoid subtle edge-case bugs, but nothing catastrophic.

transcription_demo_step.dart and power_cycle_step.dart (Future.delayed in build), device_provider.dart (onDeviceDisconnected ordering), device_onboarding_provider.dart (list alias)

Important Files Changed

Filename Overview
app/lib/providers/device_onboarding_provider.dart New provider coordinating 4-step onboarding state; stores a mutable alias to transcript segments list (P2), otherwise clean logic.
app/lib/providers/device_provider.dart Hooks disconnect/reconnect events into onboarding provider; onDeviceDisconnected fires before _manualDisconnect guard (P2 ordering issue).
app/lib/pages/onboarding/interactive_device_onboarding/steps/transcription_demo_step.dart Step 0 implementation; Future.delayed inside Consumer build body creates repeated timers on each rebuild (P1).
app/lib/pages/onboarding/interactive_device_onboarding/steps/power_cycle_step.dart Step 2 implementation; same Future.delayed-in-build anti-pattern as transcription step for the reconnected state transition.
app/lib/pages/onboarding/interactive_device_onboarding/interactive_device_onboarding_wrapper.dart Clean wrapper using ChangeNotifierProvider.value + PageView; caches CaptureProvider reference correctly per prior review feedback.
app/lib/providers/capture_provider.dart Adds onboarding interception for button events and transcript segments; step-1 single-tap fall-through is correct.

Comments Outside Diff (1)

  1. app/lib/pages/onboarding/interactive_device_onboarding/steps/transcription_demo_step.dart, line 748-752 (link)

    P1 Future.delayed inside build creates duplicate timers

    Future.delayed is called directly inside the Consumer builder, which means every time the DeviceOnboardingProvider calls notifyListeners() while transcriptionComplete && !_showContinue is true, a new timer is enqueued. Multiple independent timers pile up. Although each is harmless individually (the mounted && setState call is idempotent), the accumulation is wasteful and fragile.

    The same pattern exists in power_cycle_step.dart (line 313–317) for the reconnected state.

    Move the one-shot delay into a provider listener registered in initState so it fires exactly once.

Reviews (2): Last reviewed commit: "feat(onboarding): add Omi image with pul..." | Re-trigger Greptile

Comment on lines +45 to +50
}

void _onStepComplete(String stepName) {
MixpanelManager().deviceOnboardingStepCompleted(stepName);

if (_onboardingProvider.currentStep < DeviceOnboardingProvider.totalSteps - 1) {
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 context.read<>() called in dispose()

Calling context.read<CaptureProvider>() inside dispose() is discouraged — by the time dispose() runs the element may be deactivating and Provider can emit warnings or throw in debug mode. Cache the reference in initState instead:

late CaptureProvider _captureProvider;

@override
void initState() {
  super.initState();
  _captureProvider = context.read<CaptureProvider>();
  WidgetsBinding.instance.addPostFrameCallback((_) {
    _captureProvider.deviceOnboardingProvider = _onboardingProvider;
    _onboardingProvider.startOnboarding();
    MixpanelManager().deviceOnboardingStarted();
  });
}

@override
void dispose() {
  _captureProvider.deviceOnboardingProvider = null;
  _onboardingProvider.dispose();
  _pageController.dispose();
  super.dispose();
}

),
const Divider(height: 1, color: Color(0xFF3C3C43)),
_buildSettingsItem(
title: 'Device Tutorial',
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 Hardcoded string — l10n required

Every other _buildSettingsItem call in this file uses context.l10n.* (e.g. context.l10n.profile, context.l10n.notifications, context.l10n.developerSettings), but 'Device Tutorial' is a bare string literal. Per the project's localization policy all user-facing strings must go through l10n.

The same applies to all titles, subtitles, button labels, and hint texts in the new onboarding files:

  • transcription_demo_step.dart'Speak Into Your Omi', 'Say a few words...', 'Good job!'
  • single_press_step.dart'Ask Omi a Question', 'Listening...', 'Processing...', etc.
  • power_cycle_step.dart'Turn Off & On', 'Hold the button...', 'Device is off!', etc.
  • double_press_config_step.dart'Customize Double Tap', option titles and descriptions
  • onboarding_step_scaffold.dart'Continue', 'Finish'

All of these should be extracted into the app's ARB files and referenced via context.l10n.

Context Used: Flutter localization - all user-facing strings mus... (source)

Track message count at voice session start and only look at messages
added after that point. Filter out app notifications (fromIntegration)
and incomplete messages. Also show the user's transcribed question
above the AI response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aaravgarg aaravgarg requested a review from mdmohsin7 March 28, 2026 11:52
aaravgarg and others added 13 commits March 28, 2026 17:29
…ssion

The previous approach tried to snapshot inside _onMessagesChanged when
voiceSessionActive was true, but that callback only fires on MessageProvider
changes, not DeviceOnboardingProvider changes — so the snapshot was never
set, causing the response filter to return early.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If user accidentally single-taps during the double-press config step,
show an amber hint: "That was a single tap — try tapping twice quickly!"
Hint clears when they successfully double-tap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ispose

context.read in dispose() crashes because the widget tree is already
deactivated. Cache the reference in initState's post-frame callback
and use it directly in dispose.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace plain black with a top-to-bottom gradient from deep purple
(#1A0033) to black across all onboarding steps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The AI response can contain markdown (bold, italic, code) which renders
as raw asterisks/backticks in a plain Text widget. Strip it before display.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fully rounded corners (borderRadius 100) on option cards and hint bar
- Removed subtitle line below heading
- Shorter option descriptions
- Replaced bare icon+text for double-tap detected with a styled pill chip

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Selected option gets solid white background with black text/icons
instead of a subtle highlight with a checkmark.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Star: stacked conversation cards with live waveform on top, star
  appears on double-tap
- End Conversation: active waveform that cuts on double-tap, new
  conversation card fades in
- Mute/Unmute: conversation card with waveform that toggles to
  flat/muted state on double-tap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of showing a new card below, the waveform splits inline:
frozen/faded old waveform on the left, a thin vertical divider gap,
then a live green waveform continuing on the right.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Star: single card with waveform, star toggles on/off each double-tap
- Mute: red dot/waveform/icon when muted, toggles infinitely
- End: split resets after 2s so user can split again
- Removed "double tap detected" green pill, Finish button shows after
  first double-tap but user can keep experimenting
- Changed doublePressDetected bool to doublePressCount int

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dart's replaceAll with RegExp treats $1 as literal text, not a
backreference. Use replaceAllMapped to properly extract capture groups.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move waveform animations from a separate widget below the options
into the selected option card itself. Updated colors for white card
background (dark waveforms, red mute state).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… on split

- Hide Finish button when single-tap hint is showing
- Replace frozen waveform on left side of split with horizontal lines
  graphic representing a generated summary

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
aaravgarg and others added 7 commits March 28, 2026 20:50
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Card-based layout: waveform bar with mic icon and word counter while
listening, green pill when complete. Transcript appears in a rounded
card below. Matches double-tap page design language.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Card-based states: rounded card with touch icon for waiting, green
bordered card with waveform while listening, card with spinner while
processing, chat bubble layout for question/answer. No more floating
circles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Card-based layout: colored status pill (green/red) showing connection
state, instruction card with icon, amber hint card. Animated transitions
between states. No more floating circles or bare icons.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sult UI

Remove all waveform animations and AnimationController from the step.
Result now shows in a white card with question as grey text above a
divider, answer as black text below. Clean, static, readable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Title changes between "Turn Off" and "Turn On" based on state
- Omi device image between title and status card
- Connected: omiWithoutRope image, Disconnected: omiWithoutRopeTurnedOff
  with red X badge on bottom right
- AnimatedSwitcher for smooth image transitions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aaravgarg aaravgarg marked this pull request as draft March 28, 2026 15:39
@aaravgarg
Copy link
Copy Markdown
Collaborator Author

@greptile-apps review

aaravgarg and others added 16 commits March 28, 2026 21:13
Waveform pulses amplitude in place instead of scrolling left/right.
Uses sin(phase) to modulate amplitude rather than shifting the wave.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Image size 140 -> 180, margin below 24 -> 32, red X badge 28 -> 36
with icon 16 -> 20.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…on step

Omi device image centered above the status card with 3 concentric
pulsating circles radiating outward behind it. Circles fade out as
they expand. Hidden after transcription completes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Start with selectedDoubleTapAction = -1 (none selected) so user
must explicitly pick an option. Ignore double-tap events until
an option is selected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pulses

Removed the waveform status card entirely. Omi image 120->160px,
pulse container 200->320px so circles radiate much wider. Animation
duration 2s->4s for slower pulses. Removed _LiveWavePainter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove CircularProgressIndicator, use ShaderMask with a sweeping
LinearGradient on "Processing your question..." text instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows "Try it now! Double tap your Omi" pill at bottom after user
selects an option but before first double-tap. Same style as the
single-tap warning but in neutral grey.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Waiting state: Omi device image with an animated finger icon that bobs
down to tap and lifts back up on a loop.
Listening state: Omi with pulsating circles (like transcription page)
plus the waveform bar below it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When user presses the device button, the Omi image scales down to 0.85
and bounces back over 300ms, giving a "pressed into screen" feel.
Finger animation stops when listening starts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Finger icon starts at bottom-right corner, moves toward the device,
presses it (Omi scales down to 0.92), then retreats back out. Pauses
briefly before looping. Omi bounces back after each press.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…on Omi

Remove finger icon entirely. Waiting state shows Omi image with a
looping bounce-click animation (scales down to 0.9 and back).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add device_onboarding_completed field to GET/PATCH /v1/users/onboarding.
Stored in Firestore under users/{uid}.onboarding.device_onboarding_completed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend updateUserOnboardingState to accept deviceOnboardingCompleted
parameter for setting the Firestore flag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On completion, sets both local pref and calls updateUserOnboardingState
to persist device_onboarding_completed=true in Firestore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When device connects, check if deviceOnboardingCompleted is false
(local pref first, then Firestore). If not completed, push the
interactive device onboarding as a full-screen route. Works for
both new users (first connection) and existing users (next connection
after feature ships). Guard prevents showing twice per session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

1 participant