React Native 시작(5) - frontend(1)

조영문·2024년 12월 26일
0

React Native

목록 보기
6/6

  • app.js

    stack navigation 사용

import React, { useEffect } from 'react';
import { Alert, BackHandler, Button, FlatList, SafeAreaView, StyleSheet, Text, TextInput, View } from 'react-native';
import { createStackNavigator } from '@react-navigation/stack';
import { CommonActions, NavigationContainer, StackActions, useNavigation } from '@react-navigation/native';
import Login from './src/screens/Login';
import List from './src/screens/List';
import DetailA from './src/screens/DetailA';
import DetailBC from './src/screens/DetailBC';
import Camera from './src/components/Camera';
import Ionicons from '@expo/vector-icons/Ionicons';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { tr } from 'date-fns/locale';
import { useReactQueryDevTools } from '@dev-plugins/react-query';
import { QueryClient, useQuery } from '@tanstack/react-query';

const Stack = createStackNavigator();

const queryClient = new QueryClient();

export default function App({ route }) {
    useEffect(() => {
        // const gestureHandler = navigation.addListener('beforeRemove', (e) => {
        //     e.preventDefault();
        //     alert('뒤로가기 금지');
        // });
        const backAction = () => {
            Alert.alert('앱 종료', '앱 종료하시겠습니까?', [
                { text: '취소', onPress: () => {}, style: 'cancel' },
                { text: '확인', onPress: () => BackHandler.exitApp() },
            ]);
            return true;
        };
        const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction);
        return () => {
            backHandler.remove();
        };
    });
    return (
        <NavigationContainer>
            <Stack.Navigator
                screenOptions={({ navigation }) => ({
                    // headerLeft: () => null,
                    headerRight: () => {
                        return (
                            <Ionicons
                                name="exit"
                                size={30}
                                color="#fff"
                                onPress={() => {
                                    Alert.alert('로그아웃', '로그아웃하시겠어요?', [
                                        {
                                            text: '취소',
                                            onPress: () => {},
                                            style: 'cancel',
                                        },
                                        {
                                            text: '확인',
                                            onPress: async () => {
                                                try {
                                                    await AsyncStorage.removeItem('id');
                                                    await AsyncStorage.removeItem('accesstoken');
                                                    await AsyncStorage.clear();
                                                    navigation.dispatch(
                                                        CommonActions.reset({
                                                            index: 0,
                                                            routes: [{ name: 'Login' }],
                                                        })
                                                    );
                                                } catch (error) {
                                                    console.log(error);
                                                }
                                            },
                                        },
                                    ]);
                                }}
                                style={{ marginRight: 20 }}
                            />
                        );
                    },
                    headerTitleAlign: 'center',
                    headerStyle: {
                        backgroundColor: '#096e5b',
                    },
                    headerTintColor: '#fff',
                    headerTitleStyle: {
                        fontWeight: 'bold',
                        overflow: 'scroll',
                    },
                })}
                initialRouteName="Login"
            >
                <Stack.Screen
                    name="Login"
                    component={Login}
                    options={{
                        headerShown: false,
                    }}
                />
                <Stack.Screen
                    name="List"
                    component={List}
                    options={(route) => ({
                        headerTitle: () => {
                            return (
                                // ${route.route.params}
                                <Text
                                    style={styles.headerTitle}
                                    numberOfLines={2}
                                >{`${route.route.params.id}\n목록화면`}</Text>
                            );
                        },
                    })}
                />
                <Stack.Screen
                    name="DetailA"
                    component={DetailA}
                    options={(route) => ({
                        headerTitle: () => {
                            return (
                                <Text style={styles.headerTitle} numberOfLines={2}>
                                    {`${route.route.params.id}\n${`상세화면`} `}
                                </Text>
                            );
                        },
                    })}
                />
                <Stack.Screen
                    name="DetailBC"
                    component={DetailBC}
                    options={(route) => ({
                        headerTitle: () => {
                            return (
                                <Text style={styles.headerTitle} numberOfLines={2}>
                                    {`${route.route.params.id}\n${`${route.route.params.item.boardState}  화면`} `}
                                </Text>
                            );
                        },
                    })}
                />
                <Stack.Screen
                    name="Camera"
                    component={Camera}
                    options={({ navigation }) => ({
                        headerLeft: () => {
                            return (
                                <Ionicons
                                    name="arrow-back"
                                    size={30}
                                    color="#fff"
                                    onPress={() => {
                                        console.log('headerLeft goBack');
                                        if (navigation.canGoBack()) {
                                            navigation.goBack();
                                        }
                                    }}
                                    style={{ marginLeft: 20 }}
                                />
                            );
                        },
                    })}
                />
            </Stack.Navigator>
        </NavigationContainer>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center',
    },
    headerTitle: {
        textAlign: 'center',
        fontSize: 18,
        color: 'white',
        fontWeight: 'bold',
        paddingBottom: 15,
    },
});
  • Login.js

    token을 받아와 로그인
    카메라, 위치 허가 받기

