작성
·
97
0
FlatList로 게시글 작성 모달 강의의 시작과 같은 코드를 어디서 받을 수 있나요? 스크롤해서 보이는 링크에는 완성된 전체 코드만 있어서요.
일일이 지우고 따라치려해도 나머지 부분이 완성되어 있어서 애뮬레이터에서 진행상황을 볼 수가 없어요...
답변 2
0
0
https://github.com/ZeroCho/threads-clone/tree/9313da137d2520c8a336e9add8ec61a776bca4e4/app
위 링크에서 modal.tsx 만 다음 코드로 바꾼 후 실습 진행하시면 됩니다.
import React, { useState, useRef, useEffect, useCallback } from "react";
import {Add commentMore actions
View,
Text,
TextInput,
TouchableOpacity,
FlatList,
Image,
StyleSheet,
Platform,
Modal as RNModal,
Pressable,
Linking,
Alert,
} from "react-native";
import { useRouter } from "expo-router";
import { Ionicons, FontAwesome } from "@expo/vector-icons";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import * as Location from "expo-location";
interface Thread {
id: string;
text: string;
hashtag?: string;
location?: [number, number];
imageUris: string[];
}
export default function Modal() {
const router = useRouter();
const [threads, setThreads] = useState<Thread[]>([
{ id: Date.now().toString(), text: "", imageUris: [] },
]);
const insets = useSafeAreaInsets();
const [replyOption, setReplyOption] = useState("Anyone");
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
const [isPosting, setIsPosting] = useState(false);
const replyOptions = ["Anyone", "Profiles you follow", "Mentioned only"];
const handleCancel = () => {};
const handlePost = () => {};
const updateThreadText = (id: string, text: string) => {};
const canAddThread = (threads.at(-1)?.text.trim().length ?? 0) > 0;
const canPost = threads.every((thread) => thread.text.trim().length > 0);
const addImageToThread = (id: string, uri: string) => {};
const addLocationToThread = (id: string, location: [number, number]) => {};
const removeThread = (id: string) => {};
const pickImage = async (id: string) => {};
const takePhoto = async (id: string) => {};
const removeImageFromThread = (id: string, uriToRemove: string) => {};
const getMyLocation = async (id: string) => {};
const renderThreadItem = ({
item,
index,
}: {
item: Thread;
index: number;
}) => (
<View style={styles.threadContainer}>
<View style={styles.avatarContainer}>
<Image
source={require("../assets/images/avatar.png")}
style={styles.avatar}
/>
<View style={styles.threadLine} />
</View>
<View style={styles.contentContainer}>
<View style={styles.userInfoContainer}>
<Text style={styles.username}>zerohch0</Text>
{index > 0 && (
<TouchableOpacity
onPress={() => removeThread(item.id)}
style={styles.removeButton}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<Ionicons name="close-outline" size={20} color="#8e8e93" />
</TouchableOpacity>
)}
</View>
<TextInput
style={styles.input}
placeholder={"What's new?"}
placeholderTextColor="#999"
value={item.text}
onChangeText={(text) => updateThreadText(item.id, text)}
multiline
/>
{item.imageUris && item.imageUris.length > 0 && (
<FlatList
data={item.imageUris}
renderItem={({ item: uri, index: imgIndex }) => (
<View style={styles.imagePreviewContainer}>
<Image source={{ uri }} style={styles.imagePreview} />
<TouchableOpacity
onPress={() =>
!isPosting && removeImageFromThread(item.id, uri)
}
style={styles.removeImageButton}
>
<Ionicons
name="close-circle"
size={20}
color="rgba(0,0,0,0.7)"
/>
</TouchableOpacity>
</View>
)}
keyExtractor={(uri, imgIndex) =>
`${item.id}-img-${imgIndex}-${uri}`
}
horizontal
showsHorizontalScrollIndicator={false}
style={styles.imageFlatList}
/>
)}
{item.location && (
<View style={styles.locationContainer}>
<Text style={styles.locationText}>
{item.location[0]}, {item.location[1]}
</Text>
</View>
)}
<View style={styles.actionButtons}>
<Pressable
style={styles.actionButton}
onPress={() => !isPosting && pickImage(item.id)}
>
<Ionicons name="image-outline" size={24} color="#777" />
</Pressable>
<Pressable
style={styles.actionButton}
onPress={() => !isPosting && takePhoto(item.id)}
>
<Ionicons name="camera-outline" size={24} color="#777" />
</Pressable>
<Pressable
style={styles.actionButton}
onPress={() => {
getMyLocation(item.id);
}}
>
<FontAwesome name="map-marker" size={24} color="#777" />
</Pressable>
</View>
</View>
</View>
);
return (
<View style={[styles.container, { paddingTop: insets.top }]} />
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 16,
paddingVertical: 12,
backgroundColor: "#fff",
},
headerRightPlaceholder: {
width: 60,
},
cancel: {
color: "#000",
fontSize: 16,
},
disabledText: {
color: "#ccc",
},
title: {
color: "#000",
fontSize: 16,
fontWeight: "600",
},
list: {
flex: 1,
backgroundColor: "#eee",
},
threadContainer: {
flexDirection: "row",
paddingHorizontal: 20,
paddingTop: 12,
},
avatarContainer: {
alignItems: "center",
marginRight: 12,
paddingTop: 2,
},
avatar: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: "#555",
},
avatarSmall: {
width: 24,
height: 24,
borderRadius: 12,
backgroundColor: "#555",
},
threadLine: {
width: 1.5,
flexGrow: 1,
backgroundColor: "#aaa",
marginTop: 8,
},
contentContainer: {
flex: 1,
paddingBottom: 6,
},
userInfoContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 2,
},
username: {
fontWeight: "600",
fontSize: 15,
color: "#000",
},
input: {
fontSize: 15,
color: "#000",
paddingTop: 4,
paddingBottom: 8,
minHeight: 24,
lineHeight: 20,
},
actionButtons: {
flexDirection: "row",
alignItems: "center",
},
actionButton: {
marginRight: 15,
},
imageFlatList: {
marginTop: 12,
marginBottom: 4,
},
imagePreviewContainer: {
position: "relative",
marginRight: 8,
width: 100,
height: 100,
borderRadius: 8,
overflow: "hidden",
backgroundColor: "#f0f0f0",
},
imagePreview: {
width: "100%",
height: "100%",
},
removeImageButton: {
position: "absolute",
top: 4,
right: 4,
backgroundColor: "rgba(255, 255, 255, 0.8)",
borderRadius: 12,
padding: 2,
},
footer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 16,
paddingTop: 10,
backgroundColor: "#fff",
position: "absolute",
bottom: 0,
left: 0,
right: 0,
},
footerText: {
color: "#8e8e93",
fontSize: 14,
},
postButton: {
paddingVertical: 8,
paddingHorizontal: 18,
backgroundColor: "#000",
borderRadius: 18,
},
postButtonDisabled: {
backgroundColor: "#ccc",
},
postButtonText: {
color: "#fff",
fontSize: 15,
fontWeight: "600",
},
modalOverlay: {
flex: 1,
backgroundColor: "rgba(0, 0, 0, 0.4)",
justifyContent: "flex-end",
},
dropdownContainer: {
backgroundColor: "#fff",
borderRadius: 10,
marginHorizontal: 10,
overflow: "hidden",
marginBottom: 5,
},
dropdownOption: {
paddingVertical: 15,
paddingHorizontal: 20,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: "#e5e5e5",
},
selectedOption: {},
dropdownOptionText: {
fontSize: 16,
color: "#000",
},
selectedOptionText: {
fontWeight: "600",
color: "#007AFF",
},
removeButton: {
padding: 4,
marginRight: -4,
marginLeft: 8,
},
listFooter: {
paddingLeft: 26,
paddingTop: 10,
flexDirection: "row",
},
listFooterAvatar: {
marginRight: 20,
paddingTop: 2,
},
locationContainer: {
marginTop: 4,
marginBottom: 4,
},
locationText: {
fontSize: 14,
color: "#8e8e93",
},
});
완성된 코드는 아래와 같습니다.
https://github.com/ZeroCho/threads-clone/commit/9324ae8de0ffba324310e7ce1d6e9706e8988bf1
수업 노트도 업데이트 해두었습니다.