Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
IT2810 H21
Team 05
prosjekt4
Commits
e5949bef
Commit
e5949bef
authored
Nov 19, 2021
by
Erlend Hermanrud
Browse files
Merge branch '7-implementere-rating-av-film' into 'master'
Resolve "Implementere rating av film" Closes
#7
See merge request
!15
parents
047dcb6c
6adf420e
Changes
29
Hide whitespace changes
Inline
Side-by-side
components/Comment.tsx
0 → 100644
View file @
e5949bef
import
React
from
'
react
'
;
import
{
View
,
Text
,
useThemeColor
}
from
'
./common/Themed
'
;
import
{
StyleSheet
,
Image
}
from
'
react-native
'
;
import
moment
from
'
moment
'
;
import
{
AirbnbRating
}
from
'
react-native-ratings
'
;
interface
CommentProps
{
date
:
string
;
comment
:
string
;
username
:
string
;
rating
:
number
;
}
const
Comment
:
React
.
FC
<
CommentProps
>
=
({
date
,
comment
,
username
,
rating
}:
CommentProps
)
=>
{
return
(
<
View
style
=
{
[
styles
.
comment
,
{
backgroundColor
:
useThemeColor
({},
'
component
'
)
}]
}
>
<
View
style
=
{
[
styles
.
row
,
{
backgroundColor
:
useThemeColor
({},
'
component
'
)
}]
}
>
<
Text
style
=
{
styles
.
dateText
}
>
{
moment
(
date
).
format
(
'
DD/MM/YYYY - HH:MM
'
)
}
</
Text
>
<
AirbnbRating
count
=
{
5
}
defaultRating
=
{
rating
}
showRating
=
{
false
}
isDisabled
=
{
true
}
size
=
{
15
}
/>
</
View
>
<
View
style
=
{
[
styles
.
leftRow
,
{
backgroundColor
:
useThemeColor
({},
'
component
'
)
}]
}
>
<
Image
source
=
{
{
uri
:
`https://i.pravatar.cc/64?img=
${
Math
.
floor
(
Math
.
random
()
*
70
)}
`
,
}
}
style
=
{
styles
.
avatar
}
/>
<
Text
style
=
{
styles
.
text
}
>
{
comment
}
</
Text
>
</
View
>
</
View
>
);
};
const
styles
=
StyleSheet
.
create
({
comment
:
{
flexGrow
:
1
,
width
:
'
100%
'
,
marginVertical
:
4
,
padding
:
4
,
},
dateText
:
{
fontSize
:
14
,
},
leftRow
:
{
flexGrow
:
1
,
flexDirection
:
'
row
'
,
},
row
:
{
flexGrow
:
1
,
flexDirection
:
'
row
'
,
alignContent
:
'
space-around
'
,
justifyContent
:
'
space-between
'
,
},
avatar
:
{
width
:
32
,
height
:
32
,
borderRadius
:
32
,
},
text
:
{
marginLeft
:
4
,
fontSize
:
14
,
},
});
export
default
Comment
;
components/FilterPane.tsx
View file @
e5949bef
import
React
from
"
react
"
;
import
{
StyleSheet
}
from
"
react-native
"
;
import
{
View
}
from
"
../components/Themed
"
;
import
{
genres
,
sortValues
}
from
"
../constants/filterOptions
"
;
import
{
FilterKeys
,
FilterValues
,
SortDirection
,
SortKeys
,
}
from
"
../constants/filterOptions/interface
"
;
import
{
genres
,
sortDirections
,
sortValues
}
from
"
../constants/filterOptions
"
;
import
SelectInput
from
"
./SelectInput
"
;
import
SearchInput
from
"
./SearchInput
"
;
import
SearchInput
from
"
./common/SearchInput
"
;
import
SelectInput
from
"
./common/SelectInput
"
;
import
{
View
}
from
"
./common/Themed
"
;
import
ToggleButton
from
"
./ToggleButton
"
;
export
type
FilterPaneProps
=
{
onSearchChange
:
(
value
:
string
)
=>
void
;
...
...
@@ -37,13 +38,21 @@ const FilterPane: React.FC<FilterPaneProps> = ({
<
View
style
=
{
styles
.
inlineRow
}
>
<
SelectInput
defaultValue
=
"Order by"
style
=
{
{
borderRightWidth
:
0
,
borderTopRightRadius
:
0
,
borderBottomRightRadius
:
0
,
}
}
data
=
{
sortValues
}
onChange
=
{
(
value
)
=>
onSortChange
(
value
?
value
:
"
title
"
)
}
/>
<
SelectInput
data
=
{
sortDirections
}
style
=
{
{
marginRight
:
4
}
}
onChange
=
{
(
value
)
=>
onSortDirectionChange
(
value
?
value
:
"
asc
"
)
}
<
ToggleButton
style
=
{
{
borderBottomLeftRadius
:
0
,
borderTopLeftRadius
:
0
}
}
onClick
=
{
(
toggled
)
=>
onSortDirectionChange
(
toggled
?
"
asc
"
:
"
desc
"
)
}
activeIcon
=
"arrow-upward"
inactiveIcon
=
"arrow-downward"
/>
</
View
>
</
View
>
...
...
@@ -74,6 +83,27 @@ const styles = StyleSheet.create({
flexDirection
:
"
row
"
,
justifyContent
:
"
flex-end
"
,
},
sortOrderWrapper
:
{
height
:
40
,
width
:
40
,
zIndex
:
1000
,
borderRadius
:
15
,
borderTopLeftRadius
:
0
,
borderBottomLeftRadius
:
0
,
borderWidth
:
1
,
overflow
:
"
hidden
"
,
},
sortOrderButton
:
{
height
:
40
,
width
:
40
,
paddingBottom
:
2
,
alignItems
:
"
center
"
,
justifyContent
:
"
center
"
,
borderRadius
:
15
,
borderTopLeftRadius
:
0
,
borderBottomLeftRadius
:
0
,
borderWidth
:
1
,
},
});
export
default
FilterPane
;
components/KeyStatisticsItem.tsx
View file @
e5949bef
import
React
from
"
react
"
;
import
{
View
,
Text
}
from
"
./Themed
"
;
import
{
StyleSheet
}
from
"
react-native
"
;
import
React
from
'
react
'
;
import
{
StyleSheet
}
from
'
react-native
'
;
import
{
Text
,
View
}
from
'
./common/Themed
'
;
interface
KeyStatisticsItemProps
{
title
:
string
;
statistics
:
string
;
}
export
default
function
KeyStatisticsItem
({
statistics
,
title
,
}:
KeyStatisticsItemProps
)
{
export
default
function
KeyStatisticsItem
({
statistics
,
title
}:
KeyStatisticsItemProps
)
{
const
styles
=
StyleSheet
.
create
({
item
:
{
alignItems
:
"
center
"
,
width
:
"
50%
"
,
alignItems
:
'
center
'
,
width
:
'
50%
'
,
marginBottom
:
10
,
},
});
return
(
<
View
style
=
{
styles
.
item
}
lightColor
=
"rgba(0,0,0)"
darkColor
=
"rgba(255,255,255)"
>
<
Text
style
=
{
styles
.
item
}
lightColor
=
"rgba(0,0,0,1)"
darkColor
=
"rgba(255,255,255,1)"
>
<
View
style
=
{
styles
.
item
}
lightColor
=
'rgba(0,0,0)'
darkColor
=
'rgba(255,255,255)'
>
<
Text
style
=
{
styles
.
item
}
lightColor
=
'rgba(0,0,0,1)'
darkColor
=
'rgba(255,255,255,1)'
>
{
title
}
</
Text
>
<
Text
style
=
{
styles
.
item
}
lightColor
=
"rgba(0,0,0,1)"
darkColor
=
"rgba(255,255,255,1)"
>
<
Text
style
=
{
styles
.
item
}
lightColor
=
'rgba(0,0,0,1)'
darkColor
=
'rgba(255,255,255,1)'
>
{
statistics
}
</
Text
>
</
View
>
...
...
components/LoginComponent.tsx
View file @
e5949bef
import
React
,
{
useEffect
,
useState
}
from
'
react
'
;
import
{
useForm
,
SubmitHandler
,
FormProvider
}
from
'
react-hook-form
'
;
import
{
useDispatch
,
useSelector
}
from
'
react-redux
'
;
import
{
signIn
,
signUp
}
from
'
../store/ducks/auth/actions
'
;
import
{
ApplicationState
}
from
'
../store/interface
'
;
import
Logo
from
'
../assets/images/cinema.png
'
;
import
{
FormProvider
,
SubmitHandler
,
useForm
}
from
'
react-hook-form
'
;
import
{
Alert
,
View
,
Text
,
ActivityIndicator
,
Button
,
Image
,
Pressable
,
StyleSheet
,
Text
,
useWindowDimensions
,
Button
,
Pressable
,
ActivityIndicator
,
View
,
}
from
'
react-native
'
;
import
CustomInput
from
'
./CustomInput
'
;
import
AwesomeAlert
from
'
react-native-awesome-alerts
'
;
import
{
useDispatch
,
useSelector
}
from
'
react-redux
'
;
import
Logo
from
'
../assets/images/cinema.png
'
;
import
{
signIn
,
signUp
}
from
'
../store/ducks/auth/actions
'
;
import
{
ApplicationState
}
from
'
../store/interface
'
;
import
CustomInput
from
'
./common/CustomInput
'
;
export
default
function
App
()
{
const
{
height
}
=
useWindowDimensions
();
...
...
@@ -35,8 +34,7 @@ export default function App() {
handleSubmit
,
}
=
methods
;
const
onSubmit
:
SubmitHandler
<
FormValues
>
=
(
values
:
FormValues
)
=>
onFinish
(
values
);
const
onSubmit
:
SubmitHandler
<
FormValues
>
=
(
values
:
FormValues
)
=>
onFinish
(
values
);
interface
FormValues
{
email
:
string
;
password
:
string
;
...
...
@@ -58,7 +56,7 @@ export default function App() {
email
:
values
.
email
,
username
:
values
.
username
||
''
,
password
:
values
.
password
,
})
})
,
);
}
};
...
...
@@ -107,35 +105,31 @@ export default function App() {
return
(
<
View
style
=
{
styles
.
root
}
>
<
Image
source
=
{
Logo
}
style
=
{
[
styles
.
logo
,
{
height
:
height
*
0.3
}]
}
resizeMode
=
"contain"
/>
<
Image
source
=
{
Logo
}
style
=
{
[
styles
.
logo
,
{
height
:
height
*
0.3
}]
}
resizeMode
=
'contain'
/>
<
FormProvider
{
...
methods
}
>
{
isLogin
?
null
:
(
<
CustomInput
required
=
{
isLogin
?
false
:
true
}
error
=
{
errors
.
username
?
true
:
false
}
errorMessage
=
"
Du må fylle inn brukernavn
"
name
=
"
username
"
placeholder
=
"
username
"
errorMessage
=
'
Du må fylle inn brukernavn
'
name
=
'
username
'
placeholder
=
'
username
'
/>
)
}
<
CustomInput
required
=
{
true
}
error
=
{
errors
.
email
?
true
:
false
}
errorMessage
=
"
Du må fylle inn e-post
"
name
=
"
email
"
placeholder
=
"
email
"
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
"
errorMessage
=
'
Passorder må oppfylle krav ...
'
name
=
'
password
'
secureTextEntry
=
{
true
}
placeholder
=
"
password
"
placeholder
=
'
password
'
/>
<
View
style
=
{
styles
.
button
}
>
<
Button
...
...
@@ -151,9 +145,7 @@ export default function App() {
style
=
{
styles
.
container
}
>
<
Text
style
=
{
styles
.
subText
}
>
{
!
isLogin
?
'
Already have a user? Login.
'
:
'
Need a new user? Register.
'
}
{
!
isLogin
?
'
Already have a user? Login.
'
:
'
Need a new user? Register.
'
}
</
Text
>
</
Pressable
>
</
View
>
...
...
@@ -166,12 +158,12 @@ export default function App() {
message
=
{
`Server responded with:
${
alertMessage
}
`
}
closeOnTouchOutside
=
{
true
}
showConfirmButton
=
{
true
}
confirmText
=
"
Ok
"
confirmText
=
'
Ok
'
onConfirmPressed
=
{
()
=>
{
setAlertmessage
(
''
);
}
}
/>
{
loading
&&
<
ActivityIndicator
size
=
"
large
"
color
=
"
#00ff00
"
/>
}
{
loading
&&
<
ActivityIndicator
size
=
'
large
'
color
=
'
#00ff00
'
/>
}
</
View
>
);
}
components/MovieCard.tsx
View file @
e5949bef
import
{
useNavigation
}
from
'
@react-navigation/native
'
;
import
React
,
{
useEffect
,
useState
}
from
'
react
'
;
import
{
Image
,
StyleSheet
,
Text
,
View
}
from
'
react-native
'
;
import
{
Dimensions
,
Image
,
StyleSheet
,
Text
}
from
'
react-native
'
;
import
{
AirbnbRating
}
from
'
react-native-ratings
'
;
import
{
MovieEntity
}
from
'
../store/ducks/movies/types
'
;
import
{
Dimensions
}
from
'
react-native
'
;
import
{
useThemeColor
}
from
'
./Themed
'
;
import
{
useNavigation
}
from
'
@react-navigation/native
'
;
import
{
useThemeColor
,
View
}
from
'
./common/Themed
'
;
interface
MovieCardProps
{
movie
:
MovieEntity
;
...
...
@@ -53,7 +52,13 @@ const MovieCard = (props: MovieCardProps): JSX.Element => {
}
}
style
=
{
[
styles
.
poster
,
{
width
:
iw
(),
height
:
ih
()
}]
}
/>
<
View
style
=
{
[
styles
.
cardInfo
,
{
maxWidth
:
iw
()
}]
}
>
<
View
style
=
{
[
styles
.
cardInfo
,
{
maxWidth
:
iw
()
},
{
backgroundColor
:
useThemeColor
({},
'
component
'
)
},
]
}
>
<
Text
style
=
{
[
styles
.
titleText
,
{
color
:
useThemeColor
({},
'
inputText
'
)
}]
}
>
{
movie
.
title
}
</
Text
>
...
...
components/MovieInfo.tsx
View file @
e5949bef
...
...
@@ -3,31 +3,40 @@ import { useEffect, useState } from 'react';
import
{
ActivityIndicator
,
Button
,
FlatList
,
Image
,
ScrollView
,
StyleSheet
,
useWindowDimensions
,
}
from
'
react-native
'
;
import
AwesomeAlert
from
'
react-native-awesome-alerts
'
;
import
{
useDispatch
,
useSelector
}
from
'
react-redux
'
;
import
{
Movie
Detail
}
from
'
../
models/movieDetail.model
'
;
import
{
rate
Movie
}
from
'
../
store/ducks/auth/actions
'
;
import
{
clearMovie
,
fetchMovieById
}
from
'
../store/ducks/movies/actions
'
;
import
{
MovieState
}
from
'
../store/ducks/movies/types
'
;
import
{
ApplicationState
}
from
'
../store/interface
'
;
import
Seperator
from
'
./common/Seperator
'
;
import
{
Text
,
View
}
from
'
./common/Themed
'
;
import
KeyStatisticsItem
from
'
./KeyStatisticsItem
'
;
import
Pill
from
'
./Pill
'
;
import
Seperator
from
'
./Seperator
'
;
import
{
Text
,
View
}
from
'
./Themed
'
;
import
RatingModal
from
'
./RatingModal
'
;
import
Comment
from
'
./Comment
'
;
interface
MovieInfoProps
{
movieID
:
string
;
}
const
noAlert
=
{
title
:
''
,
message
:
''
,
error
:
false
,
};
export
default
function
MovieInfo
({
movieID
}:
MovieInfoProps
)
{
const
{
height
}
=
useWindowDimensions
();
const
styles
=
StyleSheet
.
create
({
root
:
{
padding
:
20
,
flex
:
1
,
},
logo
:
{
flex
:
1
,
...
...
@@ -61,12 +70,52 @@ export default function MovieInfo({ movieID }: MovieInfoProps) {
marginTop
:
15
,
paddingBottom
:
'
1rem
'
,
},
comments
:
{
flexGrow
:
1
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
textAlign
:
'
center
'
,
marginBottom
:
32
,
},
alert
:
{
width
:
400
,
},
alertError
:
{
color
:
'
red
'
,
},
});
const
dispatch
=
useDispatch
();
const
data
=
useSelector
(({
movies
}:
ApplicationState
)
=>
movies
.
byId
);
const
{
ratings
,
token
,
loading
,
error
}
=
useSelector
((
state
:
ApplicationState
)
=>
state
.
auth
);
const
[
rateModalVisible
,
setRateModalVisible
]
=
useState
(
false
);
const
[
awaitingRating
,
setAwaitingRating
]
=
useState
(
false
);
const
[
alert
,
setAlert
]
=
useState
({
...
noAlert
});
/**
* Callback for rating modal
* @param rating number of stars
* @param comment optional comment
*/
const
dispatchRating
=
(
rating
:
number
,
comment
:
string
)
=>
{
if
(
token
)
{
// If user is logged in, dispatch rateMovie
setAwaitingRating
(
true
);
dispatch
(
rateMovie
({
token
:
token
,
movieId
:
movieID
,
rating
,
comment
}));
}
else
{
// If user is not logged in, dispatch error alert
setAwaitingRating
(
false
);
setAlert
({
title
:
'
Error
'
,
message
:
'
User not logged in
'
,
error
:
true
,
});
}
};
// Fetch movies on load
useEffect
(()
=>
{
dispatch
(
fetchMovieById
({
id
:
movieID
}));
return
()
=>
{
...
...
@@ -74,51 +123,120 @@ export default function MovieInfo({ movieID }: MovieInfoProps) {
};
},
[]);
if
(
data
)
{
useEffect
(()
=>
{
// If we're not loading and awaitingRating is true, we sucessfully rated movie => Show sucess alert
if
(
!
loading
&&
awaitingRating
&&
ratings
)
{
dispatch
(
fetchMovieById
({
id
:
movieID
}));
setAwaitingRating
(
false
);
setAlert
({
title
:
'
Rating succesful
'
,
message
:
'
You have succesfully rated this movie
'
,
error
:
false
,
});
}
},
[
ratings
]);
useEffect
(()
=>
{
// If an error occurs, show an error alert with the error message returned from the server
if
(
error
)
{
setAwaitingRating
(
false
);
setAlert
({
title
:
'
Error
'
,
message
:
'
Server responded with:
'
+
error
,
error
:
true
,
});
}
},
[
error
]);
if
(
loading
||
awaitingRating
)
{
return
(
<
View
style
=
{
{
flex
:
1
,
alignContent
:
'
center
'
,
alignItems
:
'
center
'
,
height
:
'
100%
'
}
}
>
<
ActivityIndicator
size
=
'large'
color
=
'#00ff00'
/>
</
View
>
);
}
else
if
(
data
)
{
return
(
<
ScrollView
style
=
{
styles
.
root
}
>
<
View
style
=
{
{
alignItems
:
'
center
'
}
}
>
<
Image
source
=
{
{
uri
:
data
.
movie
.
poster
,
}
}
style
=
{
[
styles
.
logo
,
{
height
:
height
*
0.5
}]
}
resizeMode
=
'contain'
/>
</
View
>
<
View
style
=
{
{
alignItems
:
'
center
'
}
}
>
<
Text
>
Stars -
{
'
>
'
}
To be implemented
</
Text
>
</
View
>
<
View
>
<
Button
onPress
=
{
()
=>
console
.
log
(
'
To be implemented
'
)
}
title
=
{
'
Give rating
'
}
/>
</
View
>
<
View
style
=
{
styles
.
key_number_container
}
>
<
KeyStatisticsItem
title
=
'RUNTIME'
statistics
=
{
String
(
data
.
movie
.
runtime
)
}
/>
<
KeyStatisticsItem
title
=
'IMDG-RATING'
statistics
=
{
String
(
data
.
movie
.
imdbRating
)
}
/>
<
KeyStatisticsItem
title
=
'RATING'
statistics
=
{
String
(
data
.
movie
.
rating
)
}
/>
<
KeyStatisticsItem
title
=
'PGA-RATING'
statistics
=
{
String
(
data
.
movie
.
rated
)
}
/>
</
View
>
<
Seperator
/>
<
View
>
<
Text
style
=
{
{
fontSize
:
22
}
}
>
{
data
.
movie
.
year
}
</
Text
>
<
Text
style
=
{
{
fontSize
:
18
}
}
>
{
data
.
movie
.
title
}
</
Text
>
</
View
>
<
Seperator
/>
<
Text
>
{
data
.
movie
.
plot
}
</
Text
>
<
Seperator
/>
{
LabelAndText
(
'
DIRECTORS
'
,
'
Joshua King
'
)
}
<
Seperator
/>
{
LabelAndText
(
'
PRODUCTION
'
,
'
Paramount Pictures, W365
'
)
}
<
Seperator
/>
{
LabelAndText
(
'
WRITERS
'
,
'
Phil Hay, Matt Manfredi, Peter Chung
'
)
}
<
Seperator
/>
{
LabelAndText
(
'
STARRING ACTORS
'
,
'
Charlize Theron, Frances McDormand, Sophie Okonedo
'
)
}
<
Seperator
/>
<
View
>
<
Text
>
Reviews -
{
'
>
'
}
To be implemented
</
Text
>
</
View
>
</
ScrollView
>
<
View
style
=
{
{
flex
:
1
,
alignItems
:
'
center
'
}
}
>
<
ScrollView
style
=
{
styles
.
root
}
>
<
View
style
=
{
{
alignItems
:
'
center
'
}
}
>
<
Image
source
=
{
{
uri
:
data
.
movie
.
poster
,
}
}
style
=
{
[
styles
.
logo
,
{
height
:
height
*
0.5
}]
}
resizeMode
=
'contain'
/>
</
View
>
<
View
style
=
{
{
alignItems
:
'
center
'
}
}
>
<
Text
>
Stars -
{
'
>
'
}
To be implemented
</
Text
>
</
View
>
<
View
>
<
Button
onPress
=
{
()
=>
setRateModalVisible
(
true
)
}
title
=
{
'
Give rating
'
}
disabled
=
{
(()
=>
{
return
ratings
.
find
((
rating
)
=>
rating
.
movie
.
id
===
movieID
)
?
true
:
false
;
})()
}
/>
</
View
>
<
View
style
=
{
styles
.
key_number_container
}
>
<
KeyStatisticsItem
title
=
'RUNTIME'
statistics
=
{
String
(
data
.
movie
.
runtime
)
}
/>
<
KeyStatisticsItem
title
=
'IMDG-RATING'
statistics
=
{
String
(
data
.
movie
.
imdbRating
)
}
/>
<
KeyStatisticsItem
title
=
'RATING'
statistics
=
{
String
(
data
.
movie
.
rating
)
}
/>
<
KeyStatisticsItem
title
=
'PGA-RATING'
statistics
=
{
String
(
data
.
movie
.
rated
)
}
/>
</
View
>
<
Seperator
/>
<
View
>
<
Text
style
=
{
{
fontSize
:
22
}
}
>
{
data
.
movie
.
year
}
</
Text
>
<
Text
style
=
{
{
fontSize
:
18
}
}
>
{
data
.
movie
.
title
}
</
Text
>
</
View
>
<
Seperator
/>
<
Text
>
{
data
.
movie
.
plot
}
</
Text
>
<
Seperator
/>
{
LabelAndText
(
'
DIRECTORS
'
,
'
Joshua King
'
)
}
<
Seperator
/>
{
LabelAndText
(
'
PRODUCTION
'
,
'
Paramount Pictures, W365
'
)
}
<
Seperator
/>
{
LabelAndText
(
'
WRITERS
'
,
'
Phil Hay, Matt Manfredi, Peter Chung
'
)
}
<
Seperator
/>
{
LabelAndText
(
'
STARRING ACTORS
'
,
'
Charlize Theron, Frances McDormand, Sophie Okonedo
'
)
}
<
Seperator
/>
<
RatingModal
modalVisible
=
{
rateModalVisible
}
onClose
=
{
()
=>
setRateModalVisible
(
false
)
}
onSubmit
=
{
dispatchRating
}
></
RatingModal
>
<
View
style
=
{
styles
.
comments
}
>
{
data
.
ratings
.
map
((
rating
)
=>
{
return
(
<
Comment
date
=
{
rating
.
date
}
comment
=
{
rating
.
comment
}
username
=
{
rating
.
user
.
username
}
rating
=
{
rating
.
rating
}
key
=
{
rating
.
user
.
id
}
></
Comment
>
);
})
}
</
View
>
</
ScrollView
>
<
AwesomeAlert
contentContainerStyle
=
{
styles
.
alert
}
titleStyle
=
{
alert
.
error
?
styles
.
alertError
:
{}
}
show
=
{
alert
.
title
.
length
>
0
}