diff options
Diffstat (limited to 'src')
54 files changed, 925 insertions, 242 deletions
diff --git a/src/App.tsx b/src/App.tsx index 5abf1ff4..2e6865fd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,14 +1,14 @@ import React from 'react'; import {NavigationContainer} from '@react-navigation/native'; -import {Routes, AuthContextProvider} from './routes'; +import Routes, {AuthProvider} from './routes'; const App = () => { return ( - <AuthContextProvider> + <AuthProvider> <NavigationContainer> <Routes /> </NavigationContainer> - </AuthContextProvider> + </AuthProvider> ); }; diff --git a/src/assets/images/avatar-placeholder.png b/src/assets/images/avatar-placeholder.png Binary files differnew file mode 100644 index 00000000..313f384e --- /dev/null +++ b/src/assets/images/avatar-placeholder.png diff --git a/src/assets/images/avatar-placeholder@2x.png b/src/assets/images/avatar-placeholder@2x.png Binary files differnew file mode 100644 index 00000000..d038441e --- /dev/null +++ b/src/assets/images/avatar-placeholder@2x.png diff --git a/src/assets/images/avatar-placeholder@3x.png b/src/assets/images/avatar-placeholder@3x.png Binary files differnew file mode 100644 index 00000000..814472ec --- /dev/null +++ b/src/assets/images/avatar-placeholder@3x.png diff --git a/src/assets/images/cover-placeholder.png b/src/assets/images/cover-placeholder.png Binary files differnew file mode 100644 index 00000000..e27eb9bd --- /dev/null +++ b/src/assets/images/cover-placeholder.png diff --git a/src/assets/images/cover-placeholder@2x.png b/src/assets/images/cover-placeholder@2x.png Binary files differnew file mode 100644 index 00000000..4aafe9d4 --- /dev/null +++ b/src/assets/images/cover-placeholder@2x.png diff --git a/src/assets/images/cover-placeholder@3x.png b/src/assets/images/cover-placeholder@3x.png Binary files differnew file mode 100644 index 00000000..991f5d63 --- /dev/null +++ b/src/assets/images/cover-placeholder@3x.png diff --git a/src/components/common/GradientBackground.tsx b/src/components/common/GradientBackground.tsx index f363bd61..c1247ca2 100644 --- a/src/components/common/GradientBackground.tsx +++ b/src/components/common/GradientBackground.tsx @@ -5,20 +5,19 @@ import { TouchableWithoutFeedback, Keyboard, ViewProps, - SafeAreaView, } from 'react-native'; interface GradientBackgroundProps extends ViewProps {} const GradientBackground: React.FC<GradientBackgroundProps> = (props) => { return ( - <LinearGradient - locations={[0.89, 1]} - colors={['transparent', 'rgba(0, 0, 0, 0.6)']} - style={styles.container}> - <TouchableWithoutFeedback accessible={false} onPress={Keyboard.dismiss}> - <SafeAreaView {...props}>{props.children}</SafeAreaView> - </TouchableWithoutFeedback> - </LinearGradient> + <TouchableWithoutFeedback accessible={false} onPress={Keyboard.dismiss}> + <LinearGradient + locations={[0.89, 1]} + colors={['transparent', 'rgba(0, 0, 0, 0.6)']} + style={styles.container}> + {props.children} + </LinearGradient> + </TouchableWithoutFeedback> ); }; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index a1bcc558..826675ff 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -4,3 +4,4 @@ export {default as RadioCheckbox} from './RadioCheckbox'; export {default as TaggInput} from './TaggInput'; export {default as NavigationIcon} from './NavigationIcon'; export {default as GradientBackground} from './GradientBackground'; +export {default as Post} from './post'; diff --git a/src/components/common/post/Post.tsx b/src/components/common/post/Post.tsx new file mode 100644 index 00000000..d6c5a7d6 --- /dev/null +++ b/src/components/common/post/Post.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import {StyleSheet, View} from 'react-native'; +import {PostType} from '../../../types'; +import PostHeader from './PostHeader'; +import {SCREEN_WIDTH} from '../../../utils'; + +interface PostProps { + post: PostType; +} +const Post: React.FC<PostProps> = ({post: {owner}}) => { + return ( + <> + <PostHeader owner={owner} /> + <View style={styles.image} /> + </> + ); +}; + +const styles = StyleSheet.create({ + image: { + width: SCREEN_WIDTH, + height: SCREEN_WIDTH, + backgroundColor: '#eee', + }, +}); +export default Post; diff --git a/src/components/common/post/PostHeader.tsx b/src/components/common/post/PostHeader.tsx new file mode 100644 index 00000000..8558d21d --- /dev/null +++ b/src/components/common/post/PostHeader.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import {UserType} from '../../../types'; +import {View, StyleSheet, Image, Text} from 'react-native'; +import {AuthContext} from '../../../routes/authentication'; + +const AVATAR_DIM = 35; +interface PostHeaderProps { + owner: UserType; +} +const PostHeader: React.FC<PostHeaderProps> = ({owner: {username}}) => { + const {avatar} = React.useContext(AuthContext); + return ( + <View style={styles.container}> + <View style={styles.leftElem}> + <Image + style={styles.avatar} + source={ + avatar + ? {uri: avatar} + : require('../../../assets/images/avatar-placeholder.png') + } + /> + <Text style={styles.username}>{username}</Text> + </View> + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + justifyContent: 'space-between', + padding: 10, + backgroundColor: 'white', + }, + leftElem: { + flexDirection: 'row', + alignItems: 'center', + }, + avatar: { + width: AVATAR_DIM, + height: AVATAR_DIM, + borderRadius: AVATAR_DIM / 2, + marginRight: 10, + }, + username: { + fontSize: 18, + }, +}); + +export default PostHeader; diff --git a/src/components/common/post/index.ts b/src/components/common/post/index.ts new file mode 100644 index 00000000..033f8a8d --- /dev/null +++ b/src/components/common/post/index.ts @@ -0,0 +1 @@ +export {default} from './Post'; diff --git a/src/components/index.ts b/src/components/index.ts index 724b14ac..48b7df05 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,2 +1,3 @@ export * from './common'; export * from './onboarding'; +export * from './profile'; diff --git a/src/components/profile/Avatar.tsx b/src/components/profile/Avatar.tsx new file mode 100644 index 00000000..a0f7596c --- /dev/null +++ b/src/components/profile/Avatar.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import {Image, StyleSheet} from 'react-native'; +import {AuthContext} from '../../routes/authentication'; + +const PROFILE_DIM = 100; +interface AvatarProps { + style: object; +} +const Avatar: React.FC<AvatarProps> = ({style}) => { + const {avatar} = React.useContext(AuthContext); + return ( + <Image + style={[styles.image, style]} + source={ + avatar + ? {uri: avatar} + : require('../../assets/images/avatar-placeholder.png') + } + /> + ); +}; + +const styles = StyleSheet.create({ + image: { + height: PROFILE_DIM, + width: PROFILE_DIM, + borderRadius: PROFILE_DIM / 2, + }, +}); + +export default Avatar; diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx new file mode 100644 index 00000000..82b5fdc0 --- /dev/null +++ b/src/components/profile/Content.tsx @@ -0,0 +1,58 @@ +import React, {useState} from 'react'; +import {StyleSheet, LayoutChangeEvent} from 'react-native'; +import Animated from 'react-native-reanimated'; +const {ScrollView} = Animated; + +import {UserType} from '../../types'; +import ProfileCutout from './ProfileCutout'; +import ProfileHeader from './ProfileHeader'; +import ProfileBody from './ProfileBody'; +import MomentsBar from './MomentsBar'; +import Feed from './Feed'; +import LinearGradient from 'react-native-linear-gradient'; +import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; + +interface ContentProps { + y: Animated.Value<number>; + user: UserType; +} +const Content: React.FC<ContentProps> = ({y, user}) => { + const [profileBodyHeight, setProfileBodyHeight] = useState(0); + const onLayout = (e: LayoutChangeEvent) => { + const {height} = e.nativeEvent.layout; + setProfileBodyHeight(height); + }; + return ( + <ScrollView + style={styles.container} + onScroll={(e) => y.setValue(e.nativeEvent.contentOffset.y)} + showsVerticalScrollIndicator={false} + scrollEventThrottle={1} + stickyHeaderIndices={[2, 4]}> + <ProfileCutout> + <ProfileHeader /> + </ProfileCutout> + <ProfileBody {...{onLayout}} /> + <MomentsBar {...{y, profileBodyHeight}} /> + <Feed {...{user}} /> + <LinearGradient + locations={[0.89, 1]} + colors={['transparent', 'rgba(0, 0, 0, 0.6)']} + style={styles.gradient} + /> + </ScrollView> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + gradient: { + height: SCREEN_HEIGHT, + width: SCREEN_WIDTH, + position: 'absolute', + }, +}); + +export default Content; diff --git a/src/components/profile/Cover.tsx b/src/components/profile/Cover.tsx new file mode 100644 index 00000000..01199f06 --- /dev/null +++ b/src/components/profile/Cover.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import {Image, StyleSheet} from 'react-native'; +import Animated from 'react-native-reanimated'; +import {IMAGE_WIDTH, COVER_HEIGHT} from '../../constants'; +import {AuthContext} from '../../routes/authentication'; + +const {interpolate, Extrapolate} = Animated; +interface CoverProps { + y: Animated.Value<number>; +} +const Cover: React.FC<CoverProps> = ({y}) => { + const {cover} = React.useContext(AuthContext); + const scale: Animated.Node<number> = interpolate(y, { + inputRange: [-COVER_HEIGHT, 0], + outputRange: [1.5, 1.25], + extrapolateRight: Extrapolate.CLAMP, + }); + return ( + <Animated.View style={[styles.container, {transform: [{scale}]}]}> + <Image + style={styles.image} + source={ + cover + ? {uri: cover} + : require('../../assets/images/cover-placeholder.png') + } + /> + </Animated.View> + ); +}; + +const styles = StyleSheet.create({ + container: { + position: 'absolute', + }, + image: { + width: IMAGE_WIDTH, + height: COVER_HEIGHT, + }, +}); +export default Cover; diff --git a/src/components/profile/Feed.tsx b/src/components/profile/Feed.tsx new file mode 100644 index 00000000..6780f8c5 --- /dev/null +++ b/src/components/profile/Feed.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import {PostType, UserType} from '../../types'; +import {Post} from '../common'; + +interface FeedProps { + user: UserType; +} +const Feed: React.FC<FeedProps> = ({user}) => { + const posts: Array<PostType> = []; + const dummyPost: PostType = { + owner: user, + }; + for (let i = 0; i < 20; i++) { + posts.push(dummyPost); + } + return ( + <> + {posts.map((post, index) => ( + <Post key={index} post={post} /> + ))} + </> + ); +}; + +export default Feed; diff --git a/src/components/profile/FollowCount.tsx b/src/components/profile/FollowCount.tsx new file mode 100644 index 00000000..72817e7a --- /dev/null +++ b/src/components/profile/FollowCount.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import {View, Text, StyleSheet, ViewProps} from 'react-native'; + +interface FollowCountProps extends ViewProps { + mode: 'followers' | 'following'; + count: number; +} + +const FollowCount: React.FC<FollowCountProps> = ({style, mode, count}) => { + const displayed: string = + count < 5e3 + ? `${count}` + : count < 1e5 + ? `${(count / 1e3).toFixed(1)}k` + : count < 1e6 + ? `${(count / 1e3).toFixed(0)}k` + : `${count / 1e6}m`; + return ( + <View style={[styles.container, style]}> + <Text style={styles.count}>{displayed}</Text> + <Text style={styles.label}> + {mode === 'followers' ? 'Followers' : 'Following'} + </Text> + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + }, + count: { + fontWeight: '700', + fontSize: 18, + }, + label: { + fontWeight: '400', + fontSize: 16, + }, +}); + +export default FollowCount; diff --git a/src/components/profile/Moment.tsx b/src/components/profile/Moment.tsx new file mode 100644 index 00000000..eaf43fea --- /dev/null +++ b/src/components/profile/Moment.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import {View, StyleSheet, ViewProps} from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; + +interface MomentProps extends ViewProps {} +const Moment: React.FC<MomentProps> = ({style}) => { + return ( + <LinearGradient + colors={['#9F00FF', '#27EAE9']} + useAngle={true} + angle={154.72} + angleCenter={{x: 0.5, y: 0.5}} + style={[styles.gradient, style]}> + <View style={styles.image} /> + </LinearGradient> + ); +}; + +const styles = StyleSheet.create({ + gradient: { + width: 80, + height: 80, + borderRadius: 40, + justifyContent: 'center', + alignItems: 'center', + }, + image: { + width: 72, + height: 72, + borderRadius: 37.5, + backgroundColor: 'pink', + }, +}); + +export default Moment; diff --git a/src/components/profile/MomentsBar.tsx b/src/components/profile/MomentsBar.tsx new file mode 100644 index 00000000..dcc88d89 --- /dev/null +++ b/src/components/profile/MomentsBar.tsx @@ -0,0 +1,75 @@ +// @refresh react +import React from 'react'; +import {StyleSheet} from 'react-native'; +import Animated from 'react-native-reanimated'; +import Moment from './Moment'; +import {PROFILE_CUTOUT_BOTTOM_Y} from '../../constants'; +import {StatusBarHeight} from '../../utils'; + +const {View, ScrollView, interpolate, Extrapolate} = Animated; +interface MomentsBarProps { + y: Animated.Value<number>; + profileBodyHeight: number; +} +const MomentsBar: React.FC<MomentsBarProps> = ({y, profileBodyHeight}) => { + const moments: Array<JSX.Element> = []; + for (let i = 0; i < 10; i++) { + moments.push(<Moment key={i} style={styles.moment} />); + } + const shadowOpacity: Animated.Node<number> = interpolate(y, { + inputRange: [ + PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight, + PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight + 20, + ], + outputRange: [0, 0.2], + extrapolate: Extrapolate.CLAMP, + }); + const paddingTop: Animated.Node<number> = interpolate(y, { + inputRange: [ + 0, + PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight - 30, + PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight, + ], + outputRange: [20, 20, StatusBarHeight], + extrapolate: Extrapolate.CLAMP, + }); + const paddingBottom: Animated.Node<number> = interpolate(y, { + inputRange: [ + 0, + PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight - 30, + PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight, + ], + outputRange: [30, 30, 15], + extrapolate: Extrapolate.CLAMP, + }); + return ( + <View style={[styles.container, {shadowOpacity}]}> + <ScrollView + horizontal + showsHorizontalScrollIndicator={false} + style={{paddingTop, paddingBottom}} + contentContainerStyle={styles.contentContainer}> + {moments} + </ScrollView> + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: 'white', + shadowColor: '#000', + shadowRadius: 10, + shadowOffset: {width: 0, height: 2}, + zIndex: 1, + }, + contentContainer: { + alignItems: 'center', + paddingHorizontal: 15, + }, + moment: { + marginHorizontal: 14, + }, +}); + +export default MomentsBar; diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx new file mode 100644 index 00000000..e8d8de62 --- /dev/null +++ b/src/components/profile/ProfileBody.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import {StyleSheet, View, Text, LayoutChangeEvent} from 'react-native'; +import {AuthContext} from '../../routes/authentication'; + +interface ProfileBodyProps { + onLayout: (event: LayoutChangeEvent) => void; +} +const ProfileBody: React.FC<ProfileBodyProps> = ({onLayout}) => { + const { + profile, + user: {username}, + } = React.useContext(AuthContext); + const {biography, website} = profile; + return ( + <View onLayout={onLayout} style={styles.container}> + <Text style={styles.username}>{`@${username}`}</Text> + <Text style={styles.biography}>{`${biography}`}</Text> + <Text style={styles.website}>{`${website}`}</Text> + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + paddingVertical: 5, + paddingHorizontal: 20, + backgroundColor: 'white', + }, + username: { + fontWeight: '600', + fontSize: 16, + marginBottom: 5, + }, + biography: { + fontSize: 16, + lineHeight: 22, + marginBottom: 5, + }, + website: { + fontSize: 16, + color: '#4E699C', + marginBottom: 5, + }, +}); + +export default ProfileBody; diff --git a/src/components/profile/ProfileCutout.tsx b/src/components/profile/ProfileCutout.tsx new file mode 100644 index 00000000..c5deb06d --- /dev/null +++ b/src/components/profile/ProfileCutout.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import Svg, {Polygon} from 'react-native-svg'; +import { + PROFILE_CUTOUT_CORNER_Y, + PROFILE_CUTOUT_CORNER_X, + PROFILE_CUTOUT_TOP_Y, + PROFILE_CUTOUT_BOTTOM_Y, +} from '../../constants'; +import {SCREEN_WIDTH} from '../../utils'; + +const ProfileCutout: React.FC = ({children}) => { + return ( + <Svg width={SCREEN_WIDTH} height={PROFILE_CUTOUT_BOTTOM_Y}> + <Polygon + points={`0,${PROFILE_CUTOUT_CORNER_Y} ${PROFILE_CUTOUT_CORNER_X},${PROFILE_CUTOUT_TOP_Y} ${SCREEN_WIDTH},${PROFILE_CUTOUT_TOP_Y} ${SCREEN_WIDTH},${PROFILE_CUTOUT_BOTTOM_Y}, 0,${PROFILE_CUTOUT_BOTTOM_Y}`} + fill={'white'} + /> + {children} + </Svg> + ); +}; + +export default ProfileCutout; diff --git a/src/components/profile/ProfileHeader.tsx b/src/components/profile/ProfileHeader.tsx new file mode 100644 index 00000000..ec382357 --- /dev/null +++ b/src/components/profile/ProfileHeader.tsx @@ -0,0 +1,62 @@ +import React from 'react'; + +import Avatar from './Avatar'; +import FollowCount from './FollowCount'; +import {View, Text, StyleSheet} from 'react-native'; +import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import {AuthContext} from '../../routes/authentication'; + +const ProfileHeader: React.FC = () => { + const { + profile: {name}, + } = React.useContext(AuthContext); + return ( + <View style={styles.container}> + <View style={styles.row}> + <Avatar style={styles.avatar} /> + <View style={styles.header}> + <Text style={styles.name}>{name}</Text> + <View style={styles.row}> + <FollowCount + style={styles.follows} + mode="followers" + count={318412} + /> + <FollowCount style={styles.follows} mode="following" count={1036} /> + </View> + </View> + </View> + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + top: SCREEN_HEIGHT / 2.4, + paddingHorizontal: SCREEN_WIDTH / 20, + marginBottom: SCREEN_HEIGHT / 10, + }, + row: { + flexDirection: 'row', + }, + header: { + justifyContent: 'center', + alignItems: 'center', + marginTop: SCREEN_HEIGHT / 40, + marginLeft: SCREEN_WIDTH / 10, + marginBottom: SCREEN_HEIGHT / 50, + }, + avatar: { + bottom: SCREEN_HEIGHT / 80, + }, + name: { + fontSize: 20, + fontWeight: '700', + marginBottom: SCREEN_HEIGHT / 80, + }, + follows: { + marginHorizontal: SCREEN_HEIGHT / 50, + }, +}); + +export default ProfileHeader; diff --git a/src/components/profile/index.ts b/src/components/profile/index.ts new file mode 100644 index 00000000..2052ee5b --- /dev/null +++ b/src/components/profile/index.ts @@ -0,0 +1,7 @@ +export {default as Cover} from './Cover'; +export {default as Content} from './Content'; +export {default as ProfileCutout} from './ProfileCutout'; +export {default as MomentsBar} from './MomentsBar'; +export {default as ProfileBody} from './ProfileBody'; +export {default as ProfileHeader} from './ProfileHeader'; +export {default as Feed} from './Feed'; diff --git a/src/constants/api.ts b/src/constants/api.ts index 657adf03..fd654c53 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -1,7 +1,10 @@ -export const API_ENDPOINT: string = 'http://127.0.0.1:8000/api/'; -export const LOGIN_ENDPOINT: string = 'http://127.0.0.1:8000/api/login/'; -export const LOGOUT_ENDPOINT: string = 'http://127.0.0.1:8000/api/logout/'; -export const REGISTER_ENDPOINT: string = 'http://127.0.0.1:8000/api/register/'; -export const SEND_OTP_ENDPOINT: string = 'http://127.0.0.1:8000/api/send-otp/'; -export const VERIFY_OTP_ENDPOINT: string = - 'http://127.0.0.1:8000/api/verify-otp/'; +const BASE_URL: string = 'http://127.0.0.1:8000/'; +const API_URL: string = BASE_URL + 'api/'; +export const LOGIN_ENDPOINT: string = API_URL + 'login/'; +export const LOGOUT_ENDPOINT: string = API_URL + 'logout/'; +export const REGISTER_ENDPOINT: string = API_URL + 'register/'; +export const SEND_OTP_ENDPOINT: string = API_URL + 'send-otp/'; +export const VERIFY_OTP_ENDPOINT: string = API_URL + 'verify-otp/'; +export const PROFILE_INFO_ENDPOINT: string = API_URL + 'user-profile-info/'; +export const COVER_PHOTO_ENDPOINT: string = API_URL + 'large-profile-pic/'; +export const AVATAR_PHOTO_ENDPOINT: string = API_URL + 'small-profile-pic/'; diff --git a/src/constants/constants.ts b/src/constants/constants.ts new file mode 100644 index 00000000..f79c2c5b --- /dev/null +++ b/src/constants/constants.ts @@ -0,0 +1,10 @@ +import {SCREEN_WIDTH, SCREEN_HEIGHT} from '../utils'; + +export const PROFILE_CUTOUT_TOP_Y = SCREEN_HEIGHT / 2.3; +export const PROFILE_CUTOUT_BOTTOM_Y = SCREEN_HEIGHT / 1.8; +export const PROFILE_CUTOUT_CORNER_X = SCREEN_WIDTH / 2.9; +export const PROFILE_CUTOUT_CORNER_Y = SCREEN_HEIGHT / 1.95; + +export const IMAGE_WIDTH = SCREEN_WIDTH; +export const IMAGE_HEIGHT = SCREEN_WIDTH; +export const COVER_HEIGHT = SCREEN_WIDTH * (7 / 5); diff --git a/src/constants/index.ts b/src/constants/index.ts index deb89e57..7fb47dc6 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,3 +1,4 @@ export * from './api'; +export * from './constants'; export * from './regex'; export * from './termsConditions'; diff --git a/src/routes/NavigationBar.tsx b/src/routes/NavigationBar.tsx deleted file mode 100644 index 84c18e00..00000000 --- a/src/routes/NavigationBar.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import {ViewProps} from 'react-native'; -import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; -import {Fragment} from 'react'; -import {NavigationIcon} from '../components'; -import {Home, Notifications, Profile, Search, Upload} from '../screens/main'; - -interface NavigationBarProps extends ViewProps { - centered?: boolean; -} - -const Tab = createBottomTabNavigator(); - -const NavigationBar: React.FC<NavigationBarProps> = () => { - return ( - <Fragment> - <Tab.Navigator - screenOptions={({route}) => ({ - tabBarIcon: ({focused}) => { - if (route.name === 'Home') { - return focused ? ( - <NavigationIcon tab="Home" disabled={false} /> - ) : ( - <NavigationIcon tab="Home" disabled={true} /> - ); - } else if (route.name === 'Search') { - return focused ? ( - <NavigationIcon tab="Search" disabled={false} /> - ) : ( - <NavigationIcon tab="Search" disabled={true} /> - ); - } else if (route.name === 'Upload') { - return focused ? ( - <NavigationIcon tab="Upload" disabled={false} /> - ) : ( - <NavigationIcon tab="Upload" disabled={true} /> - ); - } else if (route.name === 'Notifications') { - return focused ? ( - <NavigationIcon tab="Notifications" disabled={false} /> - ) : ( - <NavigationIcon tab="Notifications" disabled={true} /> - ); - } else if (route.name === 'Profile') { - return focused ? ( - <NavigationIcon tab="Profile" disabled={false} /> - ) : ( - <NavigationIcon tab="Profile" disabled={true} /> - ); - } - }, - })} - initialRouteName="Home" - tabBarOptions={{ - showLabel: false, - style: { - backgroundColor: 'transparent', - position: 'absolute', - borderTopWidth: 0, - left: 0, - right: 0, - bottom: 0, - }, - }}> - <Tab.Screen name="Home" component={Home} /> - <Tab.Screen name="Search" component={Search} /> - <Tab.Screen name="Upload" component={Upload} /> - <Tab.Screen name="Notifications" component={Notifications} /> - <Tab.Screen name="Profile" component={Profile} /> - </Tab.Navigator> - </Fragment> - ); -}; - -export default NavigationBar; diff --git a/src/routes/OnboardingStack.tsx b/src/routes/OnboardingStack.tsx deleted file mode 100644 index 5e91fe9f..00000000 --- a/src/routes/OnboardingStack.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import {createStackNavigator} from '@react-navigation/stack'; -import { - Login, - RegistrationOne, - RegistrationTwo, - Verification, - ProfileOnboarding, -} from '../screens/onboarding'; - -export type RootStackParamList = { - Login: undefined; - RegistrationOne: undefined; - RegistrationTwo: - | {firstName: string; lastName: string; email: string} - | undefined; - Verification: {username: string; email: string; userId: string}; - ProfileOnboarding: {username: string; userId: string}; -}; - -const RootStack = createStackNavigator<RootStackParamList>(); - -interface OnboardingStackProps {} - -const OnboardingStack: React.FC<OnboardingStackProps> = ({}) => { - return ( - <RootStack.Navigator initialRouteName="Login"> - <RootStack.Screen - name="Login" - component={Login} - options={{headerShown: false}} - /> - <RootStack.Screen - name="RegistrationOne" - component={RegistrationOne} - options={{headerShown: false}} - /> - <RootStack.Screen - name="RegistrationTwo" - component={RegistrationTwo} - options={{headerShown: false}} - /> - <RootStack.Screen - name="Verification" - component={Verification} - options={{headerShown: false}} - /> - <RootStack.Screen - name="ProfileOnboarding" - component={ProfileOnboarding} - options={{headerShown: false}} - /> - </RootStack.Navigator> - ); -}; - -export default OnboardingStack; diff --git a/src/routes/Routes.tsx b/src/routes/Routes.tsx index 43a51b90..92cd3dd2 100644 --- a/src/routes/Routes.tsx +++ b/src/routes/Routes.tsx @@ -1,42 +1,14 @@ import React from 'react'; -import {OnboardingStack, NavigationBar} from './'; -interface RoutesProps {} -interface AuthProviderProps {} +import {AuthContext} from './authentication'; +import NavigationBar from './tabs'; +import Onboarding from './onboarding'; -export const AuthContext = React.createContext<{ - user: boolean; - login: () => void; - logout: () => void; -}>({ - user: false, - login: () => {}, - logout: () => {}, -}); - -export const AuthContextProvider: React.FC<AuthProviderProps> = ({ - children, -}) => { - const [loggedIn, setLoggedIn] = React.useState(false); // renders onboarding stack - return ( - <AuthContext.Provider - value={{ - user: loggedIn, - login: () => { - setLoggedIn(true); - }, - logout: () => { - setLoggedIn(false); - }, - }}> - {children} - </AuthContext.Provider> - ); -}; - -const Routes: React.FC<RoutesProps> = ({}) => { - const {user} = React.useContext(AuthContext); - return user ? <NavigationBar /> : <OnboardingStack />; +const Routes: React.FC = () => { + const { + user: {userId}, + } = React.useContext(AuthContext); + return userId ? <NavigationBar /> : <Onboarding />; }; export default Routes; diff --git a/src/routes/authentication/AuthProvider.tsx b/src/routes/authentication/AuthProvider.tsx new file mode 100644 index 00000000..bd5706f3 --- /dev/null +++ b/src/routes/authentication/AuthProvider.tsx @@ -0,0 +1,122 @@ +import React, {useEffect} from 'react'; +import {createContext, useState} from 'react'; +import RNFetchBlob from 'rn-fetch-blob'; +import {UserType, ProfileType} from '../../types'; +import { + PROFILE_INFO_ENDPOINT, + AVATAR_PHOTO_ENDPOINT, + COVER_PHOTO_ENDPOINT, +} from '../../constants'; + +interface AuthContextProps { + user: UserType; + profile: ProfileType; + login: (userId: string, username: string) => void; + logout: () => void; + avatar: string | null; + cover: string | null; +} +const NO_USER: UserType = { + userId: '', + username: '', +}; +const NO_PROFILE: ProfileType = { + biography: '', + website: '', + name: '', +}; +export const AuthContext = createContext<AuthContextProps>({ + user: NO_USER, + profile: NO_PROFILE, + login: () => {}, + logout: () => {}, + avatar: null, + cover: null, +}); + +/** + * Authentication provider for the application. + */ +const AuthProvider: React.FC = ({children}) => { + const [user, setUser] = useState<UserType>(NO_USER); + const [profile, setProfile] = useState<ProfileType>(NO_PROFILE); + const [avatar, setAvatar] = useState<string | null>(null); + const [cover, setCover] = useState<string | null>(null); + + const {userId} = user; + useEffect(() => { + if (!userId) { + return; + } + const loadProfileInfo = async () => { + try { + const response = await fetch(PROFILE_INFO_ENDPOINT + `${userId}/`, { + method: 'GET', + }); + const status = response.status; + if (status === 200) { + const info = await response.json(); + let {name, biography, website} = info; + setProfile({name, biography, website}); + } + } catch (error) { + console.log(error); + } + }; + const loadAvatar = async () => { + try { + const response = await RNFetchBlob.config({ + fileCache: true, + appendExt: 'jpg', + }).fetch('GET', AVATAR_PHOTO_ENDPOINT + `${userId}/`); + const status = response.info().status; + if (status === 200) { + setAvatar(response.path()); + } else { + setAvatar(''); + } + } catch (error) { + console.log(error); + } + }; + const loadCover = async () => { + try { + let response = await RNFetchBlob.config({ + fileCache: true, + appendExt: 'jpg', + }).fetch('GET', COVER_PHOTO_ENDPOINT + `${userId}/`); + const status = response.info().status; + if (status === 200) { + setCover(response.path()); + } else { + setCover(''); + } + } catch (error) { + console.log(error); + } + }; + loadProfileInfo(); + loadAvatar(); + loadCover(); + }, [userId]); + + return ( + <AuthContext.Provider + value={{ + user, + profile, + avatar, + cover, + login: (id, username) => { + setUser({...user, userId: id, username}); + }, + logout: () => { + setUser(NO_USER); + }, + }}> + {children} + </AuthContext.Provider> + ); +}; + +export default AuthProvider; diff --git a/src/routes/authentication/index.ts b/src/routes/authentication/index.ts new file mode 100644 index 00000000..9968ae93 --- /dev/null +++ b/src/routes/authentication/index.ts @@ -0,0 +1,2 @@ +export * from './AuthProvider'; +export {default} from './AuthProvider'; diff --git a/src/routes/index.ts b/src/routes/index.ts index 054a25c3..69697b20 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,6 +1,4 @@ -export {default as OnboardingStack} from './OnboardingStack'; -export * from './OnboardingStack'; -export {default as NavigationBar} from './NavigationBar'; -export * from './NavigationBar'; -export {default as Routes} from './Routes'; -export * from './Routes'; +export {default as AuthProvider} from './authentication'; +export * from './authentication'; +export * from './onboarding'; +export {default} from './Routes'; diff --git a/src/routes/onboarding/Onboarding.tsx b/src/routes/onboarding/Onboarding.tsx new file mode 100644 index 00000000..d2bfbfd6 --- /dev/null +++ b/src/routes/onboarding/Onboarding.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import {OnboardingStack} from './OnboardingStack'; +import { + Login, + RegistrationOne, + RegistrationTwo, + Verification, + ProfileOnboarding, +} from '../../screens'; + +const Onboarding: React.FC = () => { + return ( + <OnboardingStack.Navigator initialRouteName="Login"> + <OnboardingStack.Screen + name="Login" + component={Login} + options={{headerShown: false}} + /> + <OnboardingStack.Screen + name="RegistrationOne" + component={RegistrationOne} + options={{headerShown: false}} + /> + <OnboardingStack.Screen + name="RegistrationTwo" + component={RegistrationTwo} + options={{headerShown: false}} + /> + <OnboardingStack.Screen + name="Verification" + component={Verification} + options={{headerShown: false}} + /> + <OnboardingStack.Screen + name="ProfileOnboarding" + component={ProfileOnboarding} + options={{headerShown: false}} + /> + </OnboardingStack.Navigator> + ); +}; + +export default Onboarding; diff --git a/src/routes/onboarding/OnboardingStack.tsx b/src/routes/onboarding/OnboardingStack.tsx new file mode 100644 index 00000000..f9722d46 --- /dev/null +++ b/src/routes/onboarding/OnboardingStack.tsx @@ -0,0 +1,13 @@ +import {createStackNavigator} from '@react-navigation/stack'; + +export type OnboardingStackParams = { + Login: undefined; + RegistrationOne: undefined; + RegistrationTwo: + | {firstName: string; lastName: string; email: string} + | undefined; + Verification: {username: string; email: string; userId: string}; + ProfileOnboarding: {username: string; userId: string}; +}; + +export const OnboardingStack = createStackNavigator<OnboardingStackParams>(); diff --git a/src/routes/onboarding/index.ts b/src/routes/onboarding/index.ts new file mode 100644 index 00000000..66b0f3f4 --- /dev/null +++ b/src/routes/onboarding/index.ts @@ -0,0 +1,2 @@ +export * from './OnboardingStack'; +export {default} from './Onboarding'; diff --git a/src/routes/tabs/NavigationBar.tsx b/src/routes/tabs/NavigationBar.tsx new file mode 100644 index 00000000..aca968c2 --- /dev/null +++ b/src/routes/tabs/NavigationBar.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; +import {NavigationIcon} from '../../components'; +import { + ProfileScreen, + Home, + Notifications, + Search, + Upload, +} from '../../screens'; + +const Tabs = createBottomTabNavigator(); + +const NavigationBar: React.FC = () => { + return ( + <Tabs.Navigator + screenOptions={({route}) => ({ + tabBarIcon: ({focused}) => { + if (route.name === 'Home') { + return focused ? ( + <NavigationIcon tab="Home" disabled={false} /> + ) : ( + <NavigationIcon tab="Home" disabled={true} /> + ); + } else if (route.name === 'Search') { + return focused ? ( + <NavigationIcon tab="Search" disabled={false} /> + ) : ( + <NavigationIcon tab="Search" disabled={true} /> + ); + } else if (route.name === 'Upload') { + return focused ? ( + <NavigationIcon tab="Upload" disabled={false} /> + ) : ( + <NavigationIcon tab="Upload" disabled={true} /> + ); + } else if (route.name === 'Notifications') { + return focused ? ( + <NavigationIcon tab="Notifications" disabled={false} /> + ) : ( + <NavigationIcon tab="Notifications" disabled={true} /> + ); + } else if (route.name === 'Profile') { + return focused ? ( + <NavigationIcon tab="Profile" disabled={false} /> + ) : ( + <NavigationIcon tab="Profile" disabled={true} /> + ); + } + }, + })} + initialRouteName="Profile" + tabBarOptions={{ + showLabel: false, + style: { + backgroundColor: 'transparent', + position: 'absolute', + borderTopWidth: 0, + left: 0, + right: 0, + bottom: 0, + }, + }}> + <Tabs.Screen name="Home" component={Home} /> + <Tabs.Screen name="Search" component={Search} /> + <Tabs.Screen name="Upload" component={Upload} /> + <Tabs.Screen name="Notifications" component={Notifications} /> + <Tabs.Screen name="Profile" component={ProfileScreen} /> + </Tabs.Navigator> + ); +}; + +export default NavigationBar; diff --git a/src/routes/tabs/index.ts b/src/routes/tabs/index.ts new file mode 100644 index 00000000..8ea77e8f --- /dev/null +++ b/src/routes/tabs/index.ts @@ -0,0 +1 @@ +export {default} from './NavigationBar'; diff --git a/src/screens/index.ts b/src/screens/index.ts new file mode 100644 index 00000000..5dd3007a --- /dev/null +++ b/src/screens/index.ts @@ -0,0 +1,3 @@ +export * from './main'; +export * from './onboarding'; +export * from './profile'; diff --git a/src/screens/main/Profile.tsx b/src/screens/main/Profile.tsx deleted file mode 100644 index 3a6536e4..00000000 --- a/src/screens/main/Profile.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import {Text} from 'react-native-animatable'; -import {StyleSheet} from 'react-native'; -import {GradientBackground} from '../../components'; - -/** - * Profile Screen for a user's logged in profile - * including posts, messaging, and settings - */ - -const Profile: React.FC = () => { - return ( - <GradientBackground> - <Text style={styles.text}> Profile Screen 🤩 </Text> - </GradientBackground> - ); -}; -const styles = StyleSheet.create({ - text: { - justifyContent: 'center', - backgroundColor: 'transparent', - }, -}); -export default Profile; diff --git a/src/screens/main/index.ts b/src/screens/main/index.ts index 9bd00c57..fb1bf49b 100644 --- a/src/screens/main/index.ts +++ b/src/screens/main/index.ts @@ -1,5 +1,4 @@ export {default as Home} from './Home'; export {default as Notifications} from './Notifications'; -export {default as Profile} from './Profile'; export {default as Search} from './Search'; export {default as Upload} from './Upload'; diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx index 7b76e97c..5c569ec3 100644 --- a/src/screens/onboarding/Login.tsx +++ b/src/screens/onboarding/Login.tsx @@ -13,13 +13,14 @@ import { Platform, } from 'react-native'; -import {RootStackParamList, AuthContext} from '../../routes'; +import {OnboardingStackParams} from '../../routes/onboarding'; +import {AuthContext} from '../../routes/authentication'; import {Background, TaggInput, SubmitButton} from '../../components'; import {usernameRegex, LOGIN_ENDPOINT} from '../../constants'; -type VerificationScreenRouteProp = RouteProp<RootStackParamList, 'Login'>; +type VerificationScreenRouteProp = RouteProp<OnboardingStackParams, 'Login'>; type VerificationScreenNavigationProp = StackNavigationProp< - RootStackParamList, + OnboardingStackParams, 'Login' >; interface LoginProps { @@ -98,9 +99,9 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { }; /** - * Handler for the Let's Start button or the Go button on the keyboard. - Makes a POST request to the Django login API and presents Alerts based on the status codes that the backend returns. - */ + * Handler for the Let's Start button or the Go button on the keyboard. + Makes a POST request to the Django login API and presents Alerts based on the status codes that the backend returns. + */ const handleLogin = async () => { if (!form.attemptedSubmit) { setForm({ @@ -110,17 +111,19 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { } try { if (form.isValidUser && form.isValidPassword) { + const {username, password} = form; let response = await fetch(LOGIN_ENDPOINT, { method: 'POST', body: JSON.stringify({ - username: form.username, - password: form.password, + username, + password, }), }); let statusCode = response.status; + let data = await response.json(); if (statusCode === 200) { - login(); + login(data.UserID, username); } else if (statusCode === 401) { Alert.alert( 'Login failed 😔', diff --git a/src/screens/onboarding/ProfileOnboarding.tsx b/src/screens/onboarding/ProfileOnboarding.tsx index 6ce1ff80..9405ca52 100644 --- a/src/screens/onboarding/ProfileOnboarding.tsx +++ b/src/screens/onboarding/ProfileOnboarding.tsx @@ -10,17 +10,18 @@ import { Alert, View, } from 'react-native'; -import {RootStackParamList, AuthContext} from '../../routes'; +import {OnboardingStackParams} from '../../routes/onboarding'; +import {AuthContext} from '../../routes/authentication'; import {Background} from '../../components'; import ImagePicker from 'react-native-image-crop-picker'; import {REGISTER_ENDPOINT} from '../../constants'; type ProfileOnboardingScreenRouteProp = RouteProp< - RootStackParamList, + OnboardingStackParams, 'ProfileOnboarding' >; type ProfileOnboardingScreenNavigationProp = StackNavigationProp< - RootStackParamList, + OnboardingStackParams, 'ProfileOnboarding' >; interface ProfileOnboardingProps { @@ -150,10 +151,10 @@ const ProfileOnboarding: React.FC<ProfileOnboardingProps> = ({route}) => { }, body: form, }); - let data = await response.json(); let statusCode = response.status; + let data = await response.json(); if (statusCode === 200) { - login(); + login(userId, username); } else if (statusCode === 400) { Alert.alert('Profile update failed. 😔', `${data}`); } else { diff --git a/src/screens/onboarding/RegistrationOne.tsx b/src/screens/onboarding/RegistrationOne.tsx index 3b9ddb3e..720fcaed 100644 --- a/src/screens/onboarding/RegistrationOne.tsx +++ b/src/screens/onboarding/RegistrationOne.tsx @@ -12,7 +12,7 @@ import { KeyboardAvoidingView, } from 'react-native'; -import {RootStackParamList} from '../../routes'; +import {OnboardingStackParams} from '../../routes'; import { ArrowButton, RegistrationWizard, @@ -22,11 +22,11 @@ import { import {nameRegex, emailRegex} from '../../constants'; type RegistrationScreenOneRouteProp = RouteProp< - RootStackParamList, + OnboardingStackParams, 'RegistrationOne' >; type RegistrationScreenOneNavigationProp = StackNavigationProp< - RootStackParamList, + OnboardingStackParams, 'RegistrationOne' >; interface RegistrationOneProps { diff --git a/src/screens/onboarding/RegistrationTwo.tsx b/src/screens/onboarding/RegistrationTwo.tsx index 09e217f6..b67c2403 100644 --- a/src/screens/onboarding/RegistrationTwo.tsx +++ b/src/screens/onboarding/RegistrationTwo.tsx @@ -14,7 +14,7 @@ import { } from 'react-native'; import {usePromiseTracker, trackPromise} from 'react-promise-tracker'; -import {RootStackParamList} from '../../routes'; +import {OnboardingStackParams} from '../../routes'; import { ArrowButton, RegistrationWizard, @@ -30,11 +30,11 @@ import { } from '../../constants'; type RegistrationScreenTwoRouteProp = RouteProp< - RootStackParamList, + OnboardingStackParams, 'RegistrationTwo' >; type RegistrationScreenTwoNavigationProp = StackNavigationProp< - RootStackParamList, + OnboardingStackParams, 'RegistrationTwo' >; interface RegistrationTwoProps { diff --git a/src/screens/onboarding/Verification.tsx b/src/screens/onboarding/Verification.tsx index 197bc0ca..0676bb3a 100644 --- a/src/screens/onboarding/Verification.tsx +++ b/src/screens/onboarding/Verification.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {RootStackParamList} from '../../routes'; +import {OnboardingStackParams} from '../../routes'; import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; import {Background, RegistrationWizard, SubmitButton} from '../../components'; @@ -23,11 +23,11 @@ import { import {usePromiseTracker, trackPromise} from 'react-promise-tracker'; type VerificationScreenRouteProp = RouteProp< - RootStackParamList, + OnboardingStackParams, 'Verification' >; type VerificationScreenNavigationProp = StackNavigationProp< - RootStackParamList, + OnboardingStackParams, 'Verification' >; interface VerificationProps { diff --git a/src/screens/onboarding/index.ts b/src/screens/onboarding/index.ts index 9b2f4cb0..7a9816e7 100644 --- a/src/screens/onboarding/index.ts +++ b/src/screens/onboarding/index.ts @@ -1,5 +1,5 @@ export {default as Login} from './Login'; +export {default as ProfileOnboarding} from './ProfileOnboarding'; export {default as RegistrationOne} from './RegistrationOne'; export {default as RegistrationTwo} from './RegistrationTwo'; export {default as Verification} from './Verification'; -export {default as ProfileOnboarding} from './ProfileOnboarding'; diff --git a/src/screens/profile/ProfileScreen.tsx b/src/screens/profile/ProfileScreen.tsx new file mode 100644 index 00000000..3d1ef2a8 --- /dev/null +++ b/src/screens/profile/ProfileScreen.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import {Cover, Content} from '../../components'; +import Animated from 'react-native-reanimated'; +import {AuthContext} from '../../routes/authentication'; +import {StatusBar} from 'react-native'; + +// destructure Value object from Animated +const {Value} = Animated; + +/** + * Profile Screen for a user's logged in profile + * including posts, messaging, and settings + */ +const ProfileScreen: React.FC = () => { + const {user} = React.useContext(AuthContext); + const y = new Value(0); + return ( + <> + <StatusBar /> + <Cover {...{y, user}} /> + <Content {...{y, user}} /> + </> + ); +}; + +export default ProfileScreen; diff --git a/src/screens/profile/index.ts b/src/screens/profile/index.ts new file mode 100644 index 00000000..0ade259d --- /dev/null +++ b/src/screens/profile/index.ts @@ -0,0 +1 @@ +export {default as ProfileScreen} from './ProfileScreen'; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 00000000..fcb073fe --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1 @@ +export * from './types'; diff --git a/src/types/types.ts b/src/types/types.ts new file mode 100644 index 00000000..1a023932 --- /dev/null +++ b/src/types/types.ts @@ -0,0 +1,14 @@ +export interface UserType { + userId: string; + username: string; +} + +export interface ProfileType { + name: string; + biography: string; + website: string; +} + +export interface PostType { + owner: UserType; +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..5bc168e3 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './screenDimensions'; +export * from './statusBarHeight'; diff --git a/src/utils/screenDimensions.ts b/src/utils/screenDimensions.ts new file mode 100644 index 00000000..56277ddc --- /dev/null +++ b/src/utils/screenDimensions.ts @@ -0,0 +1,6 @@ +import {Dimensions} from 'react-native'; + +const {width, height} = Dimensions.get('window'); + +export const SCREEN_WIDTH = width; +export const SCREEN_HEIGHT = height; diff --git a/src/utils/statusBarHeight.ts b/src/utils/statusBarHeight.ts new file mode 100644 index 00000000..4c68f9ee --- /dev/null +++ b/src/utils/statusBarHeight.ts @@ -0,0 +1,19 @@ +import {Platform, StatusBar} from 'react-native'; +import {SCREEN_WIDTH, SCREEN_HEIGHT} from './screenDimensions'; + +const X_WIDTH = 375; +const X_HEIGHT = 812; +const XSMAX_WIDTH = 414; +const XSMAX_HEIGHT = 896; + +const isIPhoneX = () => + Platform.OS === 'ios' && !Platform.isPad && !Platform.isTVOS + ? (SCREEN_WIDTH === X_WIDTH && SCREEN_HEIGHT === X_HEIGHT) || + (SCREEN_WIDTH === XSMAX_WIDTH && SCREEN_HEIGHT === XSMAX_HEIGHT) + : false; + +export const StatusBarHeight = Platform.select({ + ios: isIPhoneX() ? 44 : 20, + android: StatusBar.currentHeight, + default: 0, +}); |