You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Parent epic:#1446 (Amicus — AI Study Partner v1) Phase: 3 · Size: M · Depends on:#1460 (access hook), #1474 (chip selection)
Build the floating action button and the peek sheet that expands on tap. The FAB is Amicus's always-on surface — visible on every primary screen, context-aware, premium-gated.
Files to create
app/src/components/amicus/AmicusFab.tsx — the FAB button itself
app/src/components/amicus/AmicusPeekSheet.tsx — bottom-sheet that expands on tap
app/src/contexts/AmicusFabContext.tsx — provider + hook for screens to opt in/out of showing the FAB
app/src/navigation/RootNavigator.tsx — mount <AmicusFabContext.Provider> + <AmicusFab /> at root level (above the TabNavigator but inside SafeAreaProvider)
Screens that want to suppress the FAB (auth, onboarding, modals) — call useAmicusFab().hide() on mount, show() on unmount
Conventions to follow
Bottom-sheet pattern: follow QnavOverlay.tsx and LinkedJourneySheet.tsx — @gorhom/bottom-sheet with snap points, sheetRef.current?.snapToIndex(0)
Theme imports: useTheme, spacing, radii, fontFamily, MIN_TOUCH_TARGET from @/theme
Android back handler via BackHandler.addEventListener('hardwareBackPress', ...) — closes sheet not app
Safe-area via react-native-safe-area-context
Keyboard-aware: KeyboardAvoidingView or @gorhom built-in
Logger, not console.log; strict TS
AmicusFab component
Visual
Circular button, 56×56 px (matches Material FAB standard)
Hidden when useAmicusAccess().reason === 'disabled_in_settings'
Context API
exportinterfaceAmicusFabContextValue{isVisible: boolean;hide: ()=>void;// screen asks FAB to hide while mountedshow: ()=>void;// back to default}exportfunctionuseAmicusFab(): AmicusFabContextValue;
Screens that want to suppress: call hide() on mount in useEffect, show() in cleanup.
AmicusPeekSheet component
Snap points
['50%', '85%'] — 50% for initial peek, 85% if user drags up or expands a mini-conversation
Peek sheet hosts the mini-conversation component (#1463) when a chip or input is activated. Before activation, only chips + input are visible.
Integration with navigation context
The FAB reads navigation state to determine current screen context and pass to #1474's chip selector:
constroute=useNavigationState(state=>getCurrentRoute(state));constchapterRef=extractChapterRefFromRoute(route);// null if not on a chapter// Pass to useAmicusChips(chapterRef) in #1474
Animation & performance
FAB fade-in on first render after hydration (not jarring)
Peek open uses @gorhom default spring
All animations use react-native-reanimated (match existing usage); no JS-thread animations
FAB render should not trigger re-renders in host screens — isolated via memoization
Acceptance criteria
FAB visible on all primary screens when premium + enabled
FAB hidden on modals, auth, onboarding, Amicus tab
FAB hidden when amicus_enabled preference is false
Parent epic: #1446 (Amicus — AI Study Partner v1)
Phase: 3 · Size: M · Depends on: #1460 (access hook), #1474 (chip selection)
Build the floating action button and the peek sheet that expands on tap. The FAB is Amicus's always-on surface — visible on every primary screen, context-aware, premium-gated.
Files to create
app/src/components/amicus/AmicusFab.tsx— the FAB button itselfapp/src/components/amicus/AmicusPeekSheet.tsx— bottom-sheet that expands on tapapp/src/contexts/AmicusFabContext.tsx— provider + hook for screens to opt in/out of showing the FABapp/src/components/amicus/__tests__/AmicusFab.test.tsxapp/src/components/amicus/__tests__/AmicusPeekSheet.test.tsxFiles to modify
app/src/navigation/RootNavigator.tsx— mount<AmicusFabContext.Provider>+<AmicusFab />at root level (above the TabNavigator but inside SafeAreaProvider)useAmicusFab().hide()on mount,show()on unmountConventions to follow
QnavOverlay.tsxandLinkedJourneySheet.tsx—@gorhom/bottom-sheetwith snap points,sheetRef.current?.snapToIndex(0)useTheme, spacing, radii, fontFamily, MIN_TOUCH_TARGETfrom@/themeBackHandler.addEventListener('hardwareBackPress', ...)— closes sheet not appreact-native-safe-area-contextKeyboardAvoidingViewor@gorhombuilt-inAmicusFab component
Visual
bottom: safeBottom + 16 + tabBarHeight, right: 16base.gold, icon:MessageSquarefrom lucide-react-native at 24px in dark foregroundshadowColor, Android:elevation: 4)expo-hapticsif available)Visibility rules (composed via AmicusFabContext)
amicusEnabledpreference is false (see ai-partner: Settings controls #1459)useAmicusAccess().reason === 'disabled_in_settings'Context API
Screens that want to suppress: call
hide()on mount inuseEffect,show()in cleanup.AmicusPeekSheet component
Snap points
['50%', '85%']— 50% for initial peek, 85% if user drags up or expands a mini-conversationLayout (top → bottom)
Behavior
State
Mini-conversation integration
Peek sheet hosts the mini-conversation component (#1463) when a chip or input is activated. Before activation, only chips + input are visible.
Integration with navigation context
The FAB reads navigation state to determine current screen context and pass to #1474's chip selector:
Animation & performance
@gorhomdefault springreact-native-reanimated(match existing usage); no JS-thread animationsAcceptance criteria
amicus_enabledpreference is falseanytypes; lint cleanOut of scope