Updated markdown and started basic lazy loading. also fixed some refresh and top blur issues among other things. #68
Conversation
…ollower/following lists - Refactored UserProfileScreen to use Animated.ScrollView for smoother scrolling. - Replaced ScrollView with FlatList for followers and following lists to improve performance and add loading states. - Introduced FadeInImage and LazyFadeIn components for better image loading experience. - Added useTopBlurScroll hook to manage scroll position for TopBlur component. - Updated TopBlur component to include animated opacity based on scroll position. - Improved user experience in MarkdownText and MediaGallery components with deferred rendering. - Enhanced ProjectCard and Post components with lazy loading and improved save/like functionality. - Optimized SavedContext and SavedStreamsContext for better state management during save actions.
There was a problem hiding this comment.
Pull request overview
This PR updates multiple frontend screens/components to support scroll-position–driven top blur, introduces deferred/lazy rendering helpers, improves media/image loading transitions, and refactors feeds (Bytes/Streams) toward paginated + infinite scrolling.
Changes:
- Added
useTopBlurScrolland integrated it across screens to driveTopBluropacity based on scroll position. - Introduced deferred/lazy rendering primitives (
useDeferredRender,LazyFadeIn,FadeInImage) and applied them to media-heavy components. - Refactored Bytes/Streams feeds to use
FlatListwith pagination/infinite loading and adjusted refresh control offsets/layout padding.
Reviewed changes
Copilot reviewed 34 out of 64 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/hooks/useTopBlurScroll.ts | New hook providing scrollY + onScroll for animated scroll tracking. |
| frontend/hooks/useDeferredRender.ts | New hook to defer rendering until after interactions (and optional delay). |
| frontend/contexts/SavedStreamsContext.tsx | Optimistic save/unfollow handling changes for streams. |
| frontend/contexts/SavedContext.tsx | Optimistic save/unsave handling changes for bytes/posts. |
| frontend/components/header.tsx | Updates header tagline and spacing. |
| frontend/components/UserCard.tsx | Switches avatars to FadeInImage. |
| frontend/components/User.tsx | Switches profile images to FadeInImage. |
| frontend/components/TopBlur.tsx | Makes top blur reactive to scroll position via animated opacity + gradient veil. |
| frontend/components/SectionHeader.tsx | Minor spacing tweaks. |
| frontend/components/ScrollView.tsx | Adjusts default content padding to vertical-only. |
| frontend/components/ProjectCard.tsx | Adds LazyFadeIn wrapper + tweaks optimistic like/save behavior. |
| frontend/components/Post.tsx | Adds lazy fade-in, image fade-in, preview truncation, and refines like/save optimism. |
| frontend/components/MediaGallery.tsx | Defers heavy media (WebView/video/image) rendering and adds fade-ins. |
| frontend/components/MarkdownText.tsx | Major markdown rendering enhancements (callouts, task lists, details blocks, images). |
| frontend/components/LazyFadeIn.tsx | New animated wrapper for fading content in when visible. |
| frontend/components/FadeInImage.tsx | New animated image component that fades in on load. |
| frontend/components/Collapsible.tsx | Enhances collapsible to support ReactNode titles and styling/default open. |
| frontend/app/user/[username].tsx | Adds scroll tracking + converts modal lists to FlatList. |
| frontend/app/streams.tsx | Converts to paginated Animated.FlatList with load-more and top blur scroll tracking. |
| frontend/app/stream/[projectId].tsx | Adds scroll tracking/top blur integration + inline markdown rendering for title/summary. |
| frontend/app/settings.tsx | Adds scroll tracking/top blur integration + avatar uses FadeInImage. |
| frontend/app/saved-streams.tsx | Adds scroll tracking/top blur integration + padding tweaks. |
| frontend/app/saved-library.tsx | Adds scroll tracking/top blur integration + padding tweaks. |
| frontend/app/post/[postId].tsx | Adds scroll tracking/top blur integration + padding tweaks. |
| frontend/app/notifications.tsx | Adds scroll tracking/top blur integration + padding tweaks. |
| frontend/app/manage-streams.tsx | Adds scroll tracking/top blur integration + supports editId deep-link + inline markdown. |
| frontend/app/create-stream.tsx | Adds scroll tracking/top blur integration. |
| frontend/app/create-byte.tsx | Adds scroll tracking/top blur integration. |
| frontend/app/bytes.tsx | Converts to paginated Animated.FlatList with load-more and top blur scroll tracking. |
| frontend/app/archive-bytes.tsx | Adds scroll tracking/top blur integration + padding tweaks. |
| frontend/app/(tabs)/profile.tsx | Adds scroll tracking/top blur integration + padding tweaks. |
| frontend/app/(tabs)/index.tsx | Adds scroll tracking/top blur integration + padding tweaks. |
| frontend/app/(tabs)/explore.tsx | Adds scroll tracking/top blur integration + uses FadeInImage for avatars. |
| frontend/app/(tabs)/_layout.tsx | Enables lazy loading in tab navigator. |
| backend/api/internal/database/dev.sqlite3-shm | Adds a local SQLite SHM artifact (should not be committed). |
| backend/uploads/e4a6d08bdb895a2518bbf088.heic | Adds uploaded media artifact. |
| backend/uploads/dcb1f9fd4ba4756fb91370f8.jpg | Adds uploaded media artifact. |
| backend/uploads/d90ff3877bb16a401db253c9.jpg | Adds uploaded media artifact. |
| backend/uploads/c870ac81e1e343d783626684.heic | Adds uploaded media artifact. |
| backend/uploads/c2aa90cc4f38e600d0b3afd4.jpg | Adds uploaded media artifact. |
| backend/uploads/c00c03c4cc9ebc466d322d39.m4a | Adds uploaded media artifact. |
| backend/uploads/ba7385d7b7d2b242943e3826.jpg | Adds uploaded media artifact. |
| backend/uploads/b035fdfcce117301b0fe0dc5.heic | Adds uploaded media artifact. |
| backend/uploads/9fc9957ad638d7d8a6118bba.heic | Adds uploaded media artifact. |
| backend/uploads/9d83afc74c7bacd66483004f.pdf | Adds uploaded media artifact. |
| backend/uploads/90f2a658c3d1bd7dc282aeb9.jpg | Adds uploaded media artifact. |
| backend/uploads/87a2056f6b58258e7ddf5be9.heic | Adds uploaded media artifact. |
| backend/uploads/82c8595f5df641cb0375e499.heic | Adds uploaded media artifact. |
| backend/uploads/6df6507f3d9edb87a3b17174.jpg | Adds uploaded media artifact. |
| backend/uploads/5c389cb9e0973a5a8f5d22c3.jpg | Adds uploaded media artifact. |
| backend/uploads/4effe678d837e81ac1c706db.mp4 | Adds uploaded media artifact. |
| backend/uploads/4a94a0cb50f3b387b4de4ef5.m4a | Adds uploaded media artifact. |
| backend/uploads/3962c4807f7f91ddae073b55.jpg | Adds uploaded media artifact. |
| backend/uploads/35d075cb5485c1a84d4fff10.jpg | Adds uploaded media artifact. |
| backend/uploads/1aba36ceb27965e859b99c66.jpg | Adds uploaded media artifact. |
| backend/uploads/0d0fdc6d5157b7829e33845e.heic | Adds uploaded media artifact. |
Comments suppressed due to low confidence (1)
frontend/app/user/[username].tsx:18
ScrollViewis imported but this screen now usesAnimated.ScrollView(and the modal lists were converted toFlatList), soScrollViewis unused. Please remove the unused import.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { useAutoRefresh } from "@/hooks/useAutoRefresh"; | ||
| import { useAppColors } from "@/hooks/useAppColors"; | ||
| import { useMotionConfig } from "@/hooks/useMotionConfig"; | ||
| import { useTopBlurScroll } from "@/hooks/useTopBlurScroll"; |
There was a problem hiding this comment.
After switching the main container to Animated.ScrollView, the ScrollView import at the top of this file is no longer used. Consider removing the unused import to avoid lint/TS unused-import failures.
| import { useAutoRefresh } from "@/hooks/useAutoRefresh"; | ||
| import { useAppColors } from "@/hooks/useAppColors"; | ||
| import { useMotionConfig } from "@/hooks/useMotionConfig"; | ||
| import { useTopBlurScroll } from "@/hooks/useTopBlurScroll"; |
There was a problem hiding this comment.
ScrollView is still imported in this file, but the implementation now uses Animated.ScrollView and doesn’t reference ScrollView. Removing the unused import will prevent lint warnings/errors.
| import { TopBlur } from "@/components/TopBlur"; | ||
| import { useAppColors } from "@/hooks/useAppColors"; | ||
| import { useMotionConfig } from "@/hooks/useMotionConfig"; | ||
| import { useTopBlurScroll } from "@/hooks/useTopBlurScroll"; | ||
| import { useNotifications } from "@/contexts/NotificationsContext"; |
There was a problem hiding this comment.
This screen now uses Animated.ScrollView, so the ScrollView import at the top of the file appears unused. Consider removing it to avoid unused-import lint/TS issues.
| behavior={Platform.OS === "ios" ? "padding" : undefined} | ||
| > | ||
| <TouchableWithoutFeedback onPress={Keyboard.dismiss}> | ||
| <ScrollView | ||
| <Animated.ScrollView | ||
| contentInsetAdjustmentBehavior="never" | ||
| onScroll={onScroll} | ||
| scrollEventThrottle={16} | ||
| refreshControl={ |
There was a problem hiding this comment.
The JSX has been updated to Animated.ScrollView, but ScrollView is still imported at the top of the file and appears unused. Consider removing the unused import to avoid lint/TS unused-import failures.
| const toggleSave = async (projectId: number) => { | ||
| const isAlreadySaved = savedProjectIds.includes(projectId); | ||
| const nextIds = isAlreadySaved | ||
| ? normalizeIds(savedProjectIds.filter((id) => id !== projectId)) | ||
| : normalizeIds([...savedProjectIds, projectId]); | ||
| setSavedProjectIds(nextIds); | ||
| if (user?.username) { | ||
| if (isAlreadySaved) { | ||
| await unfollowProject(user.username, projectId); | ||
| setSavedProjectIds((prev) => | ||
| normalizeIds(prev.filter((id) => id !== projectId)), | ||
| ); | ||
| } else { | ||
| await followProject(user.username, projectId); | ||
| setSavedProjectIds((prev) => normalizeIds([...prev, projectId])); | ||
| } | ||
|
|
||
| try { | ||
| const ids = await getProjectFollowing(user.username); | ||
| setSavedProjectIds(Array.isArray(ids) ? normalizeIds(ids) : []); | ||
| if (isAlreadySaved) { | ||
| await unfollowProject(user.username, projectId); | ||
| } else { | ||
| await followProject(user.username, projectId); | ||
| } | ||
| } catch { | ||
| // Keep optimistic state if refresh fails. | ||
| setSavedProjectIds(savedProjectIds); | ||
| } |
There was a problem hiding this comment.
toggleSave computes nextIds from the current render’s savedProjectIds and then calls setSavedProjectIds(nextIds). If toggleSave is triggered multiple times before state updates flush, updates can be lost (stale-closure bug). Consider using a functional setSavedProjectIds(prev => ...) to derive nextIds, and keep an explicit prevIds snapshot for rollback in the catch.
| import { | ||
| ActivityIndicator, | ||
| Animated, | ||
| Keyboard, | ||
| KeyboardAvoidingView, | ||
| Platform, | ||
| Pressable, | ||
| ScrollView, | ||
| StyleSheet, | ||
| TextInput, | ||
| TouchableWithoutFeedback, | ||
| View, | ||
| } from "react-native"; | ||
| import { Picker } from "@react-native-picker/picker"; | ||
| import { useRouter } from "expo-router"; | ||
| import { | ||
| SafeAreaView, | ||
| useSafeAreaInsets, | ||
| } from "react-native-safe-area-context"; | ||
| import { ApiProject } from "@/constants/Types"; | ||
| import { ThemedText } from "@/components/ThemedText"; | ||
| import { TopBlur } from "@/components/TopBlur"; | ||
| import { useBottomTabOverflow } from "@/components/ui/TabBarBackground"; | ||
| import { useAuth } from "@/contexts/AuthContext"; | ||
| import { useTopBlurScroll } from "@/hooks/useTopBlurScroll"; | ||
| import { | ||
| createPost, | ||
| getProjectsByBuilderId, | ||
| uploadMedia, | ||
| } from "@/services/api"; | ||
| import { useAppColors } from "@/hooks/useAppColors"; | ||
| import { useMotionConfig } from "@/hooks/useMotionConfig"; | ||
| import * as DocumentPicker from "expo-document-picker"; | ||
| import * as ImagePicker from "expo-image-picker"; | ||
| import { MediaGallery } from "@/components/MediaGallery"; | ||
|
|
||
| export default function CreateByteScreen() { | ||
| const colors = useAppColors(); | ||
| const router = useRouter(); | ||
| const insets = useSafeAreaInsets(); | ||
| const { user } = useAuth(); | ||
| const motion = useMotionConfig(); | ||
| const bottom = useBottomTabOverflow(); | ||
| const reveal = React.useRef(new Animated.Value(0)).current; | ||
| const scrollRef = useRef<ScrollView>(null); | ||
| const { scrollY, onScroll } = useTopBlurScroll(); | ||
| const [projects, setProjects] = useState<ApiProject[]>([]); |
There was a problem hiding this comment.
This screen now renders Animated.ScrollView, but scrollRef is typed as ScrollView and the ScrollView import is kept solely for typing. This can cause a ref type mismatch with Animated.ScrollView. Consider typing the ref as Animated.ScrollView (as in other screens) or using an explicit cast to the underlying scroll view type if you need scrollResponderScrollNativeHandleToKeyboard.
| import { useAutoRefresh } from "@/hooks/useAutoRefresh"; | ||
| import { useAppColors } from "@/hooks/useAppColors"; | ||
| import { useMotionConfig } from "@/hooks/useMotionConfig"; | ||
| import { useTopBlurScroll } from "@/hooks/useTopBlurScroll"; |
There was a problem hiding this comment.
ScrollView remains imported, but this screen now renders Animated.ScrollView and doesn’t reference ScrollView directly. Consider removing the unused import to keep the module clean and avoid lint failures.
| const toggleSave = async (postId: number) => { | ||
| const isAlreadySaved = savedPostIds.includes(postId); | ||
| const nextIds = isAlreadySaved | ||
| ? savedPostIds.filter((id) => id !== postId) | ||
| : [...savedPostIds, postId]; | ||
| setSavedPostIds(nextIds); | ||
| if (user?.username) { | ||
| if (isAlreadySaved) { | ||
| await unsavePost(user.username, postId); | ||
| setSavedPostIds((prev) => prev.filter((id) => id !== postId)); | ||
| } else { | ||
| await savePost(user.username, postId); | ||
| setSavedPostIds((prev) => [...prev, postId]); | ||
| try { | ||
| if (isAlreadySaved) { | ||
| await unsavePost(user.username, postId); | ||
| } else { | ||
| await savePost(user.username, postId); | ||
| } | ||
| } catch { | ||
| setSavedPostIds(savedPostIds); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| setSavedPostIds((prev) => { | ||
| const next = prev.includes(postId) | ||
| ? prev.filter((id) => id !== postId) | ||
| : [...prev, postId]; | ||
| SecureStore.setItemAsync(SAVED_KEY, JSON.stringify(next)); | ||
| return next; | ||
| }); | ||
| SecureStore.setItemAsync(SAVED_KEY, JSON.stringify(nextIds)); | ||
| }; |
There was a problem hiding this comment.
Same stale-closure issue as SavedStreamsContext: toggleSave derives nextIds from the current render’s savedPostIds and sets state directly. Rapid toggles can drop updates. Prefer setSavedPostIds(prev => ...) and snapshot prev for rollback instead of relying on the closed-over savedPostIds.
| export function useDeferredRender({ enabled = true, delayMs = 0 }: DeferredOptions = {}) { | ||
| const [ready, setReady] = useState(!enabled); | ||
|
|
||
| useEffect(() => { | ||
| if (!enabled) { | ||
| setReady(true); | ||
| return; | ||
| } | ||
|
|
||
| let cancelled = false; | ||
| let timeout: ReturnType<typeof setTimeout> | null = null; | ||
| const task = InteractionManager.runAfterInteractions(() => { | ||
| if (cancelled) { | ||
| return; | ||
| } | ||
| if (delayMs > 0) { | ||
| timeout = setTimeout(() => { | ||
| if (!cancelled) { | ||
| setReady(true); | ||
| } | ||
| }, delayMs); | ||
| return; | ||
| } | ||
| setReady(true); | ||
| }); | ||
|
|
||
| return () => { | ||
| cancelled = true; | ||
| if (timeout) { | ||
| clearTimeout(timeout); | ||
| } | ||
| task?.cancel?.(); | ||
| }; | ||
| }, [delayMs, enabled]); |
There was a problem hiding this comment.
useDeferredRender initializes ready based on the initial enabled, but if enabled changes from false -> true later, ready never resets back to false and the render won’t actually be deferred. If this hook is intended to support dynamic enabling, consider setting ready to false at the start of the effect when enabled becomes true (and possibly when delayMs changes).
| import { | ||
| ActivityIndicator, | ||
| Animated, | ||
| Pressable, | ||
| RefreshControl, | ||
| ScrollView, | ||
| StyleSheet, | ||
| Text, | ||
| View, | ||
| } from "react-native"; |
There was a problem hiding this comment.
ScrollView is imported but the JSX now uses Animated.ScrollView, so ScrollView appears unused. Please remove the unused import.
This pull request introduces several improvements to scrolling behavior, visual polish, and code consistency across multiple screens in the frontend application. The main focus is on integrating animated scroll tracking (
useTopBlurScroll) and enhancing theTopBlurcomponent to react to scroll position, as well as improving refresh controls and image handling. Additionally, theBytesScreenreceives significant enhancements for pagination and infinite scrolling.Scrolling and Visual Enhancements:
ScrollViewcomponents withAnimated.ScrollViewin the main tab screens (explore.tsx,index.tsx,profile.tsx,archive-bytes.tsx) to enable scroll position tracking and smoother UI updates. TheonScrollhandler andscrollEventThrottleare now set, and theTopBlurcomponent receives thescrollYprop to update its appearance based on scroll position. ([[1]](https://github.com/devbits-go/DevBits/pull/68/files#diff-f80c42f79eff77421265b809e7a1add394a01d586b966b628eface3792ec3b92L453-R466),[[2]](https://github.com/devbits-go/DevBits/pull/68/files#diff-f80c42f79eff77421265b809e7a1add394a01d586b966b628eface3792ec3b92L879-R888),[[3]](https://github.com/devbits-go/DevBits/pull/68/files#diff-883062dc4f2b03ea2a95c590cd8a90eae13dbf366d75ad00a1011350d9afcad9L262-R273),[[4]](https://github.com/devbits-go/DevBits/pull/68/files#diff-883062dc4f2b03ea2a95c590cd8a90eae13dbf366d75ad00a1011350d9afcad9L387-R394),[[5]](https://github.com/devbits-go/DevBits/pull/68/files#diff-652f5f77035d77471488bdc280fef65a71f0c1f27d33dfef299006b8c5daadd0L506-R517),[[6]](https://github.com/devbits-go/DevBits/pull/68/files#diff-652f5f77035d77471488bdc280fef65a71f0c1f27d33dfef299006b8c5daadd0L783-R790),[[7]](https://github.com/devbits-go/DevBits/pull/68/files#diff-a4f945d85befed82d20e28b5edb855830f66673029e1cfa448ec75a3ec00fdeaL153-R164),[[8]](https://github.com/devbits-go/DevBits/pull/68/files#diff-a4f945d85befed82d20e28b5edb855830f66673029e1cfa448ec75a3ec00fdeaL212-R219))useTopBlurScrollhook into all main screens to provide a consistent animated blur effect at the top of the screen. ([[1]](https://github.com/devbits-go/DevBits/pull/68/files#diff-f80c42f79eff77421265b809e7a1add394a01d586b966b628eface3792ec3b92R53),[[2]](https://github.com/devbits-go/DevBits/pull/68/files#diff-f80c42f79eff77421265b809e7a1add394a01d586b966b628eface3792ec3b92R94),[[3]](https://github.com/devbits-go/DevBits/pull/68/files#diff-883062dc4f2b03ea2a95c590cd8a90eae13dbf366d75ad00a1011350d9afcad9R38),[[4]](https://github.com/devbits-go/DevBits/pull/68/files#diff-883062dc4f2b03ea2a95c590cd8a90eae13dbf366d75ad00a1011350d9afcad9R63),[[5]](https://github.com/devbits-go/DevBits/pull/68/files#diff-652f5f77035d77471488bdc280fef65a71f0c1f27d33dfef299006b8c5daadd0R30),[[6]](https://github.com/devbits-go/DevBits/pull/68/files#diff-652f5f77035d77471488bdc280fef65a71f0c1f27d33dfef299006b8c5daadd0R100),[[7]](https://github.com/devbits-go/DevBits/pull/68/files#diff-a4f945d85befed82d20e28b5edb855830f66673029e1cfa448ec75a3ec00fdeaR20),[[8]](https://github.com/devbits-go/DevBits/pull/68/files#diff-a4f945d85befed82d20e28b5edb855830f66673029e1cfa448ec75a3ec00fdeaR41),[[9]](https://github.com/devbits-go/DevBits/pull/68/files#diff-e22d58903b7d9aa83e632ec96ba75c0a8cb590ac51ed0ee622e09256cdc81162R29),[[10]](https://github.com/devbits-go/DevBits/pull/68/files#diff-e22d58903b7d9aa83e632ec96ba75c0a8cb590ac51ed0ee622e09256cdc81162R42-R49))progressViewOffset) for better alignment with the top blur and safe area insets. ([[1]](https://github.com/devbits-go/DevBits/pull/68/files#diff-f80c42f79eff77421265b809e7a1add394a01d586b966b628eface3792ec3b92L453-R466),[[2]](https://github.com/devbits-go/DevBits/pull/68/files#diff-883062dc4f2b03ea2a95c590cd8a90eae13dbf366d75ad00a1011350d9afcad9L262-R273),[[3]](https://github.com/devbits-go/DevBits/pull/68/files#diff-652f5f77035d77471488bdc280fef65a71f0c1f27d33dfef299006b8c5daadd0L506-R517),[[4]](https://github.com/devbits-go/DevBits/pull/68/files#diff-a4f945d85befed82d20e28b5edb855830f66673029e1cfa448ec75a3ec00fdeaL153-R164))Image Handling Improvements:
Imagecomponent with a customFadeInImagecomponent in theExploreScreenfor a smoother image loading experience. ([[1]](https://github.com/devbits-go/DevBits/pull/68/files#diff-f80c42f79eff77421265b809e7a1add394a01d586b966b628eface3792ec3b92R27),[[2]](https://github.com/devbits-go/DevBits/pull/68/files#diff-f80c42f79eff77421265b809e7a1add394a01d586b966b628eface3792ec3b92L582-R588),[[3]](https://github.com/devbits-go/DevBits/pull/68/files#diff-f80c42f79eff77421265b809e7a1add394a01d586b966b628eface3792ec3b92L783-R789))Paging and Infinite Scrolling in BytesScreen:
BytesScreento use a paginated loading model with infinite scrolling support. Added state for pagination (isLoadingMore,hasMore,pageIndex), and updated theloadBytesfunction to fetch posts by page and append results. Also, improved following filter logic and reset state appropriately when filters change. ([[1]](https://github.com/devbits-go/DevBits/pull/68/files#diff-e22d58903b7d9aa83e632ec96ba75c0a8cb590ac51ed0ee622e09256cdc81162R42-R49),[[2]](https://github.com/devbits-go/DevBits/pull/68/files#diff-e22d58903b7d9aa83e632ec96ba75c0a8cb590ac51ed0ee622e09256cdc81162L58-R95),[[3]](https://github.com/devbits-go/DevBits/pull/68/files#diff-e22d58903b7d9aa83e632ec96ba75c0a8cb590ac51ed0ee622e09256cdc81162L89-R118),[[4]](https://github.com/devbits-go/DevBits/pull/68/files#diff-e22d58903b7d9aa83e632ec96ba75c0a8cb590ac51ed0ee622e09256cdc81162L104-R134))Layout and Style Adjustments:
[[1]](https://github.com/devbits-go/DevBits/pull/68/files#diff-f80c42f79eff77421265b809e7a1add394a01d586b966b628eface3792ec3b92L898-R905),[[2]](https://github.com/devbits-go/DevBits/pull/68/files#diff-883062dc4f2b03ea2a95c590cd8a90eae13dbf366d75ad00a1011350d9afcad9L407-R412),[[3]](https://github.com/devbits-go/DevBits/pull/68/files#diff-652f5f77035d77471488bdc280fef65a71f0c1f27d33dfef299006b8c5daadd0L923-R929),[[4]](https://github.com/devbits-go/DevBits/pull/68/files#diff-a4f945d85befed82d20e28b5edb855830f66673029e1cfa448ec75a3ec00fdeaL227-R233))[frontend/app/(tabs)/_layout.tsxR17](https://github.com/devbits-go/DevBits/pull/68/files#diff-6417007cb8e84ff2fcdb11d052a13bfb130fa9314ab9304139ef199babe34ff9R17))These changes collectively improve the user experience by providing smoother scrolling, better visual feedback, and more robust data loading patterns.