@@ -10,6 +10,7 @@ import { useFocusEffect } from '@react-navigation/native';
1010import { FlashList , type FlashList as FlashListType } from '@shopify/flash-list' ;
1111import * as Haptics from 'expo-haptics' ;
1212import { useRouter } from 'expo-router' ;
13+ import { SquarePen } from 'lucide-react-native' ;
1314
1415import { type Folder , type Note , useApiService } from '@/src/services/api' ;
1516import { useTheme } from '@/src/theme' ;
@@ -27,8 +28,6 @@ import { useNotesFiltering } from './useNotesFiltering';
2728import { useNotesLoader } from './useNotesLoader' ;
2829
2930// Constants for FAB scroll behavior
30- const FAB_SCROLL_THRESHOLD_START = 280 ; // Start showing FAB when scrolled past this
31- const FAB_SCROLL_THRESHOLD_END = 320 ; // Fully visible at this scroll position
3231const FAB_ANIMATION_DISTANCE = 20 ; // Distance to slide up during animation
3332
3433interface RouteParams {
@@ -101,24 +100,62 @@ export default function NotesList({ navigation, route, renderHeader, scrollY: pa
101100 const filterSortSheetRef = useRef < BottomSheetModal > ( null ) ;
102101 const noteActionsSheetRef = useRef < NoteActionsSheetRef > ( null ) ;
103102 const flatListRef = useRef < FlashListType < Note > > ( null ) ;
103+ const createNoteButtonRef = useRef < View > ( null ) ;
104104
105105 // Scroll tracking for animated divider (use parent's scrollY if provided)
106106 const localScrollY = useRef ( new Animated . Value ( 0 ) ) . current ;
107107 const scrollY = parentScrollY || localScrollY ;
108108
109- // Calculate FAB visibility based on scroll position
110- // Show FAB when scrolled past the header
111- const fabOpacity = scrollY . interpolate ( {
112- inputRange : [ FAB_SCROLL_THRESHOLD_START , FAB_SCROLL_THRESHOLD_END ] ,
113- outputRange : [ 0 , 1 ] ,
114- extrapolate : 'clamp' ,
115- } ) ;
109+ // Track if create note button is off screen
110+ const [ createNoteButtonY , setCreateNoteButtonY ] = useState ( 0 ) ;
111+ const [ isCreateNoteButtonOffScreen , setIsCreateNoteButtonOffScreen ] = useState ( false ) ;
116112
117- const fabTranslateY = scrollY . interpolate ( {
118- inputRange : [ FAB_SCROLL_THRESHOLD_START , FAB_SCROLL_THRESHOLD_END ] ,
119- outputRange : [ FAB_ANIMATION_DISTANCE , 0 ] ,
120- extrapolate : 'clamp' ,
121- } ) ;
113+ // Calculate FAB visibility based on whether Create Note button is off screen
114+ const fabOpacity = useRef ( new Animated . Value ( 0 ) ) . current ;
115+ const fabTranslateY = useRef ( new Animated . Value ( FAB_ANIMATION_DISTANCE ) ) . current ;
116+
117+ // Update FAB visibility when button goes off screen
118+ useEffect ( ( ) => {
119+ Animated . parallel ( [
120+ Animated . timing ( fabOpacity , {
121+ toValue : isCreateNoteButtonOffScreen ? 1 : 0 ,
122+ duration : 200 ,
123+ useNativeDriver : true ,
124+ } ) ,
125+ Animated . timing ( fabTranslateY , {
126+ toValue : isCreateNoteButtonOffScreen ? 0 : FAB_ANIMATION_DISTANCE ,
127+ duration : 200 ,
128+ useNativeDriver : true ,
129+ } ) ,
130+ ] ) . start ( ) ;
131+ } , [ isCreateNoteButtonOffScreen ] ) ;
132+
133+ // Measure create note button position on mount and layout changes
134+ useEffect ( ( ) => {
135+ if ( createNoteButtonRef . current ) {
136+ createNoteButtonRef . current . measureInWindow ( ( x , y , width , height ) => {
137+ setCreateNoteButtonY ( y + height ) ;
138+ } ) ;
139+ }
140+ } , [ notes . length , subfolders . length ] ) ;
141+
142+ // Check if button is off screen based on scroll position
143+ useEffect ( ( ) => {
144+ const listenerId = scrollY . addListener ( ( { value } ) => {
145+ if ( createNoteButtonY > 0 ) {
146+ // Button is off screen when its bottom position is above the top of the screen
147+ // Add a small buffer (50px) to trigger slightly before it's completely gone
148+ const isOffScreen = value > createNoteButtonY - 100 ;
149+ if ( isOffScreen !== isCreateNoteButtonOffScreen ) {
150+ setIsCreateNoteButtonOffScreen ( isOffScreen ) ;
151+ }
152+ }
153+ } ) ;
154+
155+ return ( ) => {
156+ scrollY . removeListener ( listenerId ) ;
157+ } ;
158+ } , [ scrollY , createNoteButtonY , isCreateNoteButtonOffScreen ] ) ;
122159
123160 // Track last optimistic update to prevent immediate reload from overwriting it
124161 const lastOptimisticUpdateRef = useRef < number > ( 0 ) ;
@@ -620,6 +657,7 @@ export default function NotesList({ navigation, route, renderHeader, scrollY: pa
620657 onFilterPress = { ( ) => filterSortSheetRef . current ?. present ( ) }
621658 onCreateNotePress = { ( ) => navigation ?. navigate ( 'CreateNote' , { folderId : route ?. params ?. folderId } ) }
622659 onEmptyTrashPress = { handleEmptyTrash }
660+ createNoteButtonRef = { createNoteButtonRef }
623661 />
624662 </ >
625663 ) ;
@@ -722,7 +760,7 @@ export default function NotesList({ navigation, route, renderHeader, scrollY: pa
722760 onPress = { ( ) => navigation ?. navigate ( 'CreateNote' , { folderId : route ?. params ?. folderId } ) }
723761 android_ripple = { { color : 'rgba(255, 255, 255, 0.3)' , radius : 20 } }
724762 >
725- < Ionicons name = "add" size = { 20 } color = { theme . colors . primaryForeground } />
763+ < SquarePen size = { 20 } color = { theme . colors . primaryForeground } />
726764 </ Pressable >
727765 </ Animated . View >
728766 ) }
0 commit comments