import AsyncStorage from '@react-native-async-storage/async-storage';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { Alert, Button, SafeAreaView, StyleSheet, Text, TextInput, View } from 'react-native';
import { Platform, PermissionsAndroid } from 'react-native';

export default function Login({ navigation }) {
    const [id, onChangeId] = useState('admin');
    const [password, onChangePassword] = useState('1');
    const [login, setLogin] = useState(false);

    const handleLogin = async () => {
        if (id !== '' && password !== '') {
            //member 정보가 미입력 상태면 함수 발동 x
            const formData = new FormData();
            formData.append('username', id);
            formData.append('password', password);
            await axios
                .post('http://10.0.2.2:8080/login', formData, {
                    headers: {
                        'Content-Type': 'multipart/form-data',
                        // 'Content-Type': 'application/json', // 서버가 JSON 형식 기대
                    },
                })
                .then(async (response) => {
                    //응답결과 정상적으로 넘어온 경우 아래 로직 수행
                    Alert.alert('로그인', '로그인 성공');
                    await AsyncStorage.setItem('accessToken', response.headers.authorization); // 토큰 저장
                    await AsyncStorage.setItem('id', id); // 토큰 저장
                    navigation.navigate('List', { id: id });
                })
                .catch((err) => {
                    //서버에서 에러가 발생한 경우 경고창 알림
                    Alert.alert('에러', `err : ${err.response}`);
                });
        } else {
            Alert.alert('로그인', 'ID 혹은 비밀번호를 입력하세요.'); // member 정보 미입력 시 알림
        }
    };

    useEffect(() => {
        if (Platform.OS === 'android') {
            const requestCameraPermission = async () => {
                try {
                    const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, {
                        title: 'Camera Permission',
                        message: 'App needs permission for camera access',
                        buttonPositive: 'OK',
                    });
                    // if (granted === PermissionsAndroid.RESULTS.GRANTED) {
                    //     Alert.alert('접근 허가', '카메라 접급 허가');
                    // } else {
                    //     Alert.alert('접근 허가 필요', '카메라 접근 허가 필요');
                    // }
                    if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
                        Alert.alert('접근 허가 필요', '카메라 접근 허가 필요');
                    }
                } catch (err) {
                    Alert.alert('카메라 접근 허가 오류');
                    console.warn(err);
                }
            };
            requestCameraPermission();
            const requestGPSPermission = async () => {
                try {
                    const granted = await PermissionsAndroid.request(
                        PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
                        {
                            title: 'GPS Permission',
                            message: 'App needs permission for gps access',
                            buttonPositive: 'OK',
                        }
                    );
                    if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
                        Alert.alert('접근 허가 필요', '위치 접근 허가 필요');
                        // Alert.alert('접근 허가', '위치 접근 허가');
                    }
                    // else {
                    //     Alert.alert('접근 허가 필요', '위치 접근 허가 필요');
                    // }
                } catch (err) {
                    Alert.alert('위치 접근 허가 오류');
                    console.warn(err);
                }
            };
            requestGPSPermission();
        }
    });

    return (
        <SafeAreaView style={styles.container}>
            <View style={styles.row}>
                <View style={styles.column}>
                    <TextInput style={styles.input} onChangeText={onChangeId} value={id} placeholder="아이디" />
                    <TextInput
                        style={styles.input}
                        onChangeText={onChangePassword}
                        value={password}
                        secureTextEntry={true}
                        placeholder="비밀번호"
                    />
                    <View style={styles.buttonItem}>
                        <Button color={'#096e5b'} title="로그인" onPress={(e) => handleLogin()} />
                    </View>
                </View>
            </View>
        </SafeAreaView>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center',
    },
    input: {
        width: 150,
        borderWidth: 1,
        padding: 10,
        marginBottom: 20,
    },
    row: {
        flexDirection: 'row',
    },
    column: {
        flexDirection: 'column',
        gap: 5,
        height: 100,
        width: 200,
        alignItems: 'center',
        justifyContent: 'center',
    },
    buttonItem: {
        width: '75%',
        alignItems: 'center',
        justifyContent: 'center',
    },
    button: {},
});
  • List.js
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useFocusEffect } from '@react-navigation/native';
import axios from 'axios';
import { format } from 'date-fns';
import React, { useEffect, useRef, useState } from 'react';
import { Alert, AppState, FlatList, SafeAreaView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';

export default function List({ navigation }) {
    const [data, setData] = useState([]);
    const GETLIST_URL = 'http://10.0.2.2:8080/api/getList';
    const [id, setId] = useState('');
    const [ok, setOk] = useState(true);

    // 앱 상태 테스트
    const appState = useRef(AppState.currentState);
    const [appStateVisible, setAppStateVisible] = useState(appState.current);

    const getList = async () => {
        const id = await AsyncStorage.getItem('id');
        setId(id);
        const token = await AsyncStorage.getItem('accessToken');
        // console.log('list token :: ', token);
        axios
            .get(GETLIST_URL, {
                headers: {
                    Authorization: `${token}`,
                },
            })
            .then((res) => {
                let tmpData = res.data.data;
                tmpData.map((e) => {
                    if (e.boardState == 'B' || e.boardState == 'C') {
                        setOk(false);
                        navigation.navigate('DetailBC', { item: e, id: id });
                    }
                });
                setData(res.data.data);
            })
            .catch((err) => console.log(err));
    };
    useEffect(() => {
        // const subscription = AppState.addEventListener('change', (nextAppState) => {
        //     if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
        //         console.log('App has come to the foreground!');
        //     }

        //     appState.current = nextAppState;
        //     setAppStateVisible(appState.current);
        //     console.log('AppState : ', appState.current);
        // });

        getList();

        return () => {
            // subscription.remove();
        };
    }, []);

    const Item = ({ onPress, title, contents, writer, inDate, backgroundColor }) => (
        <TouchableOpacity onPress={onPress} style={[styles.item, { backgroundColor }]}>
            <Text style={styles.title}>제목 : {title}</Text>
            <Text style={styles.title} numberOfLines={1}>
                내용 : {contents}
            </Text>
            <Text style={styles.title}>작성자 : {writer}</Text>
            <Text style={styles.title}>작성일자 : {format(inDate, 'yyyy-mm-dd HH:mm')}</Text>
        </TouchableOpacity>
    );

    const renderItem = ({ item }) => {
        const backgroundColor = item.boardState == 'A' || item.boardState == 'D' ? '#c4c2c2' : '#FFF';
        // console.log('backgroundColor : ',backgroundColor)
        return (
            <Item
                onPress={() => {
                    console.log('onclick : ', item);
                    if (item.boardState == 'D') {
                        Alert.alert('진입불가', '상태 값이 D입니다.');
                    } else if (item.boardState == 'A') {
                        navigation.navigate('DetailA', { item: item, id: id });
                    } else {
                        navigation.navigate('DetailBC', { item: item, id: id });
                    }
                }}
                title={item.boardTitle}
                contents={item.boardContents}
                writer={item.boardWriter}
                inDate={new Date(item.boardIndate)}
                backgroundColor={backgroundColor}
            />
        );
    };
    if (ok) {
        return (
            <SafeAreaView style={styles.container}>
                <FlatList data={data} renderItem={renderItem} keyExtractor={(data) => data.boardSeq.toString()} />
            </SafeAreaView>
        );
    } else {
        return <View></View>;
    }
}

const styles = StyleSheet.create({
    container: {
        // flex: 1,
        // backgroundColor: '#fff',
        // alignItems: 'center',
        // justifyContent: 'center',
    },
    input: {
        height: 30,
        width: 100,
        borderWidth: 1,
        padding: 10,
        marginBottom: 20,
    },
    row: {
        flexDirection: 'row',
        gap: 10,
    },
    button: {
        width: 200,
    },
    item: {
        backgroundColor: '#fff',
        padding: 20,
        marginVertical: 8,
        marginHorizontal: 16,
    },
    title: {
        fontSize: 20,
    },
});
  • DetailA.js
import axios from 'axios';
import { format } from 'date-fns';
import { CameraView } from 'expo-camera';
import React, { useCallback, useEffect, useState } from 'react';
import { Button, FlatList, Image, SafeAreaView, StyleSheet, Text, View } from 'react-native';
import MapView, { Marker } from 'react-native-maps';
import TmapView from '../components/tmap';
import Ionicons from '@expo/vector-icons/Ionicons';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Location from 'expo-location';
import { useFocusEffect } from '@react-navigation/native';

export default function List({ route, navigation }) {
    const { item, otherParam } = route.params;
    const [data, setData] = useState(item);
    const [id, setId] = useState('');
    const UPDATE_URL = 'http://10.0.2.2:8080/api/updateAtoB';
    const DETAIL_URL = 'http://10.0.2.2:8080/api/getDetail';
    const [city, setCity] = useState('');

    useEffect(() => {
        getId();
    }, []);
    const getId = async () => {
        const Tid = await AsyncStorage.getItem('id');
        setId(Tid);
    };
    //A -> B 업데이트
    const updateAtoB = async () => {
        const token = await AsyncStorage.getItem('accessToken');
        console.log('update token : ', token);
        await axios
            .post(
                UPDATE_URL,
                {
                    boardSeq: data.boardSeq,
                    boardLoc: data.boardLoc,
                },
                {
                    headers: {
                        Authorization: `${token}`,
                        'Content-Type': 'application/json',
                    },
                }
            )
            .then((res) => {
                console.log('update res', res);
                if (res.data >= 0) {
                    getDetail();
                }
            })
            .catch((err) => console.log('update err :', err));
    };
    // 상세화면 불러오기
    const getDetail = async () => {
        const token = await AsyncStorage.getItem('accessToken');
        console.log('getDEtail boardSeq : ', data.boardSeq);
        await axios
            .post(DETAIL_URL, data.boardSeq, {
                headers: {
                    Authorization: `${token}`,
                    'Content-Type': 'application/json',
                },
            })
            .then((res) => {
                console.log('getDetail res.data : ', res.data);
                navigation.navigate('DetailBC', { item: res.data, id: id });
            })
            .catch((err) => console.log('getDetail err : ', err));
    };
    const getCity = (e) => {
        setCity(e);
        // console.log('City : ', e);
        setData({
            boardSeq: data.boardSeq,
            boardTitle: data.boardTitle,
            boardContents: data.boardContents,
            boardWriter: data.boardWriter,
            boardIndate: data.boardIndate,
            boardLoc: e,
            boardState: data.boardState,
        });
    };
    return (
        <SafeAreaView style={styles.container}>
            <View style={styles.item}>
                <Text style={styles.title}>제목 : {data.boardTitle}</Text>
                <Text style={styles.title}>내용 : {data.boardContents}</Text>
                <Text style={styles.title}>작성자 : {data.boardWriter}</Text>
                <Text style={styles.title}>작성일자 : {format(data.boardIndate, 'yyyy-mm-dd HH:mm')}</Text>
            </View>
            <View style={[styles.cameraItem]}>
                <Ionicons name="camera" size={40} onPress={(e) => navigation.navigate('Camera')} />
                {/* <Image source={{ uri: fileUri }} style={{ width: 200, height: 200, resizeMode: 'contain' }} /> */}
            </View>
            <View style={[styles.mapItem]}>
                <TmapView getCity={getCity}></TmapView>
            </View>
            <View style={[styles.item]}>
                <Text style={styles.title}>주소 : {data.boardLoc}</Text>
            </View>
            <View style={styles.buttonItem}>
                <Button style={styles.button} color={'#096e5b'} title="상태변경(A -> B)" onPress={updateAtoB} />
            </View>
        </SafeAreaView>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
    },
    input: {
        height: 30,
        width: 100,
        borderWidth: 1,
        padding: 10,
        marginBottom: 20,
    },
    row: {
        flexDirection: 'row',
        gap: 10,
    },
    button: {
        fontSize: 30,
        fontWeight: 200,
    },
    buttonItem: {
        paddingHorizontal: 15,
    },
    textItem: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
    },
    text: {
        fontStyle: 'bold',
        fontSize: 400,
    },
    item: {
        backgroundColor: '#fff',
        padding: 20,
        marginVertical: 8,
        marginHorizontal: 16,
    },
    title: {
        fontSize: 20,
    },
    cameraItem: {
        paddingHorizontal: 15,
        justifyContent: 'center',
        alignItems: 'center',
        height: 160,
    },
    mapItem: {
        paddingHorizontal: 15,
        height: 300,
    },
    map: {
        width: '100%',
        height: '100%',
    },
});
  • DetailBC.js
