app/screens/DishPage.js

import React, { useEffect, useState } from "react";
import { LogBox } from "react-native";
import {
  Dimensions,
  Image,
  ScrollView,
  StyleSheet,
  Text,
  View,
  Modal,
  Alert,
  TouchableOpacity
} from "react-native";
import { Button, Icon, Divider, Rating } from "react-native-elements";
import { color } from "react-native-reanimated";
import { SafeAreaView } from "react-native-safe-area-context";
import Carousel, { Pagination } from "react-native-snap-carousel";
import MapView, { Marker, Polyline } from 'react-native-maps';
import PropTypes, { any } from "prop-types";
import Reviews from "../components/Reviews";
import colors from "../config/colors";
import { getChefInfo, getDishInfo, getNDishReviews } from "../util/Queries";
import { addToCart, addQuantity } from "../components/ShoppingCart";
import Chef from "../objects/Chef";
import Dish from "../objects/Dish";
import coordDist from "../util/CoordDist";

const SLIDER_WIDTH = Dimensions.get("window").width;
const ITEM_WIDTH = Math.round(SLIDER_WIDTH);

function remove(count, setCount) {
  if (count > 0) {
    setCount(count - 1);
  }
}
function close(setVisible, hideModal) {
  setVisible(false);
  hideModal();
}
const CarouselCardItem = ({ item, index }) => {
  return <Image source={item.image} style={styles.carouselItem} />;
};

/**
 * 
 * @typedef DishPageProps
 * @memberof DishPage
 * @property {Dish} Dish - dish object defined in Dish.js
 * @property {boolean} visible - set visibility of modal
 * @property {int} dishid - dishid of the given dish 
 * @property {function} hideModal - function to hide the given component
 * @property {Object} navigation - Stack Navigation Object
 * 
 */
/**
 * A page to view aa dish and its information, reviews and chef
 * @class DishPage
 * @param {DishPageProps} props 
 */
function DishPage(props) {
  LogBox.ignoreLogs([
    "Non-serializable values were found in the navigation state"
  ]);

  const [count, setCount] = useState(0);
  const [modalVisible, setVisible] = useState(false);
  const [index, setIndex] = React.useState(0);
  const isCarousel = React.useRef(null);
  const [first5Reviews, setFirst5Reviews] = useState(null);
  const [dish, setDish] = useState(props.Dish!=null ? props.Dish : new Dish({}));
  var location=dish.Chef != null ? JSON.parse(dish.Chef.location) : {'latitude':0, 'longitude':0};
  location['latitudeDelta'] = 0.007;
  location['longitudeDelta'] = 0.007;
  const [region, setRegion] = useState(location);
  const [userLoc, setUserLoc] = useState(null);

  useEffect(() => {
      if(dish.dishid != null){
          getNDishReviews(dish.dishid, 5).then(function(results) {
              setFirst5Reviews(results);
          }, () => {console.log("Error in useEffect getNDishReviews")})
          .catch(err => {console.log("use Effect Err Get N Dish Reviews: ", err)});
      }

      navigator.geolocation.getCurrentPosition((pos) => {
        setUserLoc(pos);
        var loc = {};
        loc['latitude'] = (pos.coords.latitude + location.latitude)/2.0
        loc['longitude'] = (pos.coords.longitude + location.longitude)/2.0
        loc['latitudeDelta'] = Math.abs(pos.coords.latitude-loc.latitude)*(loc.latitude > pos.coords.latitude ? 10: 3);
        loc['longitudeDelta'] = Math.abs(pos.coords.longitude-loc.longitude)*1.5;
        setRegion(loc);
      }, (err) => {
        console.warn(`ERROR(${err.code}): ${err.message}`);
      }, {
        enableHighAccuracy: true, timeout: 5000, maximumAge: 0
      });
  }, []);

  useEffect(() => {
    setVisible(props.visible);
  });

  useEffect(()=> {
      if(props.dishid!=null){
          refresh(props.dishid);
      }
      
  }, [props.dishid])

  function refresh(dishid){
    getNDishReviews(dishid, 5)
    .then(
      function(results) {
        setFirst5Reviews(results);
      },
      () => {
        console.log("Error in refresh getNDishReviews");
      }
    )
    .catch(err => {
      console.log("use Effect Err Get N Dish Reviews: ", err);
    });

    getDishInfo(dishid)
    .then(
        function(results){
            var d = new Dish(results[0]);
            getChefInfo(d.chefid).then(function(results){
                var chef = new Chef(results[0]);
                d.setChef(chef)
                setDish(d); 
            }, () => {console.log("Error")})
            .catch((err) => {console.log("Refresh get Chef error: ", err)})
        },
        () => {
            console.log("Error in refresh getDishInfo");
        }
    )
    .catch(err => {
        console.log("refresh Err get dish info: ", err);
    });
  }

  function onPressChef(){
    if(dish.Chef == null) {return;}
      close(setVisible, props.hideModal)
      props.navigation.navigate("Chef", {
        Chef: dish.Chef
      })
  }

  let carouselData = [];
  if (dish.imagesURLs != null) {
    dish.imagesURLs.forEach(imageURL => {
      if (imageURL != null) {
        carouselData.push({ image: { uri: imageURL } });
      }
    });
  }

  if(dish == null){return(<View></View>);}
  return (
    <Modal
      animationType="slide"
      transparent={true}
      visible={modalVisible}
      swipeDirection="down"
      onRequestClose={() => {
        close(setVisible, props.hideModal);
      }}
    >
      <View style={styles.modalContainer}>
        <ScrollView
          style={styles.scrollView}
          showsVerticalScrollIndicator={false}
          alwaysBounceHorizontal={false}
          alwaysBounceVertical={false}
        >
          <View style={styles.modalView}>
            <View style={styles.carouselContainer}>
              <Carousel
                layout="default"
                data={carouselData}
                useScrollView={true}
                renderItem={CarouselCardItem}
                sliderWidth={ITEM_WIDTH}
                sliderHeight={Math.round(ITEM_WIDTH * (3.0 / 4.0))}
                itemWidth={ITEM_WIDTH}
                itemHeight={Math.round(ITEM_WIDTH * (3.0 / 4.0))}
                onSnapToItem={index => setIndex(index)}
                useScrollView={true}
                ref={isCarousel}
              />
            </View>
            <Divider style={styles.divider2} />
            <View style={styles.textContainer}>
              <View style={styles.title}>
                <Text style={styles.titleText}>{dish.name}</Text>
                <Text adjustsFontSizeToFit={true} numberOfLines={1} style={styles.price}>${dish.price}</Text>
              </View>
              <View style={styles.ratingsContainer}>
                <Rating
                  style={styles.rating}
                  readonly={true}
                  imageSize={20}
                  fractions={1}
                  startingValue={dish.rating ? dish.rating : 0.0}
                />
                <Text style={styles.numReviews}>
                  ({dish.numReviews} Reviews)
                </Text>
              </View>
              <View style={styles.spacer} />

              <Text style={styles.descriptionText}>
                {dish.description}
              </Text>
              <Divider style={styles.divider} />
              <Text style={styles.descriptionText}>
                Ingredients: {dish.ingredients}
              </Text>
              <Divider style={styles.divider} />
              <Text style={styles.descriptionText}>
                Estimated Time: {dish.timeString}
              </Text>
              <View style={styles.spacer}></View>
              {dish.Chef != null &&
              <View style={styles.chefInfoContainer}>
                <TouchableOpacity style={styles.chefPicHolder} onPress={onPressChef}>
                  <Image style={styles.chefPic} source={{uri: dish.Chef.profilePicURL}}/>
                </TouchableOpacity>
                <TouchableOpacity style={styles.chefTextContainer} onPress={onPressChef}>
                  <Text style={styles.chefNameText}>{dish.Chef.name}</Text>
                  <Text style={styles.chefSubtitleText}>{dish.Chef.shortDesc}</Text>
                  <Rating
                    style={{marginTop: 4}}
                    readonly={true}
                    imageSize={15}
                    fractions={1}
                    startingValue={dish.Chef.rating ? dish.Chef.rating : 0.0}
                  />
                </TouchableOpacity>
                <View style={styles.chefMapContainer}>
                  <MapView style={styles.map}
                    region={region}
                    showsUserLocation={true}
                    showsMyLocationButton={true}
                    showsTraffic={true}
                    pitchEnabled={false}
                  >
                    <Marker
                      coordinate={{latitude: location.latitude, longitude: location.longitude}}
                    />
                  </MapView>
                  {userLoc && <Text style={styles.distanceText}>
                            {coordDist(location.latitude, location.longitude, userLoc.coords.latitude, userLoc.coords.longitude).toFixed(2)} miles away
                  </Text>}
                </View>
              </View>
              }
              <View style={styles.spacer}></View>
              {dish.numReviews!=null &&
              <Reviews
                rating={dish.rating}
                numReviews={dish.numReviews}
                reviews={first5Reviews}
                dishid={dish.dishid}
                refresh={() => {refresh(dish.dishid)}}
                navigation={props.navigation}
              />
              }
              <View style={styles.spacer} />
              <View style={styles.spacer} />
            </View>
          </View>
        </ScrollView>
        <View style={styles.closeButton}>
          <Button
            onPress={() => close(setVisible, props.hideModal)}
            buttonStyle={styles.closeButtonStyle}
            icon={
              <Icon
                name="close"
                size={25}
                color="white"
                style={{
                  backgroundColor: "black",
                  borderRadius: 15,
                  outline: "white solid 1px"
                }}
              />
            }
          />
        </View>
        <View style={styles.checkout}>
          <Button
            type="solid"
            title=" - "
            onPress={() => remove(count, setCount)}
            titleStyle={styles.buttonText}
            buttonStyle={styles.minusButton}
          />
          <Text style={styles.countStyle}>{count}</Text>
          <Button
            type="solid"
            style={styles.plusButtonPadding}
            title=" + "
            onPress={() => setCount(count + 1)}
            titleStyle={styles.buttonText}
            buttonStyle={styles.plusButton}
          />
          <Button
            type="solid"
            title="Add to Cart"
            titleStyle={styles.addToCartText}
            buttonStyle={styles.addToCartButton}
            icon={
              <Icon
                style={styles.cartPadding}
                name="basket"
                type="simple-line-icon"
                size={20}
                color="white"
              />
            }
            onPress={() => {
              if (count > 0) {
                const found = global.cart.findIndex(
                  item => item["dish"] == dish
                );
                if (found < 0) {
                  const d = dish;
                  addToCart({ dish, count });
                } else {
                  addQuantity(found, count);
                }
                Alert.alert(
                  "Added to Cart",
                  "Successfully added items to cart!"
                );
              }
            }}
          />
        </View>
      </View>
    </Modal>
  );
}


