Merge pull request #4 from callmearta/tabs

[feat] tabs added
This commit is contained in:
Jorrin
2024-01-22 18:17:20 +01:00
committed by GitHub
33 changed files with 572 additions and 257 deletions

View File

@@ -1,3 +1,4 @@
{
"singleQuote": true
"singleQuote": true,
"endOfLine": "auto"
}

View File

@@ -6,6 +6,6 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

View File

@@ -20,6 +20,12 @@ module.exports = {
'react/react-in-jsx-scope': 'off',
'react/require-default-props': 'off',
'react/destructuring-assignment': 'off',
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
'react/jsx-filename-extension': [
'error',
{ extensions: ['.js', '.tsx', '.jsx'] },

View File

@@ -1,54 +1,102 @@
import FontAwesome from '@expo/vector-icons/FontAwesome';
import { Link, Tabs } from 'expo-router';
import { Pressable, useColorScheme } from 'react-native';
import { Tabs } from 'expo-router';
import Colors from '../../constants/Colors';
/**
* You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
*/
function TabBarIcon(props: {
name: React.ComponentProps<typeof FontAwesome>['name'];
color: string;
}) {
return <FontAwesome size={28} style={{ marginBottom: -3 }} {...props} />;
}
import TabBarIcon from '../../components/TabBarIcon';
import { globalStyles } from '../../styles/global';
export default function TabLayout() {
const colorScheme = useColorScheme();
return (
<Tabs
sceneContainerStyle={{
backgroundColor: '#000',
}}
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
headerShown: false,
tabBarActiveTintColor: Colors.dark.purple100,
tabBarStyle: {
backgroundColor: Colors.dark.shade700,
borderTopColor: 'transparent',
borderTopRightRadius: 20,
borderTopLeftRadius: 20,
height: 80,
},
tabBarItemStyle: {
paddingVertical: 18,
height: 82,
},
tabBarLabelStyle: [
{
marginTop: 2,
},
globalStyles.fOpenSansMedium,
],
}}
>
<Tabs.Screen
name="index"
options={{
title: 'Tab One',
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
headerRight: () => (
<Link href="/modal" asChild>
<Pressable>
{({ pressed }) => (
<FontAwesome
name="info-circle"
size={25}
color={Colors[colorScheme ?? 'light'].text}
style={{ marginRight: 15, opacity: pressed ? 0.5 : 1 }}
/>
)}
</Pressable>
</Link>
title: 'Home',
tabBarIcon: ({ focused }) => (
<TabBarIcon name="home" focused={focused} />
),
}}
/>
<Tabs.Screen
name="two"
name="about"
options={{
title: 'Tab Two',
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
title: 'About',
tabBarIcon: ({ focused }) => (
<TabBarIcon name="info-circle" focused={focused} />
),
}}
/>
<Tabs.Screen
name="search"
options={{
title: 'Search',
tabBarLabel: '',
tabBarShowLabel: false,
tabBarLabelStyle: {
display: 'none',
},
tabBarIconStyle: {},
tabBarIcon: () => (
<TabBarIcon
style={{
position: 'relative',
top: -1,
backgroundColor: Colors.dark.purple400,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
aspectRatio: 1,
borderRadius: 100,
textAlign: 'center',
textAlignVertical: 'center',
height: 56,
}}
name="search"
color="#FFF"
/>
),
}}
/>
<Tabs.Screen
name="settings"
options={{
title: 'Settings',
tabBarIcon: ({ focused }) => (
<TabBarIcon name="cog" focused={focused} />
),
}}
/>
<Tabs.Screen
name="account"
options={{
title: 'Account',
tabBarIcon: ({ focused }) => (
<TabBarIcon name="user" focused={focused} />
),
}}
/>
</Tabs>

View File

@@ -0,0 +1,36 @@
import { StyleSheet } from 'react-native';
import ScreenLayout from '../../components/layout/screenLayout';
import { globalStyles } from '../../styles/global';
import { RegularText } from '../../components/Styled';
export default function AboutScreen() {
return (
<ScreenLayout
title="About"
subtitle="What is movie-web and how content is served?"
>
<RegularText style={globalStyles.textWhite}>
No content is served from movie-web directly and movie web does not host
anything.
</RegularText>
</ScreenLayout>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});

View File

@@ -0,0 +1,18 @@
import { StyleSheet, Text } from 'react-native';
import { globalStyles } from '../../styles/global';
import ScreenLayout from '../../components/layout/screenLayout';
import { RegularText } from '../../components/Styled';
export default function AccountScreen() {
return (
<ScreenLayout
title="Account"
subtitle="Manage your movie web account from here"
>
<RegularText style={globalStyles.textWhite}>
Hey Bro! what are you up to?
</RegularText>
</ScreenLayout>
);
}

View File

@@ -1,35 +1,13 @@
import { StyleSheet } from 'react-native';
import { RegularText } from '../../components/Styled';
import ScreenLayout from '../../components/layout/screenLayout';
import { globalStyles } from '../../styles/global';
import EditScreenInfo from '../../components/EditScreenInfo';
import { Text, View } from '../../components/Themed';
export default function TabOneScreen() {
export default function HomeScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Tab One</Text>
<View
style={styles.separator}
lightColor="#eee"
darkColor="rgba(255,255,255,0.1)"
/>
<EditScreenInfo path="app/(tabs)/index.tsx" />
</View>
<ScreenLayout title="Home" subtitle="This is where all magic happens">
<RegularText style={globalStyles.textWhite}>
Movies will be listed here
</RegularText>
</ScreenLayout>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});

View File

@@ -0,0 +1,64 @@
import { FontAwesome5 } from '@expo/vector-icons';
import { useCallback, useRef, useState } from 'react';
import { View } from 'react-native';
import { TextInput } from 'react-native-gesture-handler';
import { globalStyles } from '../../../styles/global';
import Colors from '../../../constants/Colors';
import { useFocusEffect } from 'expo-router';
export default function Searchbar() {
const [keyword, setKeyword] = useState('');
const inputRef = useRef<TextInput>(null);
useFocusEffect(
useCallback(() => {
// When the screen is focused
const focus = () => {
setTimeout(() => {
inputRef?.current?.focus();
}, 20);
};
focus();
return focus; // cleanup
}, []),
);
return (
<View
style={{
...globalStyles.flexRow,
...globalStyles.itemsCenter,
...globalStyles.border,
...globalStyles.roundedFull,
marginTop: 14,
marginBottom: 24,
}}
>
<View
style={{
...globalStyles.justifyCenter,
...globalStyles.itemsCenter,
width: 48,
marginLeft: 4,
}}
>
<FontAwesome5 name="search" size={18} color={Colors.dark.shade200} />
</View>
<TextInput
value={keyword}
autoFocus={true}
onChangeText={(text) => setKeyword(text)}
ref={inputRef}
placeholder="What are you looking for?"
placeholderTextColor={Colors.dark.shade200}
style={[
globalStyles.input,
globalStyles.fOpenSansRegular,
{
width: '100%',
},
]}
></TextInput>
</View>
);
}

View File

@@ -0,0 +1,41 @@
import { Dimensions, ScrollView, View } from 'react-native';
import { globalStyles } from '../../../styles/global';
import ScreenLayout from '../../../components/layout/screenLayout';
import { TextInput } from 'react-native-gesture-handler';
import styles from './styles';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useFocusEffect } from 'expo-router';
import { BoldText } from '../../../components/Styled';
import Item from '../../../components/item/item';
import Searchbar from './Searchbar';
export default function SearchScreen() {
return (
<ScrollView>
<ScreenLayout
title={
<View
style={{ ...globalStyles.flexRow, ...globalStyles.itemsCenter }}
>
<BoldText style={globalStyles.sectionTitle}>Search</BoldText>
</View>
}
subtitle="Looking for something?"
>
<Searchbar />
<View style={styles.items}>
<View style={styles.itemOuter}>
<Item />
</View>
<View style={styles.itemOuter}>
<Item />
</View>
<View style={styles.itemOuter}>
<Item />
</View>
</View>
</ScreenLayout>
</ScrollView>
);
}

View File

@@ -0,0 +1,19 @@
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
items: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'flex-start',
width: '100%',
flexDirection: 'row',
flex: 1,
},
itemOuter: {
flexBasis: '50%',
paddingHorizontal: 12,
paddingBottom: 12,
},
});
export default styles;

