Commit fd9e9fbd authored by ErlendHer's avatar ErlendHer
Browse files

Merge remote-tracking branch 'origin/10-flatlist-mobile-bugfix'

parents 97e9386c 5912ad32
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>
);
......
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,46 +40,65 @@ const MovieTable = ({ path }: { path: string }): JSX.Element => {
// Fetch movies on page change
useEffect(() => {
if (!error && !loading) {
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',
......
......@@ -17,8 +17,7 @@ export default function UserRatings(): JSX.Element {
useEffect(() => {
if (auth.token) {
console.log(auth.token);
dispatch(getRatings({token: auth.token}));
dispatch(getRatings({ token: auth.token }));
}
}, []);
......@@ -26,12 +25,11 @@ export default function UserRatings(): JSX.Element {
<FlatList
contentContainerStyle={styles.movieList}
data={auth.ratings}
renderItem={({item}) => <UserRatingCard userRating={item} />}
keyExtractor={item => item.movie.id}
renderItem={({ item }) => <UserRatingCard userRating={item} />}
keyExtractor={(item) => item.movie.id}
showsVerticalScrollIndicator={false}
/>
)
);
}
const styles = StyleSheet.create({
......@@ -46,6 +44,6 @@ const styles = StyleSheet.create({
title: {
fontSize: 20,
textAlign: 'left',
paddingBottom: 10
paddingBottom: 10,
},
});
\ No newline at end of file
});
......@@ -21,10 +21,12 @@ export const signIn = (params: SignInParams): ApolloClientAction =>
export const setToken = () => async (dispatch: Dispatch<PayloadAction<string, Token | undefined>>) => {
const token = await getToken();
dispatch({
if (token) {
dispatch({
type: AuthActionTypes.SET_TOKEN,
payload: token,
});
});
}
}
export const signUp = (params: SignUpParams): ApolloClientAction =>
......
import { applyMiddleware, createStore, compose, Store } from 'redux';
import { logger } from 'redux-logger';
import thunk from 'redux-thunk';
import { rootSaga, persistentReducer } from './ducks';
import { rootSaga, persistentReducer, rootReducer } from './ducks';
import { ApplicationState } from './interface';
import sagaMiddleware from './middlewares/sagas';
......@@ -11,12 +10,14 @@ const composeEnhancers =
(window && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
// Compose the redux store with necessary middlewares and states
export default function configureStore(initialState: ApplicationState): Store<ApplicationState> {
export default function configureStore(
initialState: ApplicationState
): Store<ApplicationState> {
const middlewares = [thunk, sagaMiddleware];
const enhancer = composeEnhancers(applyMiddleware(...middlewares));
const store = createStore(persistentReducer, initialState, enhancer);
const store = createStore(rootReducer, initialState, enhancer);
sagaMiddleware.run(rootSaga);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment