Source

src/components/AddLandmark.tsx

import { FontAwesome } from "@expo/vector-icons";
import React, { memo, useEffect, useState } from "react";
import { ActivityIndicator, Dimensions, Image, Keyboard, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
import Picker from 'react-native-picker-select';
import { colors, Icons, IconStrings } from "../globals";
import { Landmark, useLandmarks } from "../hooks/useLandmarks";
import { SecondaryButton } from "./Auth/Buttons";

/**
 * Props for the {@link AddLandmark} component.
 */
export interface AddLandmarkProps {
    /**
     * The {@link landmark} object to be added.
     */
    landmark?: Landmark;
    /**
     * A call back that toggles the visibility state of the {@link AddLandmark} modal. Passed down from {@link AddLandmark}.
     */
    setVisible: (state: boolean) => void;
}

/**
 * Component that renders a form for adding a new {@link Landmark}. Contained within a [react-native-modal]{@link https://github.com/react-native-modal/react-native-modal}.
 * @component
 * @category Map
 */
const AddLandmark: React.FC<AddLandmarkProps> = ({landmark, setVisible}) => {
    const { 
        addLandmarkAsync, 
        resetAddLm, 
        addLandmarkStatus, 
        newLandmark, 
        getLandmarksStatus 
    } = useLandmarks(undefined);

    /**
     * Holds the state of the landmark object to be added.
     */
    const currentLandmarkState = undefined;
    const [currentLandmark, setLandmark] = useState<Landmark | undefined>(currentLandmarkState);

    useEffect(() => {
        /**
         * Updates this component's {@linkcode currentLandmarkState} when the {@linkcode landmark} prop passed down from the parent {@link Map}'s {@linkcode newLandmark} is changed. 
         * Embedded in a useEffect that listens to the {@linkcode landmark} prop.
         * @memberOf AddLandmark
         */
        const updateLandmarkStateFromParent = () => {
            setLandmark(landmark), [landmark]
        }
    })

    useEffect(() => {
        /**
         * Resets the {@link addLandmarkAsync} mutation on successful add.
         * Embedded in a useEffect that listens to the {@link addLandmarkStatus} value from the {@link useLandmarks} hook.
         * @memberOf AddLandmark
         */
        const resetAddMutationOnSuccess = () => {
            if (addLandmarkStatus == 'success') {
                resetAddLm();
            }
        }
        resetAddMutationOnSuccess();
    }, [addLandmarkStatus]);

    /**
     * Calls {@link addLandmarkAsync} from {@link useLandmarks} to initate the process of adding a landmark, then closes the modal.
     */
    const submit = async () => {
        await addLandmarkAsync(currentLandmark)
        setVisible(false);
    }

    /**
     * Closes the modal.
     */
    const close = () => {
        setVisible(false)
    }

    return (
        <View style={{backgroundColor: colors.red, paddingBottom: 20, height: Dimensions.get("window").height * .6, }}>
            {addLandmarkStatus == 'idle' ?
            <>
                <View style={{
                    justifyContent: 'space-between', 
                    alignItems: 'center', 
                    flexDirection: "row", 
                    marginBottom: 15, 
                    borderBottomWidth: 1, 
                    borderBottomColor: 'white', 
                    paddingHorizontal: 20, 
                    paddingVertical: 10}}>
                    <Text style={{color: 'white', fontSize: 15}}>Add landmark here?</Text>
                    <FontAwesome name="times" color='white' size={25} onPress={close} />
                </View>
                <View style={{paddingHorizontal: 20, paddingBottom: 20 }}>
                    <TextInput
                        multiline={true} 
                        style={{backgroundColor: 'white', paddingHorizontal: 10, paddingTop: 10, paddingBottom: 10, marginBottom: 20, height: 150}} 
                        placeholder="Description"
                        onChangeText={value => setLandmark({...currentLandmark, description: value})}>
                        {currentLandmark?.description}
                    </TextInput>
                    <View style={{flexDirection: 'row'}}>
                        <Picker
                            style={{
                                inputIOS: {color: 'white'}, 
                                inputAndroid: {color: 'white'},
                                viewContainer: {marginVertical: 10, flex: 1}, placeholder: {color: 'white'}}}
                            textInputProps={{placeholderTextColor: 'white', selectionColor: 'white'}}
                            Icon={() => <FontAwesome name="chevron-down" color='white' size={20} />}
                            placeholder={{label: "Select a landmark type...", value: 0}}
                            value={currentLandmark?.landmark_type}
                            onValueChange={(value) => {
                                setLandmark({...currentLandmark, landmark_type: value, title: IconStrings[value]})
                                console.log(value)
                            }}
                            useNativeAndroidPickerStyle={true}
                            items={Object.keys(Icons)?.map(icon  => {
                                return (
                                    {label: IconStrings[parseInt(icon)].toUpperCase(), value: icon, key: icon}
                                )})}
                        />
                        {currentLandmark?.landmark_type ? <Image style={{marginLeft: 20}} source={Icons[currentLandmark.landmark_type]}/>
                        : null}
                    </View>
                </View>
                {currentLandmark?.landmark_type ?
                <View style={{justifyContent: 'flex-end', flexDirection: 'row', paddingHorizontal: 20}}>
                    {currentLandmark.description && currentLandmark.title ?
                    <View style={{flexDirection: 'row' }}>
                        <TouchableOpacity onPress={async () => submit()}><Text style={{color: 'white', marginRight: 20}}>Add</Text></TouchableOpacity>
                        <TouchableOpacity onPress={close}><Text style={{color: 'white'}}>Cancel</Text></TouchableOpacity>
                    </View> : null}
                </View> : null}
                <TouchableWithoutFeedback style={{height: '100%'}} onPress={() => Keyboard.dismiss()}><Text></Text></TouchableWithoutFeedback>
            </> :
            <View style={{height: '100%', justifyContent: "space-evenly", alignItems: "center"}}>
                <Text style={{color: 'white', fontSize: 20}}>{
                    addLandmarkStatus == "loading" ? 'Uploading landmark...' :
                    addLandmarkStatus == "error" ? 'Something went wrong when trying to upload the landmark.' : null }
                </Text>
                {
                    addLandmarkStatus == "loading" ? <ActivityIndicator color='white' size="large"/> :
                    addLandmarkStatus == "error" ? <SecondaryButton text="Okay" onPress={close}/> : null
                }
            </View> }
        </View>
    )
}

export default memo(AddLandmark);