View File

@@ -0,0 +1,14 @@
import { RegularText } from '../../components/Styled';
import ScreenLayout from '../../components/layout/screenLayout';
import { globalStyles } from '../../styles/global';
import { StyleSheet, Text } from 'react-native';
export default function SettingsScreen() {
return (
<ScreenLayout title="Settings" subtitle="Need to change something?">
<RegularText style={globalStyles.textWhite}>
Settings would be listed in here. Coming soon
</RegularText>
</ScreenLayout>
);
}

View File

@@ -1,35 +0,0 @@
import { StyleSheet } from 'react-native';
import EditScreenInfo from '../../components/EditScreenInfo';
import { Text, View } from '../../components/Themed';
export default function TabTwoScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Tab Two</Text>
<View
style={styles.separator}
lightColor="#eee"
darkColor="rgba(255,255,255,0.1)"
/>
<EditScreenInfo path="app/(tabs)/two.tsx" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});

View File

@@ -1,17 +1,18 @@
import { Link, Stack } from 'expo-router';
import { StyleSheet } from 'react-native';
import { Text, View } from '../components/Themed';
import { StyleSheet, View } from 'react-native';
import { BoldText, RegularText } from '../components/Styled';
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: 'Oops!' }} />
<View style={styles.container}>
<Text style={styles.title}>This screen doesn&apos;t exist.</Text>
<BoldText style={styles.title}>
This screen doesn&apos;t exist.
</BoldText>
<Link href="/" style={styles.link}>
<Text style={styles.linkText}>Go to home screen!</Text>
<RegularText style={styles.linkText}>Go to home screen!</RegularText>
</Link>
</View>
</>