import axios from 'axios';
import { format } from 'date-fns';
import { CameraView } from 'expo-camera';
import React, { useCallback, useEffect, useState } from 'react';
import { Button, FlatList, SafeAreaView, StyleSheet, Text, View } from 'react-native';
import MapView, { Marker } from 'react-native-maps';
import TmapView from '../components/tmap';
import Ionicons from '@expo/vector-icons/Ionicons';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Location from 'expo-location';
import { useFocusEffect } from '@react-navigation/native';

export default function List({ route, navigation }) {
    const { item, otherParam } = route.params;
    const [data, setData] = useState(item);
    const DETAIL_URL = 'http://10.0.2.2:8080/api/getDetail';
    const [city, setCity] = useState('');

    useEffect(() => {
        getRefresh();
    }, []);

    const getRefresh = () => {
        if (data.boardState == 'B' || data.boardState == 'C') {
            //B,C는 10초마다 데이터 갱신
            setInterval(() => {
                getDetail();
            }, 10000);
        }
    };

    // 상세화면 불러오기
    const getDetail = async () => {
        const token = await AsyncStorage.getItem('accessToken');
        console.log('getDEtail boardSeq : ', data.boardSeq);
        await axios
            .post(DETAIL_URL, data.boardSeq, {
                headers: {
                    Authorization: `${token}`,
                    'Content-Type': 'application/json',
                },
            })
            .then((res) => {
                console.log('getDetail res.data : ', res.data);
                setData(res.data);
            })
            .catch((err) => console.log('getDetail err : ', err));
    };

    return (
        <SafeAreaView style={styles.container}>
            <View style={styles.item}>
                <Text style={styles.title}>제목 : {data.boardTitle}</Text>
                <Text style={styles.title}>내용 : {data.boardContents}</Text>
                <Text style={styles.title}>작성자 : {data.boardWriter}</Text>
                <Text style={styles.title}>작성일자 : {format(data.boardIndate, 'yyyy-mm-dd HH:mm')}</Text>
            </View>
            <View style={styles.textItem}>
                <Text style={styles.text}>{data.boardState}</Text>
            </View>
        </SafeAreaView>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
    },
    input: {
        height: 30,
        width: 100,
        borderWidth: 1,
        padding: 10,
        marginBottom: 20,
    },
    row: {
        flexDirection: 'row',
        gap: 10,
    },
    button: {
        fontSize: 30,
        fontWeight: 200,
    },
    buttonItem: {
        paddingHorizontal: 15,
    },
    textItem: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
    },
    text: {
        fontStyle: 'bold',
        fontSize: 400,
    },
    item: {
        backgroundColor: '#fff',
        padding: 20,
        marginVertical: 8,
        marginHorizontal: 16,
    },
    title: {
        fontSize: 20,
    },
    cameraItem: {
        paddingHorizontal: 15,
        justifyContent: 'center',
        alignItems: 'center',
        height: 160,
    },
    mapItem: {
        paddingHorizontal: 15,
        height: 300,
    },
    map: {
        width: '100%',
        height: '100%',
    },
});
  • Camera.js
