질문&답변
const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#fff", }, containerLight: { backgroundColor: "#fff", }, containerDark: { backgroundColor: "#101010", }, header: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", paddingHorizontal: 16, paddingVertical: 12, }, headerLight: { backgroundColor: "#fff", }, headerDark: { backgroundColor: "#101010", }, headerRightPlaceholder: { width: 60, }, cancel: { fontSize: 16, }, cancelLight: { color: "#000", }, cancelDark: { color: "#fff", }, disabledText: { color: "#ccc", }, title: { fontSize: 16, fontWeight: "600", }, titleLight: { color: "#000", }, titleDark: { color: "#fff", }, list: { flex: 1, }, listLight: { backgroundColor: "white", }, listDark: { backgroundColor: "#101010", }, 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, }, usernameLight: { color: "#000", }, usernameDark: { color: "#fff", }, input: { fontSize: 15, paddingTop: 4, paddingBottom: 8, minHeight: 24, lineHeight: 20, }, inputLight: { color: "#000", }, inputDark: { color: "#fff", }, 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, position: "absolute", bottom: 0, left: 0, right: 0, }, footerLight: { backgroundColor: "white", }, footerDark: { backgroundColor: "#101010", }, footerText: { fontSize: 14, }, footerTextLight: { color: "#8e8e93", }, footerTextDark: { color: "#555", }, postButton: { paddingVertical: 8, paddingHorizontal: 18, borderRadius: 18, }, postButtonLight: { backgroundColor: "black", }, postButtonDark: { backgroundColor: "white", }, postButtonDisabledLight: { backgroundColor: "#ccc", }, postButtonDisabledDark: { backgroundColor: "#555", }, postButtonText: { fontSize: 15, fontWeight: "600", }, postButtonTextLight: { color: "white", }, postButtonTextDark: { color: "black", }, modalOverlay: { flex: 1, backgroundColor: "rgba(0, 0, 0, 0.4)", justifyContent: "flex-end", }, dropdownContainer: { width: 200, borderRadius: 10, marginHorizontal: 10, overflow: "hidden", }, dropdownContainerLight: { backgroundColor: "white", }, dropdownContainerDark: { backgroundColor: "#101010", }, dropdownOption: { paddingVertical: 15, paddingHorizontal: 20, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: "#e5e5e5", }, selectedOption: {}, dropdownOptionText: { fontSize: 16, }, dropdownOptionTextLight: { color: "#000", }, dropdownOptionTextDark: { color: "#fff", }, 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", }, });
2025-08-05T08:30:50.413Z
좋아요수
0
댓글수
5
조회수
99
질문&답변
길어서 나눠서 올렸습니다..! import { FontAwesome, Ionicons } from "@expo/vector-icons"; import * as ImagePicker from "expo-image-picker"; import * as Location from "expo-location"; import * as MediaLibrary from "expo-media-library"; import { useRouter } from "expo-router"; import React, { useState } from "react"; import { Alert, FlatList, Image, Linking, Pressable, Modal as RNModal, StyleSheet, Text, TextInput, TouchableOpacity, useColorScheme, View, } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import Toast from "react-native-toast-message"; interface Thread { id: string; text: string; hashtag?: string; location?: [number, number]; imageUrls: string[]; } export function ListFooter({ canAddThread, addThread, }: { canAddThread: boolean; addThread: () => void; }) { return ( Add to thread ); } export default function Modal() { const colorScheme = useColorScheme(); const router = useRouter(); const [threads, setThreads] = useState ([ { id: Date.now().toString(), text: "", imageUrls: [] }, ]); 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 = () => { if (isPosting) return; router.back(); }; const handlePost = () => { console.log("handlePost", threads); const formData = new FormData(); threads.forEach((thread, index) => { formData.append(`posts[${index}][id]`, thread.id); formData.append(`posts[${index}][content]`, thread.text); formData.append(`posts[${index}][userId]`, "cozyu"); formData.append( `posts[${index}][location]`, JSON.stringify(thread.location) ); thread.imageUrls.forEach((imageUrl, imageIndex) => { formData.append(`posts[${index}][imageUrls][${imageIndex}]`, { uri: imageUrl, name: `image_${index}_${imageIndex}.jpeg`, type: "image/jpeg", } as unknown as Blob); }); }); // toast 메시지 Toast.show({ text1: "Posting...", type: "info", visibilityTime: 5000, }); fetch("/posts", { method: "POST", headers: { "Content-Type": "multipart/form-data", }, body: formData, }) .then((res) => res.json()) .then((data) => { console.log("post result", data); Toast.hide(); Toast.show({ text1: "Posted successfully!", type: "success", visibilityTime: 3000, }); }) .catch((err) => { console.error("post error", err); Toast.hide(); Toast.show({ text1: "Post failed!", type: "error", visibilityTime: 3000, }); }); }; const updateThreadText = (id: string, text: string) => { setThreads((prevThreads) => prevThreads.map((thread) => thread.id === id ? { ...thread, text } : thread ) ); }; const canAddThread = (threads.at(-1)?.text.trim().length ?? 0) > 0 || (threads.at(-1)?.imageUrls.length ?? 0) > 0; const canPost = threads.every( (thread) => thread.text.trim().length > 0 || thread.imageUrls.length > 0 ); const removeThread = (id: string) => { setThreads((prevThreads) => prevThreads.filter((thread) => thread.id !== id) ); }; const pickImage = async (id: string) => { let { status } = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (status !== "granted") { Alert.alert( "Photos permission not granted", "Please grant photos permission to use this feature", [ { text: "Open settings", onPress: () => Linking.openSettings() }, { text: "Cancel", }, ] ); return; } let result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ["images", "livePhotos", "videos"], allowsMultipleSelection: true, selectionLimit: 5, }); console.log("image result", result); if (!result.canceled) { setThreads((prevThreads) => prevThreads.map((thread) => thread.id === id ? { ...thread, imageUrls: thread.imageUrls.concat( result.assets?.map((asset) => asset.uri) ?? [] ), } : thread ) ); } }; const takePhoto = async (id: string) => { let { status } = await ImagePicker.requestCameraPermissionsAsync(); if (status !== "granted") { Alert.alert( "Camera permission not granted", "Please grant camera permission to use this feature", [ { text: "Open settings", onPress: () => Linking.openSettings() }, { text: "Cancel", }, ] ); return; } let result = await ImagePicker.launchCameraAsync({ mediaTypes: ["images", "livePhotos", "videos"], allowsMultipleSelection: true, selectionLimit: 5, }); console.log("camera result", result); status = (await MediaLibrary.requestPermissionsAsync()).status; if (status === "granted" && result.assets?.[0].uri) { MediaLibrary.saveToLibraryAsync(result.assets[0].uri); } if (!result.canceled) { setThreads((prevThreads) => prevThreads.map((thread) => thread.id === id ? { ...thread, imageUrls: thread.imageUrls.concat( result.assets?.map((asset) => asset.uri) ?? [] ), } : thread ) ); } }; const removeImageFromThread = (id: string, uriToRemove: string) => { setThreads((prevThreads) => prevThreads.map((thread) => thread.id === id ? { ...thread, imageUrls: thread.imageUrls.filter((uri) => uri !== uriToRemove), } : thread ) ); }; const getMyLocation = async (id: string) => { let { status } = await Location.requestForegroundPermissionsAsync(); console.log("getMyLocation", status); if (status !== "granted") { Alert.alert( "Location permission not granted", "Please grant location permission to use this feature", [ { text: "Open settings", onPress: () => { Linking.openSettings(); }, }, { text: "Cancel", }, ] ); return; } const location = await Location.getCurrentPositionAsync({}); setThreads((prevThreads) => prevThreads.map((thread) => thread.id === id ? { ...thread, location: [location.coords.latitude, location.coords.longitude], } : thread ) ); };
2025-08-05T08:29:15.323Z
좋아요수
0
댓글수
5
조회수
99