View File

@@ -9,6 +9,8 @@ import { SplashScreen, Stack } from 'expo-router';
import { useEffect } from 'react';
import { useColorScheme } from 'react-native';
import Colors from '../constants/Colors';
export {
// Catch any errors thrown by the Layout component.
ErrorBoundary,
@@ -26,7 +28,12 @@ SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const [loaded, error] = useFonts({
// eslint-disable-next-line global-require
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
OpenSansRegular: require('../assets/fonts/OpenSans-Regular.ttf'),
OpenSansLight: require('../assets/fonts/OpenSans-Light.ttf'),
OpenSansMedium: require('../assets/fonts/OpenSans-Medium.ttf'),
OpenSansBold: require('../assets/fonts/OpenSans-Bold.ttf'),
OpenSansSemiBold: require('../assets/fonts/OpenSans-SemiBold.ttf'),
OpenSansExtra: require('../assets/fonts/OpenSans-ExtraBold.ttf'),
...FontAwesome.font,
});
@@ -53,7 +60,15 @@ function RootLayoutNav() {
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack
screenOptions={{
gestureEnabled: true,
headerShown: false,
contentStyle: {
backgroundColor: Colors.dark.shade900,
},
}}
>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
</Stack>

View File

@@ -1,19 +1,13 @@
import { StatusBar } from 'expo-status-bar';
import { Platform, StyleSheet } from 'react-native';
import EditScreenInfo from '../components/EditScreenInfo';
import { Text, View } from '../components/Themed';
import { Platform, StyleSheet, View } from 'react-native';
import { BoldText, RegularText } from '../components/Styled';
export default function ModalScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Modal</Text>
<View
style={styles.separator}
lightColor="#eee"
darkColor="rgba(255,255,255,0.1)"
/>
<EditScreenInfo path="app/modal.tsx" />
<BoldText style={styles.title}>Modal</BoldText>
<View style={styles.separator} />
<RegularText>Modal?!</RegularText>
{/* Use a light status bar on iOS to account for the black space above the modal */}
<StatusBar style={Platform.OS === 'ios' ? 'light' : 'auto'} />

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,82 +0,0 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { ExternalLink } from './ExternalLink';
import { MonoText } from './StyledText';
import { Text, View } from './Themed';
import Colors from '../constants/Colors';
export default function EditScreenInfo({ path }: { path: string }) {
return (
<View>
<View style={styles.getStartedContainer}>
<Text
style={styles.getStartedText}
lightColor="rgba(0,0,0,0.8)"
darkColor="rgba(255,255,255,0.8)"
>
Open up the code for this screen:
</Text>
<View
style={[styles.codeHighlightContainer, styles.homeScreenFilename]}
darkColor="rgba(255,255,255,0.05)"
lightColor="rgba(0,0,0,0.05)"
>
<MonoText>{path}</MonoText>
</View>
<Text
style={styles.getStartedText}
lightColor="rgba(0,0,0,0.8)"
darkColor="rgba(255,255,255,0.8)"
>
Change any of the text, save the file, and your app will automatically
update.
</Text>
</View>
<View style={styles.helpContainer}>
<ExternalLink
style={styles.helpLink}
href="https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet"
>
<Text style={styles.helpLinkText} lightColor={Colors.light.tint}>
Tap here if your app doesn&apos;t automatically update after making
changes
</Text>
</ExternalLink>
</View>
</View>
);
}
const styles = StyleSheet.create({
getStartedContainer: {
alignItems: 'center',
marginHorizontal: 50,
},
homeScreenFilename: {
marginVertical: 7,
},
codeHighlightContainer: {
borderRadius: 3,
paddingHorizontal: 4,
},
getStartedText: {
fontSize: 17,
lineHeight: 24,
textAlign: 'center',
},
helpContainer: {
marginTop: 15,
marginHorizontal: 20,
alignItems: 'center',
},
helpLink: {
paddingVertical: 15,
},
helpLinkText: {
textAlign: 'center',
},
});

View File

@@ -0,0 +1,44 @@
import { Text } from 'react-native';
export const RegularText = ({ style, children, ...rest }: Text['props']) => {
return (
<Text style={[{ fontFamily: 'OpenSansRegular' }, style]} {...rest}>
{children}
</Text>
);
};
export const BoldText = ({ style, children, ...rest }: Text['props']) => {
return (
<Text style={[{ fontFamily: 'OpenSansBold' }, style]} {...rest}>
{children}
</Text>
);
};
export const SemiBoldText = ({ style, children, ...rest }: Text['props']) => {
return (
<Text style={[{ fontFamily: 'OpenSansSemiBold' }, style]} {...rest}>
{children}
</Text>
);
};
export const MediumText = ({ style, children, ...rest }: Text['props']) => {
return (
<Text style={[{ fontFamily: 'OpenSansMedium' }, style]} {...rest}>
{children}
</Text>
);
};
export const ExtraBoldText = ({ style, children, ...rest }: Text['props']) => {
return (
<Text style={[{ fontFamily: 'OpenSansExtraBold' }, style]} {...rest}>
{children}
</Text>
);
};
export const LightText = ({ style, children, ...rest }: Text['props']) => {
return (
<Text style={[{ fontFamily: 'OpenSansLight' }, style]} {...rest}>
{children}
</Text>
);
};

View File

@@ -1,5 +0,0 @@
import { Text, TextProps } from './Themed';
export function MonoText(props: TextProps) {
return <Text {...props} style={[props.style, { fontFamily: 'SpaceMono' }]} />;
}

View File

@@ -0,0 +1,21 @@
import { FontAwesome } from '@expo/vector-icons';
import Colors from '../constants/Colors';
type Props = {
focused?: boolean;
color?: string;
} & React.ComponentProps<typeof FontAwesome>;
export default function TabBarIcon({
color = Colors.dark.shade300,
focused,
...rest
}: Props) {
return (
<FontAwesome
color={color || (focused ? Colors.dark.purple300 : Colors.dark.shade300)}
size={24}
{...rest}
/>
);
}

View File

@@ -1,50 +0,0 @@
/**
* Learn more about Light and Dark modes:
* https://docs.expo.io/guides/color-schemes/
*/
import {
Text as DefaultText,
View as DefaultView,
useColorScheme,
} from 'react-native';
import Colors from '../constants/Colors';
type ThemeProps = {
lightColor?: string;
darkColor?: string;
};
export type TextProps = ThemeProps & DefaultText['props'];
export type ViewProps = ThemeProps & DefaultView['props'];
export function useThemeColor(
props: { light?: string; dark?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark,
) {
const theme = useColorScheme() ?? 'light';
const colorFromProps = props[theme];
if (colorFromProps) {
return colorFromProps;
}
return Colors[theme][colorName];
}
export function Text(props: TextProps) {
const { style, lightColor, darkColor, ...otherProps } = props;
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
return <DefaultText style={[{ color }, style]} {...otherProps} />;
}
export function View(props: ViewProps) {
const { style, lightColor, darkColor, ...otherProps } = props;
const backgroundColor = useThemeColor(
{ light: lightColor, dark: darkColor },
'background',
);
return <DefaultView style={[{ backgroundColor }, style]} {...otherProps} />;
}

View File

@@ -0,0 +1,31 @@
import { globalStyles } from '../../styles/global';
import { Image, Text, View } from 'react-native';
import styles from './styles';
import { BoldText, RegularText } from '../Styled';
import { TMDB_POSTER_PATH } from '../../constants/General';
export default function Item() {
return (
<View style={styles.wrapper}>
<View style={styles.imageWrapper}>
<Image
source={{
uri: `${TMDB_POSTER_PATH}/w342//gdIrmf2DdY5mgN6ycVP0XlzKzbE.jpg`,
width: 200,
}}
style={styles.image}
/>
</View>
<BoldText style={globalStyles.textWhite}>Hamilton</BoldText>
<View style={styles.meta}>
<RegularText style={[globalStyles.textMuted, styles.smallText]}>
Movie
</RegularText>
<View style={[globalStyles.dotSeperator]}></View>
<RegularText style={[globalStyles.textMuted, styles.smallText]}>
2023
</RegularText>
</View>
</View>
);
}

View File

@@ -0,0 +1,31 @@
import Colors from '../../constants/Colors';
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
wrapper: {
width: '100%',
},
imageWrapper: {
borderRadius: 16,
aspectRatio: 9 / 14,
width: '100%',
overflow: 'hidden',
position: 'relative',
marginBottom: 6,
},
image: {
width: '100%',
height: '100%',
resizeMode: 'cover',
},
meta: {
flexDirection: 'row',
gap: 6,
alignItems: 'center',
},
smallText: {
fontSize: 12,
},
});
export default styles;

View File

@@ -0,0 +1,31 @@
import { View } from 'react-native';
import { globalStyles } from '../../styles/global';
import { styles } from './styles';
import { BoldText, RegularText } from '../Styled';
type Props = {
title?: React.ReactNode | string;
subtitle?: string;
children?: React.ReactNode;
};
export default function ScreenLayout({ title, subtitle, children }: Props) {
return (
<View
style={[
globalStyles.pageContainer,
globalStyles.container,
styles.container,
]}
>
{typeof title === 'string' && (
<BoldText style={globalStyles.sectionTitle}>{title}</BoldText>
)}
{typeof title !== 'string' && title}
<RegularText style={[{ marginTop: 4 }, globalStyles.sectionSubtitle]}>
{subtitle}
</RegularText>
<View style={styles.children}>{children}</View>
</View>
);
}

View File

@@ -0,0 +1,10 @@
import { Dimensions, StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
container: {
minHeight: Dimensions.get('window').height,
},
children: {
paddingVertical: 12,
},
});

