Commit 38a70391 authored by Erlend Hermanrud's avatar Erlend Hermanrud
Browse files

Resolve "Styling og dokumentasjon"

parent e5949bef
import React from 'react';
import { View, Text, useThemeColor } from './common/Themed';
import { StyleSheet, Image } from 'react-native';
import moment from 'moment'; import moment from 'moment';
import React from 'react';
import { Image, StyleSheet } from 'react-native';
import { AirbnbRating } from 'react-native-ratings'; import { AirbnbRating } from 'react-native-ratings';
import { Text, useThemeColor, View } from './common/Themed';
interface CommentProps { interface CommentProps {
date: string; date: string;
...@@ -11,11 +11,21 @@ interface CommentProps { ...@@ -11,11 +11,21 @@ interface CommentProps {
rating: number; rating: number;
} }
/**
* Component for single comments (used in a FlatList or similar to render comment section)
* Contains a username, date, comment and rating along with a randomly generated avatar url.
*/
const Comment: React.FC<CommentProps> = ({ date, comment, username, rating }: CommentProps) => { const Comment: React.FC<CommentProps> = ({ date, comment, username, rating }: CommentProps) => {
return ( return (
<View style={[styles.comment, { backgroundColor: useThemeColor({}, 'component') }]}> <View style={[styles.comment, { backgroundColor: useThemeColor({}, 'component') }]}>
<View style={[styles.row, { backgroundColor: useThemeColor({}, 'component') }]}> <View style={[styles.row, { backgroundColor: useThemeColor({}, 'component') }]}>
<Text style={styles.dateText}>{moment(date).format('DD/MM/YYYY - HH:MM')}</Text> <View style={styles.headerView}>
<Text style={styles.usernameText} numberOfLines={1} ellipsizeMode='tail'>
{username}
</Text>
<Text style={styles.dateText}>{'| ' + moment(date).format('DD/MM/YY - HH:MM')}</Text>
</View>
<AirbnbRating <AirbnbRating
count={5} count={5}
defaultRating={rating} defaultRating={rating}
...@@ -31,7 +41,9 @@ const Comment: React.FC<CommentProps> = ({ date, comment, username, rating }: Co ...@@ -31,7 +41,9 @@ const Comment: React.FC<CommentProps> = ({ date, comment, username, rating }: Co
}} }}
style={styles.avatar} style={styles.avatar}
/> />
<Text style={styles.text}>{comment}</Text> <Text style={styles.text} numberOfLines={3} ellipsizeMode='tail'>
{comment}
</Text>
</View> </View>
</View> </View>
); );
...@@ -39,13 +51,25 @@ const Comment: React.FC<CommentProps> = ({ date, comment, username, rating }: Co ...@@ -39,13 +51,25 @@ const Comment: React.FC<CommentProps> = ({ date, comment, username, rating }: Co
const styles = StyleSheet.create({ const styles = StyleSheet.create({
comment: { comment: {
flexGrow: 1, flex: 1,
width: '100%', width: '100%',
marginVertical: 4, marginVertical: 4,
padding: 4, padding: 4,
maxHeight: 90,
borderRadius: 6,
},
headerView: {
flexDirection: 'row',
flexGrow: 1,
backgroundColor: 'transparent',
}, },
dateText: { dateText: {
fontSize: 14, fontSize: 12,
},
usernameText: {
marginRight: 4,
fontSize: 12,
maxWidth: 100,
}, },
leftRow: { leftRow: {
flexGrow: 1, flexGrow: 1,
...@@ -55,7 +79,7 @@ const styles = StyleSheet.create({ ...@@ -55,7 +79,7 @@ const styles = StyleSheet.create({
flexGrow: 1, flexGrow: 1,
flexDirection: 'row', flexDirection: 'row',
alignContent: 'space-around', alignContent: 'space-around',
justifyContent: 'space-between', alignItems: 'flex-start',
}, },
avatar: { avatar: {
width: 32, width: 32,
...@@ -63,8 +87,10 @@ const styles = StyleSheet.create({ ...@@ -63,8 +87,10 @@ const styles = StyleSheet.create({
borderRadius: 32, borderRadius: 32,
}, },
text: { text: {
textAlign: 'left',
marginLeft: 4, marginLeft: 4,
fontSize: 14, fontSize: 14,
flex: 1,
}, },
}); });
......
import React from "react"; import React from 'react';
import { StyleSheet } from "react-native"; import { StyleSheet } from 'react-native';
import { genres, sortValues } from "../constants/filterOptions"; import { genres, sortValues } from '../constants/filterOptions';
import { import {
FilterKeys, FilterKeys,
FilterValues, FilterValues,
SortDirection, SortDirection,
SortKeys, SortKeys,
} from "../constants/filterOptions/interface"; } from '../constants/filterOptions/interface';
import SearchInput from "./common/SearchInput"; import SearchInput from './common/SearchInput';
import SelectInput from "./common/SelectInput"; import SelectInput from './common/SelectInput';
import { View } from "./common/Themed"; import { View } from './common/Themed';
import ToggleButton from "./ToggleButton";
import ToggleButton from './ToggleButton';
export type FilterPaneProps = { export type FilterPaneProps = {
onSearchChange: (value: string) => void; onSearchChange: (value: string) => void;
...@@ -19,6 +20,9 @@ export type FilterPaneProps = { ...@@ -19,6 +20,9 @@ export type FilterPaneProps = {
onSortDirectionChange: (value: SortDirection) => void; onSortDirectionChange: (value: SortDirection) => void;
}; };
/**
* FilterPane Component for displaying the different Movie Selection filters
*/
const FilterPane: React.FC<FilterPaneProps> = ({ const FilterPane: React.FC<FilterPaneProps> = ({
onSearchChange, onSearchChange,
onFilterChange, onFilterChange,
...@@ -30,35 +34,33 @@ const FilterPane: React.FC<FilterPaneProps> = ({ ...@@ -30,35 +34,33 @@ const FilterPane: React.FC<FilterPaneProps> = ({
<View style={styles.row}> <View style={styles.row}>
<View> <View>
<SelectInput <SelectInput
defaultValue="All genres" defaultValue='All genres'
data={genres} data={genres}
onChange={(value) => onFilterChange("genres", value ? [value] : [])} onChange={(value) => onFilterChange('genres', value ? [value] : [])}
/> />
</View> </View>
<View style={styles.inlineRow}> <View style={styles.inlineRow}>
<SelectInput <SelectInput
defaultValue="Order by" defaultValue='Order by'
style={{ style={{
borderRightWidth: 0, borderRightWidth: 0,
borderTopRightRadius: 0, borderTopRightRadius: 0,
borderBottomRightRadius: 0, borderBottomRightRadius: 0,
}} }}
data={sortValues} data={sortValues}
onChange={(value) => onSortChange(value ? value : "title")} onChange={(value) => onSortChange(value ? value : 'title')}
/> />
<ToggleButton <ToggleButton
style={{ borderBottomLeftRadius: 0, borderTopLeftRadius: 0 }} style={{ borderBottomLeftRadius: 0, borderTopLeftRadius: 0 }}
onClick={(toggled) => onClick={(toggled) => onSortDirectionChange(toggled ? 'asc' : 'desc')}
onSortDirectionChange(toggled ? "asc" : "desc") activeIcon='arrow-upward'
} inactiveIcon='arrow-downward'
activeIcon="arrow-upward"
inactiveIcon="arrow-downward"
/> />
</View> </View>
</View> </View>
<View style={styles.row}> <View style={styles.row}>
<SearchInput <SearchInput
placeholder="Search for movie titles" placeholder='Search for movie titles'
searchThreshold={3} searchThreshold={3}
onChange={onSearchChange} onChange={onSearchChange}
/> />
...@@ -69,19 +71,19 @@ const FilterPane: React.FC<FilterPaneProps> = ({ ...@@ -69,19 +71,19 @@ const FilterPane: React.FC<FilterPaneProps> = ({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
width: "100%", width: '100%',
paddingVertical: "2%", paddingVertical: '2%',
}, },
row: { row: {
flexDirection: "row", flexDirection: 'row',
paddingVertical: 5, paddingVertical: 5,
justifyContent: "space-between", justifyContent: 'space-between',
}, },
inlineRow: { inlineRow: {
flexShrink: 1, flexShrink: 1,
flexGrow: 0, flexGrow: 0,
flexDirection: "row", flexDirection: 'row',
justifyContent: "flex-end", justifyContent: 'flex-end',
}, },
sortOrderWrapper: { sortOrderWrapper: {
height: 40, height: 40,
...@@ -91,14 +93,14 @@ const styles = StyleSheet.create({ ...@@ -91,14 +93,14 @@ const styles = StyleSheet.create({
borderTopLeftRadius: 0, borderTopLeftRadius: 0,
borderBottomLeftRadius: 0, borderBottomLeftRadius: 0,
borderWidth: 1, borderWidth: 1,
overflow: "hidden", overflow: 'hidden',
}, },
sortOrderButton: { sortOrderButton: {
height: 40, height: 40,
width: 40, width: 40,
paddingBottom: 2, paddingBottom: 2,
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
borderRadius: 15, borderRadius: 15,
borderTopLeftRadius: 0, borderTopLeftRadius: 0,
borderBottomLeftRadius: 0, borderBottomLeftRadius: 0,
......
...@@ -6,6 +6,10 @@ interface KeyStatisticsItemProps { ...@@ -6,6 +6,10 @@ interface KeyStatisticsItemProps {
title: string; title: string;
statistics: string; statistics: string;
} }
/**
* Component for showing a key statistics item with a title and value
*/
export default function KeyStatisticsItem({ statistics, title }: KeyStatisticsItemProps) { export default function KeyStatisticsItem({ statistics, title }: KeyStatisticsItemProps) {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
item: { item: {
......
...@@ -34,6 +34,7 @@ export default function App() { ...@@ -34,6 +34,7 @@ export default function App() {
handleSubmit, handleSubmit,
} = methods; } = methods;
// Handle form submit
const onSubmit: SubmitHandler<FormValues> = (values: FormValues) => onFinish(values); const onSubmit: SubmitHandler<FormValues> = (values: FormValues) => onFinish(values);
interface FormValues { interface FormValues {
email: string; email: string;
...@@ -49,8 +50,10 @@ export default function App() { ...@@ -49,8 +50,10 @@ export default function App() {
// Form completed // Form completed
const onFinish = (values: FormValues) => { const onFinish = (values: FormValues) => {
if (isLogin) { if (isLogin) {
// If the form is in loginMode, dispatch the signIn action
dispatch(signIn({ email: values.email, password: values.password })); dispatch(signIn({ email: values.email, password: values.password }));
} else { } else {
// If the form is in signUpMode, dispatch the signUp action
dispatch( dispatch(
signUp({ signUp({
email: values.email, email: values.email,
...@@ -61,11 +64,12 @@ export default function App() { ...@@ -61,11 +64,12 @@ export default function App() {
} }
}; };
// TODO If the redux state contains an error, display an error alert
useEffect(() => { useEffect(() => {
// If there is an error, show the alert
if (error) { if (error) {
setAlertmessage(error); setAlertmessage(error);
} else { } else {
// If the error state updated and no error was found, clear the alert
setAlertmessage(''); setAlertmessage('');
} }
}, [error]); }, [error]);
......
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Dimensions, Image, StyleSheet, Text } from 'react-native'; import { Dimensions, Image, StyleSheet, Text, TouchableOpacity } from 'react-native';
import { AirbnbRating } from 'react-native-ratings'; import { AirbnbRating } from 'react-native-ratings';
import { MovieEntity } from '../store/ducks/movies/types'; import { MovieEntity } from '../store/ducks/movies/types';
import { useThemeColor, View } from './common/Themed'; import { useThemeColor, View } from './common/Themed';
...@@ -18,14 +18,18 @@ const MovieCard = (props: MovieCardProps): JSX.Element => { ...@@ -18,14 +18,18 @@ const MovieCard = (props: MovieCardProps): JSX.Element => {
const { movie } = props; const { movie } = props;
const [dimensions, setDimensions] = useState({ window, screen }); const [dimensions, setDimensions] = useState({ window, screen });
// Dynamically calculate the width of the card based on the screen size
const iw = () => { const iw = () => {
return dimensions.window.width / 2 - 20; return dimensions.window.width / 2 - 20;
}; };
// Dynamically calculate the height of the card based on the screen size
const ih = () => { const ih = () => {
return (dimensions.window.width / 2 - 20) * (443 / 300); return (dimensions.window.width / 2 - 20) * (443 / 300);
}; };
// Once the dimensions are set, we can use them to calculate the height/width of the card
useEffect(() => { useEffect(() => {
Dimensions.addEventListener('change', ({ window, screen }) => { Dimensions.addEventListener('change', ({ window, screen }) => {
setDimensions({ window, screen }); setDimensions({ window, screen });
...@@ -34,42 +38,44 @@ const MovieCard = (props: MovieCardProps): JSX.Element => { ...@@ -34,42 +38,44 @@ const MovieCard = (props: MovieCardProps): JSX.Element => {
const navigation = useNavigation(); const navigation = useNavigation();
// When the user clicks on a movie card, we navigate to the movie page
const navigateToMovie = () => { const navigateToMovie = () => {
navigation.navigate('Movie', { navigation.navigate('Movie', {
movieTitle: movie.title, movieTitle: movie.title,
movieID: movie.id, movieID: movie.id,
}); });
}; };
return ( return (
<View <View style={[styles.card, { backgroundColor: useThemeColor({}, 'component') }]}>
style={[styles.card, { backgroundColor: useThemeColor({}, 'component') }]} <TouchableOpacity onPress={navigateToMovie}>
onTouchEnd={navigateToMovie} <View style={{ flexGrow: 1 }}>
> <Image
<Image source={{
source={{ uri: movie.poster,
uri: movie.poster, }}
}} resizeMode='cover'
style={[styles.poster, { width: iw(), height: ih() }]} style={[styles.poster, { width: iw(), height: ih() }]}
/> />
<View <View
style={[ style={[
styles.cardInfo, styles.cardInfo,
{ maxWidth: iw() }, { maxWidth: iw() },
{ backgroundColor: useThemeColor({}, 'component') }, { backgroundColor: useThemeColor({}, 'component') },
]} ]}
> >
<Text style={[styles.titleText, { color: useThemeColor({}, 'inputText') }]}> <Text style={[styles.titleText, { color: useThemeColor({}, 'inputText') }]}>
{movie.title} {movie.title}
</Text> </Text>
<AirbnbRating <AirbnbRating
count={5} count={5}
defaultRating={movie.rating} defaultRating={movie.rating}
showRating={false} showRating={false}
isDisabled={true} isDisabled={true}
size={20} size={20}
/> />
</View> </View>
</View>
</TouchableOpacity>
</View> </View>
); );
}; };
...@@ -95,7 +101,7 @@ const styles = StyleSheet.create({ ...@@ -95,7 +101,7 @@ const styles = StyleSheet.create({
shadowOffset: { width: -2, height: 4 }, shadowOffset: { width: -2, height: 4 },
shadowOpacity: 0.2, shadowOpacity: 0.2,
shadowRadius: 3, shadowRadius: 3,
elevation: 1, elevation: 3,
}, },
titleText: { titleText: {
textAlign: 'center', textAlign: 'center',
......
...@@ -2,23 +2,23 @@ import * as React from 'react'; ...@@ -2,23 +2,23 @@ import * as React from 'react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { import {
ActivityIndicator, ActivityIndicator,
Button,
FlatList,
Image, Image,
ScrollView, ScrollView,
StyleSheet, StyleSheet,
useWindowDimensions, useWindowDimensions,
} from 'react-native'; } from 'react-native';
import AwesomeAlert from 'react-native-awesome-alerts'; import AwesomeAlert from 'react-native-awesome-alerts';
import { Button, Icon } from 'react-native-elements';
import { AirbnbRating } from 'react-native-ratings';
import { default as AwesomeIcon } from 'react-native-vector-icons/FontAwesome';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { rateMovie } from '../store/ducks/auth/actions'; import { rateMovie } from '../store/ducks/auth/actions';
import { clearMovie, fetchMovieById } from '../store/ducks/movies/actions'; import { clearMovie, fetchMovieById } from '../store/ducks/movies/actions';
import { ApplicationState } from '../store/interface'; import { ApplicationState } from '../store/interface';
import Comment from './Comment';
import Seperator from './common/Seperator'; import Seperator from './common/Seperator';
import { Text, View } from './common/Themed'; import { Text, View } from './common/Themed';
import KeyStatisticsItem from './KeyStatisticsItem';
import RatingModal from './RatingModal'; import RatingModal from './RatingModal';
import Comment from './Comment';
interface MovieInfoProps { interface MovieInfoProps {
movieID: string; movieID: string;
...@@ -31,17 +31,46 @@ const noAlert = { ...@@ -31,17 +31,46 @@ const noAlert = {
}; };
export default function MovieInfo({ movieID }: MovieInfoProps) { export default function MovieInfo({ movieID }: MovieInfoProps) {
const { height } = useWindowDimensions(); const { height, width } = useWindowDimensions();
const styles = StyleSheet.create({ const styles = StyleSheet.create({
scrollView: {
flexGrow: 1,
width: '90%',
},
root: { root: {
padding: 20,
flex: 1, flex: 1,
alignItems: 'center',
}, },
logo: { infoView: {
flex: 1, marginVertical: 10,
width: 200, flexDirection: 'row',
height: 350, alignItems: 'flex-start',
alignContent: 'space-between',
},
infoRow: {
flexGrow: 1,
height: '100%',
alignItems: 'center',
textAlign: 'center',
},
poster: {
borderRadius: 5,
},
titleText: {
flexGrow: 1,
fontSize: 18,
fontWeight: 'bold',
},
infoText: {
fontSize: 16,
fontWeight: 'bold',
},
icon: { fontSize: 22, marginRight: 10 },
maxRow: {
flexGrow: 1,
flexDirection: 'row',
alignItems: 'center',
}, },
container: { container: {
width: '100%', width: '100%',
...@@ -71,7 +100,7 @@ export default function MovieInfo({ movieID }: MovieInfoProps) { ...@@ -71,7 +100,7 @@ export default function MovieInfo({ movieID }: MovieInfoProps) {
paddingBottom: '1rem', paddingBottom: '1rem',
}, },
comments: { comments: {