Commit 38cc9582 authored by Mithunan Sivakumar's avatar Mithunan Sivakumar
Browse files
parents c4f84a6b fd9e9fbd
import { StatusBar } from "expo-status-bar";
import React from "react";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { Provider } from "react-redux";
import { persistStore } from "redux-persist";
import { PersistGate } from "redux-persist/integration/react";
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { Provider } from 'react-redux';
import { persistStore } from 'redux-persist';
import { PersistGate } from 'redux-persist/integration/react';
import useCachedResources from "./hooks/useCachedResources";
import useColorScheme from "./hooks/useColorScheme";
import Navigation from "./navigation";
import configureStore from "./store";
import useCachedResources from './hooks/useCachedResources';
import useColorScheme from './hooks/useColorScheme';
import Navigation from './navigation';
import configureStore from './store';
// Setup Redux store
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const initialState = (window as any).initialReduxState;
const store = configureStore(initialState);
const persistor = persistStore(store);
export default function App() {
const isLoadingComplete = useCachedResources();
......@@ -26,10 +25,8 @@ export default function App() {
return (
<SafeAreaProvider>
<Provider store={store}>
<PersistGate persistor={persistor}>
<Navigation colorScheme={colorScheme} />
<StatusBar />
</PersistGate>
<Navigation colorScheme={colorScheme} />
<StatusBar />
</Provider>
</SafeAreaProvider>
);
......
......@@ -25,7 +25,7 @@ export default function App() {
const { formState: { errors }, handleSubmit } = methods;
const onSubmit: SubmitHandler<FormValues> = (data: any) => console.log(data);
const onSubmit: SubmitHandler<FormValues> = (values: FormValues) => onFinish(values);
interface FormValues {
email: string;
password: string;
......@@ -36,7 +36,7 @@ const { formState: { errors }, handleSubmit } = methods;
const dispatch = useDispatch();
const { loading, error, loggedIn } = useSelector(({ auth }: ApplicationState) => auth);
// Redirect to homepage if user is already logged in
// TODO Redirect to homepage if user is already logged in
useEffect(() => {
if (loggedIn) {
console.log("push");
......@@ -46,15 +46,13 @@ const { formState: { errors }, handleSubmit } = methods;
// Form completed
const onFinish = (values: FormValues) => {
if (isLogin) {
//const { email, password } = userAdmin;
//dispatch(signIn({ email, password }));
dispatch(signIn({ email: values.email, password: values.password }));
} else {
//const { email, username, password } = userAdmin;
//dispatch(signUp({ email, username: username as string, password }));
dispatch(signUp({ email: values.email, username: values.username || "", password: values.password }));
}
};
// If the redux state contains an error, display an error alert
// TODO If the redux state contains an error, display an error alert
useEffect(() => {
if (error) {
console.log("Show error");
......@@ -97,7 +95,7 @@ const { formState: { errors }, handleSubmit } = methods;
<CustomInput required={true} error={errors.email ? true : false } errorMessage="Du må fylle inn e-post" name="email" placeholder="email" />
<CustomInput required={true} error={errors.password ? true : false } errorMessage="Passorder må oppfylle krav ..." name="password" secureTextEntry={true} placeholder="password" />
<View style={styles.button}>
<Button onPress={handleSubmit(onSubmit)} title={isLogin ? "Login" : "Register"}/>
<Button disabled={loading} onPress={handleSubmit(onSubmit)} title={isLogin ? "Login" : "Register"}/>
<Pressable onPress={() => {setIsLogin(!isLogin); methods.clearErrors()}} style={styles.container}>
<Text style={styles.subText}>
{!isLogin ? "Already have a user? Login." : "Need a new user? Register."}
......
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Image, StyleSheet, Text, View } from 'react-native';
import { AirbnbRating } from 'react-native-ratings';
import { MovieEntity } from '../store/ducks/movies/types';
import { Dimensions } from 'react-native';
interface MovieCardProps {
movie: MovieEntity;
}
const window = Dimensions.get('window');
const screen = Dimensions.get('screen');
/**
* Movie Card component
*/
const MovieCard = (props: MovieCardProps): JSX.Element => {
const { movie } = props;
const [dimensions, setDimensions] = useState({ window, screen });
const iw = () => {
return dimensions.window.width / 2 - 20;
};
const ih = () => {
return (dimensions.window.width / 2 - 20) * (443 / 300);
};
useEffect(() => {
Dimensions.addEventListener('change', ({ window, screen }) => {
setDimensions({ window, screen });
});
}, []);
return (
<View style={styles.card}>
<Image
source={{
uri: movie.poster,
}}
style={styles.poster}
resizeMode="contain"
style={[styles.poster, { width: iw(), height: ih() }]}
/>
<View style={styles.cardInfo}>
<View style={[styles.cardInfo, { maxWidth: iw() }]}>
<Text style={styles.titleText}>{movie.title}</Text>
<AirbnbRating
count={5}
......@@ -37,33 +55,37 @@ const MovieCard = (props: MovieCardProps): JSX.Element => {
const styles = StyleSheet.create({
poster: {
flex: 1,
minWidth: undefined,
height: undefined,
aspectRatio: 300 / 443,
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
resizeMode: 'contain',
maxWidth: 300,
},
card: {
flex: 1,
backgroundColor: 'white',
flex: 1 / 2,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginHorizontal: 4,
minWidth: 175,
boxShadow: '0 2px 4px 0 rgba(0,0,0,0.2)',
marginVertical: 4,
borderRadius: 5,
textAlign: 'center',
shadowColor: '#171717',
shadowOffset: { width: -2, height: 4 },
shadowOpacity: 0.2,
shadowRadius: 3,
elevation: 1,
},
titleText: {
textAlign: 'center',
fontWeight: 'bold',
},
cardInfo: {
padding: 4,
textAlign: 'center',
maxWidth: 175,
minHeight: 75,
},
rating: {
// maxWidth: 150,
},
});
export default MovieCard;
import React, { useEffect, useState } from 'react';
import { FlatList, StyleSheet, Text } from 'react-native';
import { FlatList, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useDispatch, useSelector } from 'react-redux';
import { fetchMovies } from '../store/ducks/movies/actions';
......@@ -8,7 +8,7 @@ import { ApplicationState } from '../store/interface';
import MovieCard from './MovieCard';
const baseQuery: FetchMovieParams = {
perPage: 12,
perPage: 20,
page: 0,
orderBy: 'title',
order: 'desc',
......@@ -40,47 +40,65 @@ const MovieTable = ({ path }: { path: string }): JSX.Element => {
// Fetch movies on page change
useEffect(() => {
if (!error && !loading) {
console.log(movies);
setMovies([...movies, ...data]);
// Ensure that we don't add duplicate movies to the array
const moviesToAdd = [...movies];
data.forEach((movie: MovieEntity) => {
if (!moviesToAdd.some((m) => m.id === movie.id)) {
moviesToAdd.push(movie);
}
});
setMovies([...moviesToAdd]);
}
}, [data]);
return (
<FlatList
contentContainerStyle={styles.movieList}
data={movies}
numColumns={2}
keyExtractor={(movie) => movie.id}
onEndReached={({ distanceFromEnd }) => {
// Prevent bug where onEndReached is called multiple times
if (distanceFromEnd < 0) return;
// Prevent fetching if all movies are loaded
if (prevPageLoaded + 1 >= Math.ceil(documentCount / baseQuery.perPage))
return;
<View style={styles.movieList}>
<FlatList
style={{ width: '100%', height: '100%' }}
columnWrapperStyle={{ flex: 1, justifyContent: 'space-around' }}
contentContainerStyle={styles.movieItems}
data={movies}
numColumns={2}
keyExtractor={(movie) => movie.id}
onEndReached={({ distanceFromEnd }) => {
// Prevent bug where onEndReached is called multiple times
if (distanceFromEnd < 0) return;
// Prevent fetching if all movies are loaded
if (
prevPageLoaded + 1 >=
Math.ceil(documentCount / baseQuery.perPage)
)
return;
fetchByPage(prevPageLoaded + 1);
setPageLoaded(prevPageLoaded + 1);
}}
onEndReachedThreshold={0.5}
initialNumToRender={baseQuery.perPage}
showsVerticalScrollIndicator={false}
renderItem={({ item }) => <MovieCard movie={item} />}
ListFooterComponent={() => (
<>{loading && <Text style={styles.loading}>Loading...</Text>}</>
)}
/>
fetchByPage(prevPageLoaded + 1);
setPageLoaded(prevPageLoaded + 1);
}}
onEndReachedThreshold={1.5}
initialNumToRender={baseQuery.perPage}
showsVerticalScrollIndicator={false}
renderItem={({ item }) => <MovieCard movie={item} />}
ListFooterComponent={() => (
<>{loading && <Text style={styles.loading}>Loading...</Text>}</>
)}
/>
</View>
);
};
const styles = StyleSheet.create({
movieItems: {
justifyContent: 'center',
flexGrow: 1 / 2,
backgroundColor: 'white',
},
movieList: {
marginTop: 8,
textAlign: 'center',
// justifyContent: 'center',
marginTop: 4,
width: '95%',
height: '100%',
display: 'flex',
flex: 1,
flexDirection: 'column',
gap: 8,
alignItems: 'center',
textAlign: 'center',
},
loading: {
fontWeight: 'bold',
......
import * as WebBrowser from 'expo-web-browser';
import React from 'react';
import { StyleSheet, TouchableOpacity } from 'react-native';
import { StyleSheet } from 'react-native';
import Colors from '../constants/Colors';
import { UserEntity } from '../store/ducks/auth/types';
import { MonoText } from './StyledText';
import { Text, View } from './Themed';
export default function ProfileInfo({ user }: { user: UserEntity }) {
return (
<View>
<View style={styles.getStartedContainer}>
<View style={styles.profileInfoContainer}>
<Text
style={styles.profileName}
style={styles.profileInfoText}
lightColor="rgba(0,0,0,0.8)"
darkColor="rgba(255,255,255,0.8)">
{user.username}
</Text>
<Text
style={styles.getStartedText}
style={styles.profileInfoText}
lightColor="rgba(0,0,0,0.8)"
darkColor="rgba(255,255,255,0.8)">
{user.email}
......@@ -29,14 +26,8 @@ export default function ProfileInfo({ user }: { user: UserEntity }) {
);
}
function handleHelpPress() {
WebBrowser.openBrowserAsync(
'https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet'
);
}
const styles = StyleSheet.create({
getStartedContainer: {
profileInfoContainer: {
alignItems: 'center',
marginHorizontal: 50,
},
......@@ -47,15 +38,10 @@ const styles = StyleSheet.create({
borderRadius: 3,
paddingHorizontal: 4,
},
profileName: {
fontSize: 20,
lineHeight: 24,
textAlign: 'center',
},
getStartedText: {
profileInfoText: {
fontSize: 17,
lineHeight: 24,
textAlign: 'center',
textAlign: 'left',
},
helpContainer: {
marginTop: 15,
......
import React from 'react';
import { Image, StyleSheet } from 'react-native';
import { Text, View } from '../components/Themed';
import { AirbnbRating } from 'react-native-ratings';
import { UserRating } from '../store/ducks/auth/types';
import { MovieEntity } from '../store/ducks/movies/types';
import { buildExecutionContext } from 'graphql/execution/execute';
interface UserRatingCardProps {
userRating: UserRating
}
/**
* Movie Card component
*/
const UserRatingCard = (props: UserRatingCardProps): JSX.Element => {
const { userRating } = props;
const { movie, rating, comment, date } = userRating;
return (
<View style={styles.card} lightColor="#eee" darkColor="rgba(255,255,255,0.1)">
<View style={styles.image}>
<Image
source={{
uri: movie.poster,
}}
style={styles.poster}
resizeMode="contain"
/>
</View>
<View style={styles.cardInfo}>
<AirbnbRating
count={5}
defaultRating={rating}
showRating={false}
isDisabled={true}
size={20}
/>
<Text style={styles.titleText} lightColor="rgba(0,0,0,0.8)" darkColor="rgba(255,255,255,0.8)">{movie.title}</Text>
<Text style={styles.comment} lightColor="rgba(0,0,0,0.8)" darkColor="rgba(255,255,255,0.8)">{comment}</Text>
<Text style={styles.date} lightColor="rgba(0,0,0,0.6)" darkColor="rgba(255,255,255,0.6)">{date.split('T')[0]}</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
poster: {
aspectRatio: 300 / 443,
borderRadius: 5
},
card: {
borderRadius: 5,
textAlign: 'center',
flexDirection: 'row',
marginVertical: 10,
},
titleText: {
marginHorizontal: 5,
fontSize: 18,
fontWeight: 'bold',
},
comment: {
marginHorizontal: 5,
fontSize: 12,
},
date: {
marginHorizontal: 5,
fontSize: 12,
position: 'absolute',
bottom: 10,
left: 5
},
cardInfo: {
flex: 2,
textAlign: 'left',
},
image: {
flex: 1,
minWidth: 150
},
});
export default UserRatingCard;
import * as React from 'react';
import { useEffect, useState } from 'react';
import { FlatList, StyleSheet } from 'react-native';
import { AirbnbRating } from 'react-native-ratings';
import { useDispatch, useSelector } from 'react-redux';
import { Text, View } from '../components/Themed';
import { Genre } from '../models/movieData.model';
import { getRatings } from '../store/ducks/auth/actions';
import { AuthState } from '../store/ducks/auth/types';
import { ApplicationState } from '../store/interface';
import UserRatingCard from './UserRatingCard';
export default function UserRatings(): JSX.Element {
const dispatch = useDispatch();
const auth: AuthState = useSelector(({ auth }: ApplicationState) => auth);
useEffect(() => {
if (auth.token) {
dispatch(getRatings({ token: auth.token }));
}
}, []);
return (
<FlatList
contentContainerStyle={styles.movieList}
data={auth.ratings}
renderItem={({ item }) => <UserRatingCard userRating={item} />}
keyExtractor={(item) => item.movie.id}
showsVerticalScrollIndicator={false}
/>
);
}
const styles = StyleSheet.create({
movieList: {
padding: 5,
textAlign: 'center',
},
item: {
padding: 5,
marginVertical: 8,
},
title: {
fontSize: 20,
textAlign: 'left',
paddingBottom: 10,
},
});
......@@ -4,6 +4,7 @@
*
*/
import { FontAwesome } from '@expo/vector-icons';
import { MaterialIcons } from '@expo/vector-icons';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import {
NavigationContainer,
......@@ -28,6 +29,8 @@ import {
} from '../types';
import LinkingConfiguration from './LinkingConfiguration';
export default function Navigation({
colorScheme,
}: {
......@@ -35,8 +38,8 @@ export default function Navigation({
}) {
return (
<NavigationContainer
linking={LinkingConfiguration}
theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}
linking={LinkingConfiguration}
theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}
>
<RootNavigator />
</NavigationContainer>
......@@ -56,12 +59,12 @@ function RootNavigator() {
name="Root"
component={BottomTabNavigator}
options={{ headerShown: false }}
/>
/>
<Stack.Screen
name="NotFound"
component={NotFoundScreen}
options={{ title: 'Oops!' }}
/>
/>
<Stack.Group screenOptions={{ presentation: 'modal' }}>
<Stack.Screen name="Modal" component={ModalScreen} />
</Stack.Group>
......@@ -76,44 +79,28 @@ function RootNavigator() {
const BottomTab = createBottomTabNavigator<RootTabParamList>();
function BottomTabNavigator() {
const colorScheme = useColorScheme();
const colorScheme = useColorScheme();
return (
<BottomTab.Navigator
initialRouteName="TabOne"
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme].tint,
initialRouteName="Movies"
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme].tint,
}}
>
>
<BottomTab.Screen
name="TabOne"
component={TabOneScreen}
options={({ navigation }: RootTabScreenProps<'TabOne'>) => ({
title: 'Tab One',
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
headerRight: () => (
<Pressable
onPress={() => navigation.navigate('Modal')}
style={({ pressed }) => ({
opacity: pressed ? 0.5 : 1,
})}
>
<FontAwesome
name="info-circle"
size={25}
color={Colors[colorScheme].text}
style={{ marginRight: 15 }}
/>
</Pressable>
),
})}
/>
name="Movies"
component={MovieTableScreen}
options={{
title: 'Movies',
tabBarIcon: ({color}) => <MaterialIcons name="movie" size={30} style={{ marginBottom: -3 }} color={color} />,
}}
/>
<BottomTab.Screen
name="Profile"
component={Profile}
options={{
title: 'Profile',
tabBarIcon: ({ color }) => <TabBarIcon name="user" color={color} />,
tabBarIcon: ({ color }) => <MaterialIcons name="person-outline" size={30} style={{ marginBottom: -3 }} color={color} />,
headerRight: () => (
<Pressable
onPress={() => 0}
......@@ -131,24 +118,6 @@ function BottomTabNavigator() {
),
}}
/>
<BottomTab.Screen
name="MovieTableTab"
component={MovieTableScreen}
options={{
title: 'Movie Table Tab',