WikifitaGitHub live67e8de5
outro · camdom/camdom-onboarding

CAMDOM - Onboarding Flow

Complete onboarding walkthrough: HowTo instructions, Bluetooth permission request, platform-specific behavior, and the redirect-to-main-app completion path

Baixar raw

Overview

The onboarding flow is the entry point for new users. It is a 2-step process stored at app/onboarding.tsx. Once completed, the flag isOnboardingComplete is set to true in MMKV storage, and all subsequent app launches redirect directly to the main screen.

Route Behavior

if (isOnboardingComplete) return <Redirect href={"/"} />;

If onboarding is already complete, the component immediately redirects to the main index route. This means the onboarding screen is only visible on first launch (or after clearing storage).


Step 0: How To Use a Camdom

What the User Sees

  • Title: "how to use a camdom" (localized)
  • Separator: Full-width horizontal line via <Separator />
  • Content: <HowTo /> component centered in the remaining space

The <HowTo /> component (from the Menu system) renders:

  1. Instruction text: "install the app on all devices" (localized, with \n for line breaks)
  2. Bluetooth notice: "Bluetooth must be on" in a styled sub-container
  3. <CamdomInstruction /> -- a visual step-by-step guide showing how the app works

Interaction

The user reads the instructions and presses the "continue" button at the bottom.

iOS Behavior

On iOS, pressing "continue" at step 0 immediately completes onboarding:

if (onboardingStep === 0 && Platform.OS === "ios")
  setOnboardingIsComplete(true);

iOS does not require explicit Bluetooth permissions at the app level (BLE permissions are handled via Info.plist descriptions). The user proceeds directly to the main app.

Android Behavior

On Android, pressing "continue" at step 0 advances to step 1 (permissions). The onboarding is NOT completed yet.


Step 1: Permission Request

What the User Sees

  • Title: "Give Permissions" (localized as ask_permission)
  • Separator: Full-width horizontal line
  • Description: "Camdom uses bluetooth to make connections, please allow it when prompted" (localized as ask_permission_text)
  • Icon: FontAwesome Bluetooth icon (bluetooth, size 64, black)
  • Button: "continue" (localized)

The Step and StepStep Helper Components

The onboarding file defines two local helper components:

Step -- Conditional rendering wrapper:

const Step: FC<PropsWithChildren<{ visibleOnStep: number; step: number }>>

Only renders its children when step >= visibleOnStep. Used to progressively reveal content as the user advances.

StepStep -- A numbered instruction row:

const StepStep: FC<{
  step?: number;
  text: string;
  image?: ImageSourcePropType;
  shouldRecenter1?: boolean;
  hideNumberOnEnd?: boolean;
}> 

Renders a row with step number on both sides of the text, and an optional image below. Currently only text is used in the actual onboarding (no step number or image passed).

Permission Flow

When the user presses "continue" on step 1:

if (onboardingStep >= 1)
  requestPermissions(async (permissionGranted) => {
    if (permissionGranted) {
      setOnboardingIsComplete(true);
    }
  });

The requestPermissions function (from utils/requestPermissions.ts) is called. This triggers the platform-specific permission dialog:

  • iOS: Immediately calls cb(true) -- no dialog needed
  • Android API < 31: Shows ACCESS_FINE_LOCATION permission dialog
  • Android API >= 31: Shows BLUETOOTH_SCAN, BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE permission dialogs

If permissions are granted, setOnboardingIsComplete(true) is called, which:

  1. Persists true to MMKV under StorageKeys.isOnboardingComplete
  2. On next render, the <Redirect href={"/"} /> triggers, sending the user to the main app

If permissions are denied, the onboarding does NOT complete. The user remains on step 1 and can try again by pressing "continue" again.

Important: Step Increment Regardless of Permission Result

setOnboardingStep((val) => val + 1);
scrollViewRef.current?.scrollToEnd({ animated: true });

The step counter increments and the scroll view scrolls to the end regardless of whether permissions were granted. However, isOnboardingComplete is only set inside the permission callback on success. This means if the user denies permissions, the step advances visually but onboarding remains incomplete.


Visual Layout

Scroll-Based Layout

The onboarding uses a ScrollView with a ref for programmatic scrolling. The "continue" button is positioned absolutely at the bottom of the screen.

Responsive Design

Uses react-native-unistyles for responsive breakpoints:

marginTop: UnistylesRuntime.breakpoint === "sm"
  ? UnistylesRuntime.insets.top + 20
  : 80,
marginBottom: UnistylesRuntime.breakpoint === "sm"
  ? UnistylesRuntime.insets.bottom + 5
  : 10,
  • sm breakpoint (small screens): Uses safe area insets + smaller margins
  • Larger screens: Fixed 80px top margin, 10px bottom margin

Background

<NoisyBlurredBackground /> is rendered behind all content (default props -- no balls shown, just the Skia canvas background color).

Styling

All styles are defined inline in the component via StyleSheet.create:

StyleProperties
titleBasicSans-Bold, 16px, uppercase, primary color, left-aligned
subtitleBasicSans-Bold, 14px, uppercase, primary color
subSubtitleBasicSans-Bold, 11px, uppercase, primary color, center-aligned
continueButtonContainerAbsolute bottom, full width, 15px horizontal padding
continueButtonFull width, primary color border top/bottom (1px), centered content, 12px vertical padding
continueButtonTextBasicSans-SemiBold, 14px, uppercase, primary color

Button Style

The "continue" button uses a distinctive double-border style: only borderTopWidth and borderBottomWidth are set (1px each in primary color), with no left/right borders. This creates a horizontal-line-framed button aesthetic consistent with the CAMDOM design language.


Haptic Feedback

Every "continue" press triggers Haptics.selectionAsync() for tactile feedback, using the lightest haptic style.


State Machine Summary

Launch
  |
  +-- isOnboardingComplete? --> Redirect to "/" (main app)
  |
  +-- Step 0: HowTo instructions
  |     |
  |     +-- [iOS] "continue" --> setOnboardingComplete(true) --> Main app
  |     +-- [Android] "continue" --> Step 1
  |
  +-- Step 1: Permission request
        |
        +-- "continue" --> requestPermissions()
              |
              +-- granted --> setOnboardingComplete(true) --> Main app
              +-- denied --> Stay on step 1 (can retry)

Key Observations

  1. iOS skips Bluetooth permission dialogs entirely -- the permission flow on iOS is a no-op, making the onboarding a single-tap process
  2. Android requires one "continue" press before the permission dialog -- the user sees the explanation screen first, then the system dialog on the next press
  3. The onboarding does not gate on permission denial -- the step advances regardless, but the completion flag is only set on grant. This means a user who denies permissions can still see the step advance, but on next app launch they will be redirected back to onboarding (since isOnboardingComplete is still false)
  4. The HowTo component is shared -- the same <HowTo /> used in the menu system is embedded in onboarding step 0, ensuring consistent instructions across both entry points
  5. The ScrollView ref is used for scrollToEnd on step transition, but the actual content may not overflow -- this suggests the scroll is used primarily for the visual animation of content sliding up
  6. No skip mechanism -- there is no "skip" button. The user must complete the flow or force-quit the app