import React, { useEffect, useRef, useState } from 'react';
import { View, StyleSheet, Text, TouchableOpacity, Image, Alert } from 'react-native';
import { CameraView, useCameraPermissions } from 'expo-camera';
import * as ImageManipulator from 'expo-image-manipulator';
import * as FileSystem from 'expo-file-system';
import AsyncStorage from '@react-native-async-storage/async-storage';
import axios from 'axios';

export default function CameraScreen(props) {
    const [permission, requestPermission] = useCameraPermissions();
    const cameraRef = useRef(null);
    const [photoUri, setPhotoUri] = useState(null); // 원본 사진
    const [rotatedUri, setRotatedUri] = useState(null); // 회전된 사진
    const [rotation, setRotation] = useState(0); // 회전 각도

    // 권한 체크
    const checkPermissions = async () => {
        if (!permission) return;

        if (permission.status !== 'granted') {
            if (!permission.canAskAgain) {
                Alert.alert('권한 필요', '앱 설정에서 카메라 권한을 변경해주세요.', [
                    { text: '설정 열기', onPress: () => Linking.openSettings() },
                ]);
            } else {
                await requestPermission();
            }
        }
    };

    useEffect(() => {
        checkPermissions();
    }, [permission]);

    // 사진 촬영
    const takePhoto = async () => {
        if (cameraRef.current) {
            try {
                const photo = await cameraRef.current.takePictureAsync(); // 사진 촬영
                setPhotoUri(photo.uri); // 사진 URI 저장
                setRotatedUri(photo.uri); // 초기 회전된 사진 URI 저장
                setRotation(0); // 초기 회전값
            } catch (error) {
                console.error('사진 촬영 실패:', error);
                Alert.alert('사진 촬영 실패');
            }
        }
    };

    // 이미지 회전
    const rotateImage = async () => {
        if (!rotatedUri) return;

        try {
            const result = await ImageManipulator.manipulateAsync(rotatedUri, [
                { rotate: 90 }, // 90도 회전
            ]);
            setRotatedUri(result.uri); // 회전된 이미지 URI 저장
            setRotation((prev) => (prev + 90) % 360); // 현재 회전 각도 업데이트
        } catch (error) {
            console.error('이미지 회전 실패:', error);
            Alert.alert('이미지 회전 실패');
        }
    };

    // 이미지 저장
    const saveImage = async () => {
        // console.log('save!@@!@');
        if (!rotatedUri) return;

        // const TOKEN = await AsyncStorage.getItem('accessToken');
        // console.log('camera TOKEN : ', TOKEN);
        // const formData = new FormData();
        // formData.append('file', {
        //     rotatedUri,
        //     name: 'photo.jpg',
        //     type: 'image/jpeg',
        // });
        // try {
        //     const response = await axios.post('http://10.0.2.2:8080/api/saveImage', formData, {
        //         headers: {
        //             Authorization: `${TOKEN}`,
        //             'Content-Type': 'multipart/form-data',
        //         },
        //     });
        //     if (response.status === 200) {
        //         Alert.alert('사진', '사진이 서버에 저장되었습니다!');
        //     } else {
        //         console.error('서버 응답 에러: ', response.data);
        //         Alert.alert('사진', '사진 업로드 실패');
        //     }
        // } catch (err) {
        //     console.error('사진 업로드 중 에러 발생: ', err);
        //     Alert.alert('사진', '사진 업로드 실패');
        // }
        try {
            // 1. 로컬에 저장
            const fileName = `rotated-image-${Date.now()}.jpg`;
            const fileUri = `${FileSystem.documentDirectory}${fileName}`;

            await FileSystem.copyAsync({
                from: rotatedUri,
                to: fileUri,
            });

            Alert.alert('저장 완료', `이미지가 로컬에 저장되었습니다:\n${fileUri}`);
            console.log('저장된 로컬 이미지 경로:', fileUri);
            // 2. 서버에 저장
            const TOKEN = await AsyncStorage.getItem('accessToken');
            console.log('camera TOKEN : ', TOKEN);
            const formData = new FormData();
            formData.append('file', {
                uri: rotatedUri,
                name: 'photo.jpg',
                type: 'image/jpeg',
            });

            const response = await axios.post('http://10.0.2.2:8080/api/saveImage', formData, {
                headers: {
                    Authorization: `${TOKEN}`,
                    'Content-Type': 'multipart/form-data',
                },
            });
            if (response.status === 200) {
                Alert.alert('업로드 완료', '이미지가 서버에 저장되었습니다!');
            } else {
                console.error('서버 응답 에러:', response.data);
                Alert.alert('업로드 실패', '이미지를 서버에 저장하는 중 문제가 발생했습니다.');
            }
            // props.setImage(fileUri);
        } catch (error) {
            console.error('이미지 저장 실패:', error);
            Alert.alert('이미지 저장 실패');
        }
    };

    return (
        <View style={styles.container}>
            {!photoUri ? (
                <CameraView style={styles.camera} facing="back" ref={cameraRef}>
                    <View style={styles.buttonContainer}>
                        <TouchableOpacity style={styles.button} onPress={takePhoto}>
                            <Text style={styles.text}>사진 찍기</Text>
                        </TouchableOpacity>
                    </View>
                </CameraView>
            ) : (
                <View style={styles.imageContainer}>
                    <Image source={{ uri: rotatedUri }} style={styles.imagePreview} />
                    <View style={styles.controls}>
                        <TouchableOpacity style={styles.button} onPress={rotateImage}>
                            <Text style={styles.text}>회전</Text>
                        </TouchableOpacity>
                        <TouchableOpacity style={styles.button} onPress={saveImage}>
                            <Text style={styles.text}>저장</Text>
                        </TouchableOpacity>
                        <TouchableOpacity
                            style={[styles.button, styles.cancelButton]}
                            onPress={() => setPhotoUri(null)}
                        >
                            <Text style={styles.text}>다시 찍기</Text>
                        </TouchableOpacity>
                    </View>
                </View>
            )}
        </View>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        backgroundColor: '#000',
    },
    camera: {
        flex: 1,
    },
    buttonContainer: {
        flex: 1,
        flexDirection: 'row',
        justifyContent: 'center',
        alignItems: 'flex-end',
        paddingBottom: 20,
    },
    button: {
        backgroundColor: '#444',
        padding: 10,
        marginHorizontal: 10,
        borderRadius: 5,
    },
    text: {
        color: '#fff',
        fontSize: 16,
    },
    imageContainer: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
    },
    imagePreview: {
        width: '90%',
        height: '70%',
        resizeMode: 'contain',
    },
    controls: {
        flexDirection: 'row',
        justifyContent: 'center',
        marginTop: 20,
    },
    cancelButton: {
        backgroundColor: '#c00',
    },
});
  • tmap.js