View File

@@ -15,5 +15,13 @@ export default {
tint: tintColorDark,
tabIconDefault: '#ccc',
tabIconSelected: tintColorDark,
purple100: '#C082FF',
purple300: '#8D44D6',
purple400: '#7831BF',
shade50: '#676790',
shade200: '#3F3F60',
shade300: '#32324F',
shade700: '#131322',
shade900: '#0A0A12',
},
};

View File

@@ -0,0 +1 @@
export const TMDB_POSTER_PATH = `https://image.tmdb.org/t/p`;

View File

@@ -0,0 +1,75 @@
import { StyleSheet } from 'react-native';
import Colors from '../constants/Colors';
export const globalStyles = StyleSheet.create({
pageContainer: {
flex: 1,
backgroundColor: Colors.dark.shade900,
paddingVertical: 48,
},
container: {
padding: 24,
},
sectionTitle: {
color: '#FFF',
letterSpacing: -1.5,
fontWeight: 'bold',
fontSize: 28,
},
sectionSubtitle: {
color: Colors.dark.shade200,
fontSize: 14,
},
textWhite: {
color: '#FFF',
},
flexRow: {
flexDirection: 'row',
},
itemsCenter: {
alignItems: 'center',
},
justifyCenter: {
justifyContent: 'center',
},
input: {
borderRadius: 24,
paddingRight: 18,
paddingVertical: 12,
color: '#FFF',
},
border: {
borderWidth: 1,
borderColor: 'rgba(255,255,255,.1)',
},
roundedFull: {
borderRadius: 34,
},
fOpenSansLight: {
fontFamily: 'OpenSansLight',
},
fOpenSansBold: {
fontFamily: 'OpenSansBold',
},
fOpenSansSemiBold: {
fontFamily: 'OpenSansSemiBold',
},
fOpenSansMedium: {
fontFamily: 'OpenSansMedium',
},
fOpenSansExtraBold: {
fontFamily: 'OpenSansExtraBold',
},
fOpenSansRegular: {
fontFamily: 'OpenSansRegular',
},
textMuted: {
color: '#5F5F7A',
},
dotSeperator: {
width: 4,
height: 4,
backgroundColor: '#5F5F7A',
borderRadius: 50,
},
});