게시글
질문&답변
카카오 로그인이 안됩니다.
이렇게 해결했습니다. (리디렉션 문제였네요 ㅜㅜ)1. 아래의 코드를 AndroidMainFest.xml 에 추가해주세요(본인의 네이티브 앱 key 입력)(Android 12(API 31) 이상을 타깃으로 하는 앱인 경우, exported 요소를 반드시 "true"로 선언해야 합니다.) //나는 kakao1f0feecdee16fc211676546bf803b882 로 해서 넣었다. 이후 android/app/src/main/res/values/strings.xml 을 열어 다음을 추가 (+ 줄만 추가해주세요)저는 이부분이 없어서 오류가 난 것 같아요. KakaoLoginExample + your_app_key
- 0
- 3
- 202
질문&답변
npm run android 실행시 오류가 발생합니다.
같은 오류 발생하신 분들 있을까봐 남깁니다.우선 \android\app\src\main\AndroidManifest.xml 이 파일 맨 아래에 이렇게 되어있는 부분이 있는데, 이거 삭제하니 본문의 오류는 해결됐는데, 그 이후로 각종 라이브러리 오류가 발생했습니다.그래서 오류나는거 다시 설치해보면서 해봤는데 고쳐질 기미가 안보여서 몇 번 컴퓨터 재부팅하고, 로컬 디스트(C)\users에서 \.gradle 폴더 찾아서 그 안의 caches 폴더 삭제 -> npx rimraf node_modules -> npx rimraf package-lock.json -> npx expo run:android 으로 하니까 재빌드됐습니다....
- 0
- 3
- 65
질문&답변
FirebaseApp 초기화 문제가 발생합니다.
처음에 잘못 작성한 패키지로 빌드된 android 폴더가 있어서 안됐던 것 같습니다. android 폴더 삭제 후 재빌드하니까 해당 오류는 사라지긴 했습니다.
- 0
- 4
- 80
질문&답변
게시글 post 버튼을 클릭한 후에 화면 이동이 발생하지 않습니다
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); router.replace(`/@${data[0].userId}/post/${data[0].id}`); 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, }); }); };.then(data) 이후에 router.replace로 돌아가게는 할 수 있는 것 같은데 toast 메세지가 또 안뜨네요...
- 0
- 5
- 49
질문&답변
게시글 post 버튼을 클릭한 후에 화면 이동이 발생하지 않습니다
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", }, });
- 0
- 5
- 49
질문&답변
게시글 post 버튼을 클릭한 후에 화면 이동이 발생하지 않습니다
const renderThreadItem = ({ item, index, }: { item: Thread; index: number; }) => ( cozyu {index > 0 && ( removeThread(item.id)} style={styles.removeButton} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} > )} updateThreadText(item.id, text)} multiline /> {item.imageUrls && item.imageUrls.length > 0 && ( ( !isPosting && removeImageFromThread(item.id, uri) } style={styles.removeImageButton} > )} keyExtractor={(uri, imgIndex) => `${item.id}-img-${imgIndex}-${uri}` } horizontal showsHorizontalScrollIndicator={false} style={styles.imageFlatList} /> )} {item.location && ( {item.location[0]}, {item.location[1]} )} !isPosting && pickImage(item.id)} > !isPosting && takePhoto(item.id)} > { getMyLocation(item.id); }} > ); return ( Cancel New thread item.id} renderItem={renderThreadItem} ListFooterComponent={ { if (canAddThread) { setThreads((prevThreads) => [ ...prevThreads, { id: Date.now().toString(), text: "", imageUrls: [] }, ]); } }} /> } style={[ styles.list, colorScheme === "dark" ? styles.listDark : styles.listLight, ]} contentContainerStyle={{ backgroundColor: colorScheme === "dark" ? "#101010" : "white", }} keyboardShouldPersistTaps="handled" /> setIsDropdownVisible(false)} > setIsDropdownVisible(false)} > {replyOptions.map((option) => ( { setReplyOption(option); setIsDropdownVisible(false); }} > {option} ))} setIsDropdownVisible(true)}> {replyOption} can reply & quote Post ); }
- 0
- 5
- 49
질문&답변
게시글 post 버튼을 클릭한 후에 화면 이동이 발생하지 않습니다
길어서 나눠서 올렸습니다..!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 ) ); };
- 0
- 5
- 49