import React, { useEffect, useState } from 'react';
import { StyleSheet, View, PermissionsAndroid, Platform, Alert, Text } from 'react-native';
import { WebView } from 'react-native-webview';
import * as Location from 'expo-location';

export default function TmapView(props) {
    const [currentLocation, setCurrentLocation] = useState({ latitude: 37.5665, longitude: 126.978 }); // 기본 위치: 서울시청
    const [loading, setLoading] = useState(true); // 로딩 상태 관리
    useEffect(() => {
        const getCurrentLocation = async () => {
            try {
                const { status } = await Location.requestForegroundPermissionsAsync();
                if (!status) {
                    return;
                }
                const {
                    coords: { latitude, longitude },
                } = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.High });

                setCurrentLocation({
                    latitude: latitude,
                    longitude: longitude,
                });
                // console.log('currentLocation :', currentLocation);
                const locationName = await Location.reverseGeocodeAsync(
                    { latitude, longitude },
                    { useGoogleMaps: false }
                );
                // console.log('locationName : ', locationName);
                if (locationName) {
                    props.getCity(locationName[0].formattedAddress);
                }
            } catch (error) {
                console.error('위치 정보를 가져오는 중 오류 발생:', error);
                Alert.alert('오류', '위치 정보를 가져오는 중 문제가 발생했습니다.');
            } finally {
                setLoading(false);
            }
        };
        getCurrentLocation();
    }, [currentLocation]);

    // const handleWebViewMessage = (event) => {
    //     try {
    //         const data = JSON.parse(event.nativeEvent.data);
    //         if (data.type === 'address') {
    //             Alert.alert('클릭한 위치의 주소', data.address);
    //         }
    //     } catch (error) {
    //         console.error('Error parsing message from WebView:', error);
    //     }
    // };

    const tmapHtml = `
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <script src="https://apis.openapi.sk.com/tmap/jsv2?version=1&appKey=rhnIyM1eA07OkeSGBqHFy9D5WEGTwcSJ5UAMgMvZ"></script>
        <style>
          html, body, #map_div { width: 100%; height: 100%; margin: 0; padding: 0; }
        </style>
      </head>
      <body>
        <div id="map_div"></div>
        <script>
          var map = new Tmapv2.Map("map_div", {
            center: new Tmapv2.LatLng(${currentLocation.latitude}, ${currentLocation.longitude}),
            zoom: 18,
            zoomControl: true,
          });
          
          var clickMarker = new Tmapv2.Marker({
            position: new Tmapv2.LatLng(${currentLocation.latitude}, ${currentLocation.longitude}),
            map: map,
            iconSize: new Tmapv2.Size(48, 76)
          });
          // var clickMarker = null;

          map.addListener("click", function getCurrentLocation(e) {
            console.log('MAP CLICK');
            var lat = e.latLng.lat();
            var lng = e.latLng.lng();

            if (clickMarker) {
              clickMarker.setMap(null);
            }
            clickMarker = new Tmapv2.Marker({
              position: new Tmapv2.LatLng(lat, lng),
              map: map,
              iconSize: new Tmapv2.Size(50, 50)
            });

            fetch(\`https://apis.openapi.sk.com/tmap/geo/reversegeocoding?version=1&lat=\${lat}&lon=\${lng}&appKey=rhnIyM1eA07OkeSGBqHFy9D5WEGTwcSJ5UAMgMvZ&format=json\`)
              .then(response => {
                console.log('response: ', response)
                response.json()
              })
              .then(data => {
                console.log('fetch data: ',data)
                var address = data.addressInfo.fullAddress || "주소 정보를 찾을 수 없습니다.";
                window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'address', address }));
              })
              .catch(error => console.error('Error:', error));
          });
        </script>
      </body>
    </html>
  `;
    ``;

    return (
        <View style={styles.container}>
            <WebView
                source={{ html: tmapHtml }}
                style={styles.webview}
                javaScriptEnabled={true}
                domStorageEnabled={true}
                // onMessage={handleWebViewMessage}
            />
        </View>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
    },
    webview: {
        flex: 1,
    },
});

0개의 댓글