import { FontAwesome } from "@expo/vector-icons";
import React, { memo, useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, Dimensions, Image, Keyboard, ListRenderItem, StyleSheet, Text, TextInput, View } from "react-native";
import { ScrollView, TouchableWithoutFeedback } from "react-native-gesture-handler";
import Picker from "react-native-picker-select";
import { colors, Icons, IconStrings } from "../globals";
import { Landmark, LMComment, useLandmarks } from "../hooks/useLandmarks";
import { authStore } from "../stores/AuthStore";
import { PrimaryButton } from "./Auth/Buttons";
/**
* Props for the {@link Comment} component.
*/
export interface CommentProps {
/**
* The [comment]{@link LMComment} object being displayed by the {@link Comment} component.
*/
comment: LMComment
}
/**
* Component that displays a {@link LMComment} object in a clean format.
* @component
*/
export const Comment: React.FC<CommentProps> = ({comment}) => (
<View>
<Text testID="comment-text">{comment.text}</Text>
</View>
)
/**
* Props for the {@link LandmarkDetails} component.
*/
export interface LandmarkDetailsProps {
/**
* The {@link Landmark} object being displayed/edited in the {@link LandmarkDetails} modal.
*/
landmark: Landmark | undefined
/**
* A callback passed from the parent {@link Map} that toggles the visibility of the {@link LandmarkDetails} modal.
*/
setVisible: (state: boolean) => void;
/**
* A callback passed from the parent {@link Map} that toggles the ability to edit the {@link Landmark} in {@link LandmarkDetails} modal.
*/
setEditing: (state: boolean) => void;
/**
* A flag that determines whether the properties of the {@link Landmark} displayed in the {@link LandmarkDetails} modal can be edited
*/
editingEnabled: boolean
}
/**
* Component that renders the details of a selected {@link Landmark} and allows the user to edit those details. Contained within a [react-native-modal]{@link https://github.com/react-native-modal/react-native-modal}.
* @component
* @category Map
*/
const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({landmark, setVisible, setEditing, editingEnabled}) => {
/**
* Holds the state of the {@link Landmark} being displayed.
*/
const selectedLandmarkState = undefined;
const [selectedLandmark, setLandmark] = useState<Landmark | undefined>(selectedLandmarkState);
/**
* Holds state of a {@link Landmark} object parallel to {@linkcode selectedLandmarkState} that is manipulated when {@linkcode editingEnabled} is true.
*/
const updatedLandmarkState = undefined;
const [updatedLandmark, setUpdatedLandmark] = useState<Landmark | undefined>(updatedLandmarkState);
const {
updateLandmark, updateLandmarkStatus, resetUpdateLm,
deleteLandmark, deleteLandmarkStatus, resetDeleteLm,
} = useLandmarks(undefined);
useEffect(() => {
/**
* Updates this component's {@linkcode selectedLandmarkState} when the {@linkcode landmark} prop passed down from the parent {@link Map}'s {@linkcode selectedLandmark} is changed.
* Embedded in a useEffect that listens to the {@linkcode landmark} prop.
* @memberOf LandmarkDetails
*/
const updateLandmarkStateFromParent = () => {
setLandmark(landmark)
};
updateLandmarkStateFromParent();
}, [landmark])
useEffect(() => {
/**
* Resets the {@linkcode updateLandmark} mutation on successful add.
* Embedded in a useEffect that listens to the {@linkcode updateLandmarkStatus} value from the {@link useLandmarks} hook.
* @memberOf LandmarkDetails
*/
const resetUpdateMutationOnSuccess = () => {
if (updateLandmarkStatus == 'success') {
resetUpdateLm();
setLandmark(updatedLandmark)
}
}
resetUpdateMutationOnSuccess();
}, [updateLandmarkStatus]);
useEffect(() => {
/**
* Resets the {@linkcode deleteLandmark} mutation on successful delete.
* Embedded in a useEffect that listens to the {@linkcode deleteLandmarkStatus} value from the {@link useLandmarks} hook.
* @memberOf LandmarkDetails
*/
const resetDeleteMutationOnSuccess = () => {
if (deleteLandmarkStatus == 'success') {
resetDeleteLm();
}
}
resetDeleteMutationOnSuccess();
}, [deleteLandmarkStatus]);
/**
* Toggles whether or not the {@linkcode selectedLandmark} can be edited through the {@linkcode editingEnabled} prop.
*/
const toggleEditing = (editingState: boolean) => {
if (editingState) { setUpdatedLandmark(selectedLandmark); }
else { setUpdatedLandmark(undefined); }
setEditing(editingState);
}
/**
* Calls the {@linkcode updateLandmark} mutation from the {@link useLandmarks} hook and closes the modal once finished.
*/
const editLandmark = async () => {
if (updatedLandmark) {
await updateLandmark(updatedLandmark);
}
setEditing(false);
}
/**
* Calls the {@linkcode deleteLandmark} mutation from the {@link useLandmarks} hook and closes the modal once finished.
*/
const removeLandmark = async () => {
await deleteLandmark(selectedLandmark?.id);
setVisible(false);
}
/**
* Renders all [comments]{@link LMComment} associated with the {@linkcode selectedLandmark} as items for the [FlatList]{@link https://reactnative.dev/docs/flatlist} in this component.
*/
const renderComment: ListRenderItem<LMComment> = useCallback(({item}) => (
<Comment comment={item} />
), []);
return (
<View style={[styles.container, editingEnabled ? {height: Dimensions.get("window").height * .4,} : {height: Dimensions.get("window").height * .4,}]}>
{updateLandmarkStatus == "idle" && deleteLandmarkStatus == "idle" ?
<>
<View style={styles.detailsHeader}>
{authStore.userId !== selectedLandmark?.user ? null : !editingEnabled ?
<View style={{flexDirection: 'row', alignItems: "center"}}>
<FontAwesome style={{marginRight: 20, marginTop: 2}} size={25} color="white" name="edit" onPress={() => toggleEditing(true)}/>
<FontAwesome color="white" size={25} name="trash" onPress={async () => removeLandmark()}/>
</View> :
<View style={{flexDirection: 'row', alignItems: "center",}}>
<FontAwesome color="white" size={25} name="close" onPress={() => toggleEditing(false)}/>
</View>}
{!editingEnabled ?
<FontAwesome color="white" size={25} name="close" onPress={() => setVisible(false)}/>:
updatedLandmark?.description && updatedLandmark.landmark_type ?
<FontAwesome style={{marginTop: 2}} size={25} color="white" name="check" onPress={async () => editLandmark()}/> : null}
</View>
<View style={styles.detailsContainer}>
{editingEnabled ?
<View>
<View style={{flexDirection: 'row', marginBottom: 20, justifyContent: "space-between"}}>
{updatedLandmark?.landmark_type ?
<>
<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={
selectedLandmark?.landmark_type ?
{label: IconStrings[selectedLandmark.landmark_type].toUpperCase(), value: selectedLandmark.landmark_type} :
{label: "Select landmark type", value: 0} }
value={updatedLandmark.landmark_type}
onValueChange={(value) => {
setUpdatedLandmark({...updatedLandmark, landmark_type: value, title: IconStrings[value]})
}}
useNativeAndroidPickerStyle={true}
items={Object.keys(Icons)?.filter(icon => parseInt(icon) != selectedLandmark?.landmark_type).map(icon => {
return (
{label: IconStrings[parseInt(icon)].toUpperCase(), value: icon, key: icon}
)})}
/>
<Image style={{marginLeft: 20}} source={Icons[updatedLandmark.landmark_type]}/>
</>
: null}
</View>
<TouchableWithoutFeedback style={{marginBottom: 20}} onPress={() => Keyboard.dismiss()}>
<ScrollView style={{backgroundColor: 'white'}}>
<TextInput
multiline={true}
style={[styles.input, {fontSize: 13, marginBottom: 10}]}
onChangeText={(value) => setUpdatedLandmark({...updatedLandmark, description: value})} >
{updatedLandmark?.description}
</TextInput>
</ScrollView>
</TouchableWithoutFeedback>
</View>:
<View style={{flexDirection: 'row'}}>
<View style={{flex: 8, flexDirection: 'column', marginBottom: 20}}>
<Text style={{color: 'white', marginBottom: 10, fontSize: 15}}>{selectedLandmark?.title?.toUpperCase()}</Text>
<ScrollView>
<Text style={{color: 'white', fontSize: 13}}>{selectedLandmark?.description}</Text>
</ScrollView>
</View>
{selectedLandmark?.landmark_type ? <Image source={Icons[selectedLandmark.landmark_type]} /> : null}
</View>}
<TouchableWithoutFeedback style={{height: '100%'}} onPress={() => Keyboard.dismiss()}><Text></Text></TouchableWithoutFeedback>
{/* {!editingEnabled ?
<View style={styles.commentContainer}>
<Text style={{color: 'white', marginBottom: 20}}>Comments: </Text>
<FlatList<LMComment>
keyExtractor={i => i.id}
data={[
{id: '1', text: 'Hello', poster: 'chase'}, {id: '3', text: 'Hello', poster: 'chase'}, {id: '2', text: 'Hello', poster: 'chase'},
{id: '4', text: 'Hello', poster: 'chase'}, {id: '5', text: 'Hello', poster: 'chase'}, {id: '6', text: 'Hello', poster: 'chase'},
{id: '9', text: 'Hello', poster: 'chase'}, {id: '8', text: 'Hello', poster: 'chase'}, {id: '7', text: 'Hello', poster: 'chase'},
{id: '12', text: 'Hello', poster: 'chase'}, {id: '10', text: 'Hello', poster: 'chase'}, {id: '11', text: 'Hello', poster: 'chase'},
]}
renderItem={renderComment}
style={{backgroundColor: 'white'}}/>
</View> : null} */}
</View>
</> :
<View style={{height: '100%', justifyContent: "space-evenly", alignItems: "center", marginHorizontal: 20}}>
<Text style={{color: 'white', fontSize: 20}}>{
deleteLandmarkStatus == 'loading' ? "Deleting landmark..." :
deleteLandmarkStatus == 'error' ? "Something went wrong trying to delete the landmark" :
updateLandmarkStatus == 'loading' ? "Updating landmark..." :
updateLandmarkStatus == 'error' ? "Something went wrong trying to update the landmark" : null}
</Text>
{
deleteLandmarkStatus == 'loading' || updateLandmarkStatus == 'loading' ? <ActivityIndicator color='white' size="large"/> :
deleteLandmarkStatus == 'error' || updateLandmarkStatus == 'error' ? <PrimaryButton text="Okay" style={{borderColor: 'white', borderWidth: 1}} onPress={() => setVisible(false)} /> : null
}
</View> }
</View>
)
}
const styles = StyleSheet.create({
container: {
backgroundColor: colors.red,
},
detailsHeader: {
borderBottomWidth: 1,
borderColor: 'white',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#df3f3f',
marginBottom: 10,
paddingVertical: 10,
paddingHorizontal: 20
},
detailsContainer: {
flex: 1,
marginHorizontal: 20
},
commentContainer: {
flex: 5,
marginBottom: 40
},
title: {
},
input: {
padding: 5,
color: 'black'
}
})
export default memo(LandmarkDetails);
Source