Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 68 additions & 58 deletions src/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Tabs } from 'expo-router/js-tabs';
import { useColorScheme } from 'nativewind';
import * as React from 'react';

import { colors } from '@/components/ui';
import { SafeAreaView, colors } from '@/components/ui';
import {
Users as FollowingIcon,
Home as HomeIcon,
Expand Down Expand Up @@ -38,64 +38,74 @@ export default function TabLayout(): React.ReactElement {
return () => clearTimeout(timer);
}, []);

// Single owner of the tab area's system-chrome insets: this wrapper consumes
// all four edges, the tab bar gets an explicit zero bottom inset below, and
// the tab screens are plain views. Any change here (e.g. a floating tab bar
// over scrolling content) only edits this file — and a regression shows up
// as obvious layout breakage, not a silent shift inside library defaults.
const shellBg = isDark ? 'bg-neutral-900' : 'bg-white';

return (
<Tabs
screenOptions={{
// No per-screen header — each tab manages its own top safe area, and
// the bottom tab bar is enough navigation.
headerShown: false,
tabBarActiveTintColor: colors.primary[500],
tabBarInactiveTintColor: isDark ? colors.neutral[400] : colors.neutral[500],
tabBarStyle: {
backgroundColor: isDark ? colors.neutral[900] : colors.white,
borderTopColor: isDark ? colors.neutral[800] : colors.neutral[200],
},
}}
>
<Tabs.Screen
name="index"
options={{
title: t('tabs.home'),
tabBarIcon: ({ color }) => <HomeIcon color={color} />,
tabBarButtonTestID: 'home-tab',
}}
/>
<Tabs.Screen
name="search"
options={{
title: t('tabs.search'),
tabBarIcon: ({ color }) => <SearchIcon color={color} />,
tabBarButtonTestID: 'search-tab',
// Hide from the tab bar if the user disabled it in Settings.
href: tabs.search ? undefined : null,
}}
/>
<Tabs.Screen
name="following"
options={{
title: t('tabs.following'),
tabBarIcon: ({ color }) => <FollowingIcon color={color} />,
tabBarButtonTestID: 'following-tab',
href: tabs.following ? undefined : null,
}}
/>
<Tabs.Screen
name="library"
options={{
title: t('tabs.library'),
tabBarIcon: ({ color }) => <LibraryIcon color={color} />,
tabBarButtonTestID: 'library-tab',
href: tabs.library ? undefined : null,
}}
/>
<Tabs.Screen
name="settings"
options={{
title: t('tabs.settings'),
tabBarIcon: ({ color }) => <SettingsIcon color={color} />,
tabBarButtonTestID: 'settings-tab',
<SafeAreaView className={`flex-1 ${shellBg}`}>
<Tabs
safeAreaInsets={{ bottom: 0 }}
screenOptions={{
// No per-screen header — each tab manages its own top safe area, and
// the bottom tab bar is enough navigation.
headerShown: false,
tabBarActiveTintColor: colors.primary[500],
tabBarInactiveTintColor: isDark ? colors.neutral[400] : colors.neutral[500],
tabBarStyle: {
backgroundColor: isDark ? colors.neutral[900] : colors.white,
borderTopColor: isDark ? colors.neutral[800] : colors.neutral[200],
},
}}
/>
</Tabs>
>
<Tabs.Screen
name="index"
options={{
title: t('tabs.home'),
tabBarIcon: ({ color }) => <HomeIcon color={color} />,
tabBarButtonTestID: 'home-tab',
}}
/>
<Tabs.Screen
name="search"
options={{
title: t('tabs.search'),
tabBarIcon: ({ color }) => <SearchIcon color={color} />,
tabBarButtonTestID: 'search-tab',
// Hide from the tab bar if the user disabled it in Settings.
href: tabs.search ? undefined : null,
}}
/>
<Tabs.Screen
name="following"
options={{
title: t('tabs.following'),
tabBarIcon: ({ color }) => <FollowingIcon color={color} />,
tabBarButtonTestID: 'following-tab',
href: tabs.following ? undefined : null,
}}
/>
<Tabs.Screen
name="library"
options={{
title: t('tabs.library'),
tabBarIcon: ({ color }) => <LibraryIcon color={color} />,
tabBarButtonTestID: 'library-tab',
href: tabs.library ? undefined : null,
}}
/>
<Tabs.Screen
name="settings"
options={{
title: t('tabs.settings'),
tabBarIcon: ({ color }) => <SettingsIcon color={color} />,
tabBarButtonTestID: 'settings-tab',
}}
/>
</Tabs>
</SafeAreaView>
);
}
10 changes: 7 additions & 3 deletions src/app/(app)/following.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ import { Link } from 'expo-router';
import type * as React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';

import { FocusAwareStatusBar, SafeAreaView } from '@/components/ui';
import { FocusAwareStatusBar } from '@/components/ui';
import { useFollowingStore } from '@/lib/stores/following-store';

export default function FollowingScreen(): React.ReactElement {
const { following, unfollow } = useFollowingStore();

return (
<SafeAreaView className="flex-1 bg-white dark:bg-neutral-900">
<View
// The tab bar below already sits above the system navigation bar; also
// applying the bottom safe-area edge here double-pads the content.
className="flex-1 bg-white dark:bg-neutral-900"
>
<FocusAwareStatusBar />
{following.length === 0 ? (
<View className="flex-1 items-center justify-center py-20">
Expand Down Expand Up @@ -48,6 +52,6 @@ export default function FollowingScreen(): React.ReactElement {
keyExtractor={(item) => item.slug}
/>
)}
</SafeAreaView>
</View>
);
}
10 changes: 5 additions & 5 deletions src/app/(app)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as React from 'react';
import { ActivityIndicator, TouchableOpacity, View } from 'react-native';

import { useVideos } from '@/api/video-queries';
import { SafeAreaView, Text } from '@/components/ui';
import { Text } from '@/components/ui';
import { VideoTile } from '@/components/video-tile';
import { flattenUniquePages } from '@/lib/flatten-pages';
import { useColumns } from '@/lib/hooks/use-columns';
Expand Down Expand Up @@ -46,20 +46,20 @@ export default function Home(): React.ReactElement {

if (isError && videos.length === 0) {
return (
<SafeAreaView className="flex-1 items-center justify-center bg-white dark:bg-neutral-900">
<View className="flex-1 items-center justify-center bg-white dark:bg-neutral-900">
<Text className="text-lg text-neutral-900 dark:text-neutral-100">Error loading videos</Text>
<TouchableOpacity
onPress={() => refetch()}
className="mt-4 rounded-lg bg-primary-500 px-4 py-2"
>
<Text className="font-semibold text-white">Retry</Text>
</TouchableOpacity>
</SafeAreaView>
</View>
);
}

return (
<SafeAreaView className="flex-1 bg-white dark:bg-neutral-900">
<View className="flex-1 bg-white dark:bg-neutral-900">
<FlashList
key={numColumns}
data={videos}
Expand All @@ -86,6 +86,6 @@ export default function Home(): React.ReactElement {
refreshing={isRefetching}
onRefresh={refetch}
/>
</SafeAreaView>
</View>
);
}
10 changes: 7 additions & 3 deletions src/app/(app)/library.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as React from 'react';
import { Alert, ScrollView, Text, TouchableOpacity, View, useWindowDimensions } from 'react-native';
import { Swipeable } from 'react-native-gesture-handler';

import { FocusAwareStatusBar, SafeAreaView } from '@/components/ui';
import { FocusAwareStatusBar } from '@/components/ui';
import { Trash } from '@/components/ui/icons';
import { VideoTile } from '@/components/video-tile';
import type { DownloadMetadata } from '@/lib/download';
Expand Down Expand Up @@ -372,7 +372,11 @@ export default function Library(): React.ReactElement {
};

return (
<SafeAreaView className="bg-white dark:bg-neutral-900 flex-1">
<View
// The tab bar below already sits above the system navigation bar; also
// applying the bottom safe-area edge here double-pads the content.
className="bg-white dark:bg-neutral-900 flex-1"
>
<FocusAwareStatusBar />
<View className="border-neutral-200 dark:border-neutral-800 flex-row border-b">
{TABS.map((tab, index) => (
Expand Down Expand Up @@ -404,6 +408,6 @@ export default function Library(): React.ReactElement {
</View>
))}
</ScrollView>
</SafeAreaView>
</View>
);
}
10 changes: 7 additions & 3 deletions src/app/(app)/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as React from 'react';
import { ActivityIndicator, ScrollView, TextInput, TouchableOpacity, View } from 'react-native';

import { useSearch } from '@/api/search';
import { FocusAwareStatusBar, SafeAreaView, Text } from '@/components/ui';
import { FocusAwareStatusBar, Text } from '@/components/ui';
import { VideoTile } from '@/components/video-tile';
import { useTranslate } from '@/lib';
import { flattenUniquePages } from '@/lib/flatten-pages';
Expand Down Expand Up @@ -95,7 +95,11 @@ export default function SearchScreen() {
const showResults = submittedQuery.length > 0;

return (
<SafeAreaView className="bg-white dark:bg-neutral-900 flex-1">
<View
// The tab bar below already sits above the system navigation bar; also
// applying the bottom safe-area edge here double-pads the content.
className="bg-white dark:bg-neutral-900 flex-1"
>
<FocusAwareStatusBar />
<View className="border-neutral-200 dark:border-neutral-800 px-4 py-3">
<TextInput
Expand Down Expand Up @@ -195,6 +199,6 @@ export default function SearchScreen() {
</View>
</ScrollView>
)}
</SafeAreaView>
</View>
);
}
6 changes: 3 additions & 3 deletions src/app/(app)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { AppIconItem } from '@/components/settings/app-icon-item';
import { ItemsContainer } from '@/components/settings/items-container';
import { LanguageItem } from '@/components/settings/language-item';
import { ThemeItem } from '@/components/settings/theme-item';
import { FocusAwareStatusBar, SafeAreaView, ScrollView, Text, View, colors } from '@/components/ui';
import { FocusAwareStatusBar, ScrollView, Text, View, colors } from '@/components/ui';
import { Trash } from '@/components/ui/icons';
import { UpdateDialog } from '@/components/update-dialog';
import { LOCK_TIMEOUT_OPTIONS, useSecuritySettings } from '@/lib/hooks/use-security-settings';
Expand Down Expand Up @@ -141,7 +141,7 @@ export default function Settings() {
return (
<>
<FocusAwareStatusBar />
<SafeAreaView className="flex-1 bg-white dark:bg-neutral-900">
<View className="flex-1 bg-white dark:bg-neutral-900">
<ScrollView className="flex-1">
<View className="flex-1 px-4 py-4">
{/* Preferences */}
Expand Down Expand Up @@ -367,7 +367,7 @@ export default function Settings() {
<View className="h-8" />
</View>
</ScrollView>
</SafeAreaView>
</View>

<UpdateDialog
release={pendingRelease}
Expand Down