DishPage.propTypes = {
  navigation: any.isRequired
};

const styles = StyleSheet.create({
  scrollView: {
    width: "100%",
    height: "80%"
  },

  modalContainer: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: colors.background
  },

  modalView: {
    backgroundColor: colors.background,
    alignItems: "flex-start",
    width: "100%",
    minHeight: "100%"
  },

  carouselContainer: {
    width: "100%"
  },

  closeButton: {
    alignSelf: "flex-end",
    position: "absolute",
    top: 20,
    right: 0
  },

  closeButtonStyle: {
    backgroundColor: "transparent"
  },

  carouselItem: {
    width: ITEM_WIDTH,
    height: Math.round(ITEM_WIDTH * (3.0 / 4.0))
  },

  dots: {
    position: "absolute",
    alignSelf: "center",
    bottom: 0
  },

  dotStyle: {
    width: 10,
    height: 10,
    borderRadius: 5,
    marginHorizontal: 0,
    backgroundColor: colors.black
  },

  closeButton: {
    alignSelf: "flex-end",
    position: "absolute",
    top: 40,
    right: 0
  },

  closeButtonStyle: {
    backgroundColor: "transparent"
  },

  minusButton: {
    borderBottomLeftRadius: 20,
    borderTopLeftRadius: 20,
    backgroundColor: colors.secondary,
    marginBottom: 10
  },

  plusButton: {
    borderBottomRightRadius: 20,
    borderTopRightRadius: 20,
    backgroundColor: colors.secondary,
    marginBottom: 10
  },

  plusButtonPadding: {
    paddingRight: 10
  },

  addToCartButton: {
    borderRadius: 20,
    backgroundColor: colors.secondary,
    paddingLeft: 10,
    marginBottom: 10
  },

  addToCartText: {
    fontWeight: "bold",
    paddingRight: 10
  },

  buttonText: {
    fontWeight: "bold"
  },

  cartPadding: {
    paddingLeft: 10,
    paddingRight: 10
  },

  textContainer: {
    flex: 5,
    backgroundColor: "white",
    paddingTop: "0%",
    width: "100%",
    alignItems: "flex-start",
    flexDirection: "column"
  },

  title: {
    flexDirection: "row",
    justifyContent: "space-between",
    width: "100%",
    fontFamily: "Avenir"
  },

  divider: {
    width: "90%",
    alignSelf: "center",
    backgroundColor: colors.secondary,
    height: 1
  },

  divider2: {
    width: "100%",
    alignSelf: "center",
    backgroundColor: colors.secondary,
    height: 1
  },
  titleText: {
    fontSize: 30,
    padding: 10,
    color: "black",
    fontWeight: "bold",
    fontFamily: "Avenir",
    maxWidth: "80%"
  },

  countStyle: {
    fontSize: 20,
    padding: 10,
    color: "white",
    height: 40,
    fontWeight: "bold",
    fontFamily: "Avenir",
    marginBottom: 10
  },

  price: {
    alignSelf: "flex-start",
    fontSize: 30,
    padding: 10,
    color: colors.black,
    fontWeight: "bold",
    fontFamily: "Avenir",
    maxWidth: "30%"
  },

  description: {
    alignSelf: "flex-start",
    flex: 2,
    paddingLeft: 20
  },

  descriptionText: {
    fontSize: 20,
    padding: 10,
    color: "gray",
    fontFamily: "Avenir"
  },

  checkout: {
    flex: 1,
    backgroundColor: colors.primary,
    alignItems: "center",
    justifyContent: "center",
    flexDirection: "row",
    width: "100%"
  },
  chefInfoContainer:{
    width: '100%',
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center'
  },
  chefPic: {
    width: SLIDER_WIDTH*0.2,
    height: SLIDER_WIDTH*0.2,
    borderRadius: SLIDER_WIDTH*0.1,
  },
  chefPicHolder: {
    height: SLIDER_WIDTH*0.2,
    width: SLIDER_WIDTH*0.2,
    margin: '5%'
  },
  chefTextContainer:{
    width: '40%',
    alignSelf: 'center',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center'
  },
  chefNameText:{
    fontSize: 20,
    color: colors.secondary,
    fontWeight: 'bold',
    fontFamily: "Avenir",
    alignContent: 'center',
    textAlign: 'center'
  },
  chefSubtitleText:{
    fontSize: 12,
    color: 'grey',
    fontFamily: "Avenir",
    textAlign: 'center'
  },
  chefMapContainer:{
    width: '20%',
    marginHorizontal: '5%',
    marginVertical: '2.5%',
  },
  map:{
    height: SLIDER_WIDTH*0.2,
    width: SLIDER_WIDTH*0.2,
    borderRadius: SLIDER_WIDTH*0.02
  },
  distanceText: {
    fontSize: 10,
    color: 'black',
    fontFamily: "Avenir",
    textAlign: 'center',
    fontWeight: 'bold',
    marginTop: 5
  },
  ratingsContainer: {
    width: "100%",
    flexDirection: "row",
    alignContent: "center",
    alignItems: "center",
    justifyContent: "flex-start",
    marginBottom: 15,
    marginLeft: 10
  },
  numReviews: {
    marginLeft: 5,
    fontSize: 18,
    color: "grey"
  },
  spacer: {
    height: 10,
    backgroundColor: colors.background,
    width: "100%"
  }
});

export default DishPage;