Skip to content

Settings Screen

Metadata

  • System type: flow

System Intent

  • What this is: The settings screen (SettingsScreenContent) is a React Native screen that lets authenticated users manage device registration (pairing/unpairing Meta View smart glasses), sign out of their account, and toggle developer debug mode. Device registration is coordinated across an in-app button tap, a native iOS module (WearablesModule), the Meta View companion app (an OS-level external app), and a URL-scheme callback — making foreground-return state refresh a critical concern.

Mermaid Diagram

flowchart TD
  SettingsScreen["SettingsScreen (LoggingContextProvider)"]
  SettingsScreen --> SettingsScreenContent

  SettingsScreenContent --> useFocusEffect["useFocusEffect\n(reads isAnyDeviceRegisted on RN nav focus)"]
  SettingsScreenContent --> AppStateListener["AppState listener\n(reads isAnyDeviceRegisted + resets isToggling\non app foreground return)"]

  SettingsScreenContent --> handleToggleRegistration
  handleToggleRegistration --> |"isRegistered=false"| startRegistration["WearablesModule.startRegistration()\n(resolves immediately, opens Meta View via URL scheme)"]
  handleToggleRegistration --> |"isRegistered=true"| startUnregistration["WearablesModule.startUnregistration()\n(awaits full native result)"]

  startRegistration --> MetaView["Meta View (OS-level app)\nUser completes pairing"]
  MetaView --> |"encache:// URL callback"| WearablesAppDelegateSubscriber
  WearablesAppDelegateSubscriber --> |"handleUrl() — sets registered state natively"| AppForeground["App returns to foreground"]
  AppForeground --> AppStateListener

  startUnregistration --> |"result: outcome, durationMs, states, errorMessage"| RefreshAfterUnreg["setIsRegistered + setIsToggling(false)"]

Flows

Flow: deviceRegistration

  • Test files: main/app/__tests__/settings-screen.test.tsx
  • Core files: main/app/app/settings.tsx, main/app/wearables-module/ios/WearablesModule.swift, main/app/wearables-module/ios/WearablesAppDelegateSubscriber.swift

Types

WearablesModule.startRegistration() -> Promise<void>
  Resolves in milliseconds after opening the Meta View companion app via URL scheme.
  Does NOT wait for pairing to complete.

WearablesModule.startUnregistration() -> Promise<UnregistrationResult>
  UnregistrationResult {
    outcome: string        ("success" | "error" | "timeout" | "already_unregistered")
    durationMs: number
    registrationStateBefore: string
    registrationStateAfter: string
    errorMessage: string | null
  }

WearablesModule.isAnyDeviceRegisted() -> boolean
  Synchronous. Reads current native registration state.

Paths

path input output path-type notes
deviceRegistration.start User presses "Register" button startRegistration() called; button disabled (isToggling=true) happy path isToggling is NOT reset in the finally block — AppState listener resets it when app foregrounds
deviceRegistration.foregroundReturn App returns to foreground (AppState active) isRegistered refreshed via isAnyDeviceRegisted(); isToggling reset to false happy path Triggered by AppState.addEventListener('change', ...) — not by useFocusEffect
deviceRegistration.error startRegistration() throws isToggling reset to false in catch block; button re-enabled error User can retry
deviceUnregistration.start User presses "Unregister" button startUnregistration() called; button disabled happy path Awaits full native result before re-enabling button
deviceUnregistration.complete startUnregistration() resolves isRegistered refreshed; isToggling reset to false; button re-enabled happy path Outcome logged via useLogging (wearable_unregistration_completed)
deviceUnregistration.error startUnregistration() throws isToggling reset to false in catch block error isRegistered is re-read via isAnyDeviceRegisted() in finally-equivalent

Pseudocode

// State refresh on RN navigation focus (handles in-app navigation)
useFocusEffect(() => {
  setIsRegistered(WearablesModule.isAnyDeviceRegisted())
})

// State refresh on OS foreground return (handles Meta View → Encache transition)
useEffect(() => {
  const sub = AppState.addEventListener('change', (nextState) => {
    if (appStateRef.current !== 'active' && nextState === 'active') {
      setIsRegistered(WearablesModule.isAnyDeviceRegisted())
      setIsToggling(false)
    }
    appStateRef.current = nextState
  })
  return () => sub.remove()
}, [])

// Registration handler
handleToggleRegistration:
  setIsToggling(true)
  if isRegistered:
    result = await WearablesModule.startUnregistration()
    log('wearable_unregistration_completed', result fields)
    setIsRegistered(WearablesModule.isAnyDeviceRegisted())
    setIsToggling(false)
  else:
    await WearablesModule.startRegistration()
    // Do NOT reset isToggling — AppState listener handles it on foreground return
  catch:
    setIsToggling(false)

Key invariant: startRegistration() opens Meta View via URL scheme and resolves in milliseconds. The button must remain disabled (isToggling=true) until the user completes pairing in Meta View and the OS returns the Encache app to the foreground. useFocusEffect does NOT fire on OS foreground return — only AppState does.

Flow: signOut

  • Test files: main/app/__tests__/settings-screen.test.tsx
  • Core files: main/app/app/settings.tsx

Types

No input payload. Calls useAuth().signOut() then navigates to /(auth)/login.

Paths

path input output path-type notes
signOut.success User presses "Sign out" signOut() called; router navigates to /(auth)/login happy path Logged before action

Flow: debugModeToggle

  • Test files: main/app/__tests__/settings-screen.test.tsx
  • Core files: main/app/app/settings.tsx, main/app/lib/debug-mode.ts

Paths

path input output path-type notes
debugModeToggle User flips the switch setDebugMode(newValue) persisted via expo-secure-store key encache.debug_mode happy path Value read on focus via getDebugMode()

Logs

Source Location
wearable_unregistration_completed Frontend logger (useLogging flow=settings) — includes outcome, duration_ms, registration_state_before, registration_state_after, error_message
sign_out_requested Frontend logger (useLogging flow=settings) — includes username_present

Deployment

  • Mechanism: local only (React Native / Expo screen, no server deployment)
  • Deploy command:
    # Run from main/app/
    npx expo start
    
  • Notes: iOS only for device registration (WearablesModule is an iOS native module). The AppState listener and isAnyDeviceRegisted sync are both iOS-specific concerns.