app/react-native

<react-native>mission 앱 제작1_메인화면, 로그인, 홈 화면, ...

환테크 2022. 5. 18. 13:31
반응형

 

 

 

 

 

 

 

 

 

APP 제작

 

 

 

APP 설명

 

 

혼자서는 정해진 일을 하지 못하게 되고, 좋은 습관을 들이지 못하는 사람들을 위해, 오늘 일을 내일로 미루는 사람들을 위해 터치 업 할 수 있도록, 타인과 약속을 만들고 공유하여 능률을 올릴 수 있는 어플

 

 

 

 

구조설명

 

 

 

 

 

 

DB 관계도

 

 

 

 

 

 

 

 

현재까지 구현 page 설명

 

src

 

- assets

필요한 image

- components

- core

아직 불필요 - core - theme.tsx만 사용

- screens

보여지는 화면

 

app.tsx

screens가 보이도록 연결

 

pakage.json

깔아야하는 패키지를 보여줌

 

 

 

 

pakage.json

 

필요한 pakege들을 yarn or npm 으로 설치

 

 

 

 

app.tsx

 

 

src/screen으로 가도록 연결

가장 첫화면은 appstack에 들어가는 login 화면이 나온다

 

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow
 */

 import React from 'react';
 import AppStack from './src/screens';
 
 const App = () => {
   return (
     <AppStack />
   );
 };
 
 
 export default App;

 

 

src

 

 

core

 

theme.tsx

 

 

import { DefaultTheme } from 'react-native-paper';

export const theme = {
  ...DefaultTheme,
  colors: {
    ...DefaultTheme.colors,
    primary: '#46c3ad',
    secondary: '#414757',
    error: '#f13a59',
  },
};

 

 

 

 

screens

 

 

 

index.tsx

 

- 모든 화면에 들어가는 전체화면과 네비게이터들

 

import React from "react";
import { Text } from "react-native";
import { createAppContainer, createSwitchNavigator } from "react-navigation";
import { createStackNavigator } from "react-navigation-stack";
import { createBottomTabNavigator } from "react-navigation-tabs";

import LoginScreen from "./LoginScreen";
import HomeScreen from "./HomeScreen";
import CreateScreen from "./CreateScreen";
import MissionDetailScreen from "./MissionDetailScreen";
import SettingScreen from "./SettingScreen";
import SomethingScreen from "./SomethingScreen";
import { Navigation } from '../types';

type Props = {
  navigation: Navigation;
}

const HomeStack = createStackNavigator(
  {
    HomeScreen,
  },
  // if you need.
  // recommend custom header
  {
    defaultNavigationOptions: ({ navigation }) => ({
      title: "Hello mission world",
    }),
  }
);
const SettingStack = createStackNavigator(
  {
    SettingScreen,
    SomethingScreen,
  },
  {
    defaultNavigationOptions: ({ navigation }) => ({
      title: "Setting",
    }),
    initialRouteName: "SettingScreen",
  }
);

const TabNavigator = createBottomTabNavigator(
  {
    Home: HomeStack,
    Setting: SettingStack,
  },
  {
    defaultNavigationOptions: ({ navigation }) => ({
      tabBarIcon: ({ focused, horizontal, tintColor }) => {
        const { routeName } = navigation.state;
        let icon = "▲";

        if (routeName === "Home") {
          icon = "🌈";
        } else if (routeName === "Setting") {
          icon = "🌙";
        }

        // can use react-native-vector-icons
        // <Icon name={iconName} size={iconSize} color={iconColor} />
        return (
          <Text style={{ color: (focused && "#46c3ad") || "#888" }}>
            {icon}
          </Text>
        );
      },
    }),
    lazy: false,
    tabBarOptions: {
      activeTintColor: "#46c3ad",
      inactiveTintColor: "#888",
    },
  }
);

const AppStack = createStackNavigator({
  LoginScreen: LoginScreen,
  CreateScreen: CreateScreen,
  MissionDetailScreen: MissionDetailScreen,
  TabNavigator: {
    screen: TabNavigator,
  },
});

export default createAppContainer(AppStack);

 

 

 

types.tsx - 네비게이터 선언

 

export type Navigation = {
  navigate: (scene: string) => void;
};

 

 

- style을 따로 빼놓고 구현했어야했는데 방식을 아직몰라서

  아직 화면마다 일일히 구현하였습니다 ㅠ

 

 

 

LoginScreen -index.tsx

 

import React, {Component, memo} from 'react';
import {widthPercentageToDP as wp, heightPercentageToDP as hp} from 'react-native-responsive-screen';
import { Navigation } from '../../types';
import {useState } from 'react';
import { TouchableOpacity, StyleSheet, Text, View, Image } from 'react-native';
import Background from '../../components/Background';
import Logo from '../../components/Logo';
import Header from '../../components/Header';
import Button from '../../components/Button';
import TextInput from '../../components/TextInput';
import BackButton from '../../components/BackButton';
import { theme } from '../../core/theme';
import { emailValidator, passwordValidator } from '../../core/utils';



type Props = {
    navigation: Navigation;
}


const LoginScreen = ({ navigation }: Props) => {
    
    const [email, setEmail] = useState({ value: '', error: '' });
    const [password, setPassword] = useState({ value: '', error: '' });

    const _onLoginPressed = () => {
    const emailError = emailValidator(email.value);
    const passwordError = passwordValidator(password.value);

if (emailError || passwordError) {
        setEmail({ ...email, error: emailError });
        setPassword({ ...password, error: passwordError });
        return;
}
    navigation.navigate('Dashboard');
    };
        return (
            <View style={styles.container}>
                <View>
                      <Image
                            style={styles.image}
                            source={require('../../assets/logo.png')}
                        />
                </View>
                <View style={styles.formArea}>
                    <TextInput 
                        style={styles.textForm} 
                        placeholder={"ID"}/>
                    <TextInput 
                        style={styles.textForm} 
                        placeholder={"Password"}/>
                </View>
                <View style={styles.buttonArea}>
                <TouchableOpacity onPress={() => navigation.navigate('HomeScreen')}>
                    <Text style={styles.link}>Login</Text>
                </TouchableOpacity>
                </View>
            </View>

        );
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: 'white',
        paddingLeft: wp('10%'),
        paddingRight: wp('10%'),
        justifyContent: 'center',
    },
    titleArea: {
        width: '100%',
        padding: wp('10%'),
        alignItems: 'center',
    },
    title: {
        fontSize: wp('10%'),
        color: theme.colors.primary,
    },
    formArea: {
        width: '100%',
        paddingBottom: wp('10%'),
    },
    textForm: {
        borderWidth: 0.5,
        borderColor: '#888',
        width: '100%',
        height: hp('5%'),
        paddingLeft: 5,
        paddingRight: 5,
        marginBottom: 5,
    },
    buttonArea: {
        width: '100%',
        height: hp('5%'),
    },
    button: {
        backgroundColor: "#46c3ad",
        width: "100%",
        height: "100%",
        justifyContent: 'center',
        alignItems: 'center',
    },
    buttonTitle: {
        color: 'white',
    },
  link: {
    fontWeight: 'bold',
    color: theme.colors.primary,
  },
  image: {
      width: 200 ,
      height : 200 ,
      marginLeft : 50 ,
      marginBottom : 20
  }
})

export default memo(LoginScreen);

 

 

HomeScreen -index.tsx

 

 

import React, {Component,memo} from 'react';
import {
    View,
    Text,
    ScrollView,
    StyleSheet
} from 'react-native';
import { TouchableOpacity} from 'react-native';
import {widthPercentageToDP as wp, heightPercentageToDP as hp} from 'react-native-responsive-screen';
import { Navigation } from '../../types';
import Header from '../../components/Header';
import { theme } from '../../core/theme';

type Props = {
    navigation: Navigation;
}
const HomeScreen = ({ navigation }: Props) => {
    <Header>Home Test</Header>
        return (
            <View style={styles.container}>
                                <TouchableOpacity 
                    style={styles.wrapButton}
                    onPress={() => navigation.navigate('MissionDetailScreen')}>
                    <Text> algorithm Mission</Text>
                </TouchableOpacity>
                <TouchableOpacity 
                    style={styles.CreateButton}
                    onPress={() => navigation.navigate('CreateScreen')}>
                    <Text style={styles.text}>+</Text>
                </TouchableOpacity>
            </View>
        );
}



const styles = StyleSheet.create({
    container: {
        flex: 1,
        padding: wp('5%'),
        backgroundColor: 'white',
    },
    // wrapContent: {
    //     width: wp('90%'),
    //     height: wp('90%'),
    //     paddingBottom: wp('5%'),
        
    // },
    // content: {
    //     width: "100%",
    //     height: "100%",
    //     backgroundColor: "#46c3ad",
    // },
    wrapButton: {
        backgroundColor: '#46c3ad',
        width: wp('45%'),
        height: hp('30%'),
        paddingLeft: wp('8%'),
        marginTop: 20,
        justifyContent: 'center',
        borderBottomWidth: 0.5,
        borderColor: '#46c3ad',
    },
    CreateButton: {
        backgroundColor: '#46c3ad',
        alignItems: 'center',
        justifyContent: 'center',
        width: 50,
        height: 50,
        marginTop: 200,
        marginLeft: 300,
        borderRadius: 35,
    },
    text: {
        fontSize: 30,
        textAlign: 'center',
        color: 'white'
    }
})
export default memo(HomeScreen);

 

 

SettingScreen -index.tsx

 

 

import React, {Component, memo} from 'react';
import {
    View,
    Text,
    TouchableOpacity,
    Alert,
    StyleSheet
} from 'react-native';
import { StackActions, NavigationActions } from 'react-navigation';
import {widthPercentageToDP as wp, heightPercentageToDP as hp} from 'react-native-responsive-screen';
import { Navigation } from '../../types';
import Header from '../../components/Header';

type Props = {
    navigation: Navigation;
}

const SettingScreen = ({ navigation }: Props) => {
    <Header>Setting Screen Test</Header>

        return (
            <View style={styles.container}>
                <TouchableOpacity 
                    style={styles.wrapButton}
                    onPress={() => navigation.navigate('LoginScreen')}>
                    <Text>🏅 Something</Text>
                </TouchableOpacity>
                <TouchableOpacity 
                    style={styles.wrapButton}
                    onPress={() => navigation.navigate('LoginScreen')}>
                    <Text>🔓 Logout</Text>
                </TouchableOpacity>
            </View>
        );
}



const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: 'white',
    },
    wrapButton: {
        width: wp('100%'),
        height: hp('8%'),
        paddingLeft: wp('8%'),
        justifyContent: 'center',
        borderBottomWidth: 0.5,
        borderColor: '#ccc',
    }
})

export default memo(SettingScreen);

 

 

 

CreateScreen -index.tsx : 미션 생성 화면

 

import React, {Component, memo} from 'react';
import {widthPercentageToDP as wp, heightPercentageToDP as hp} from 'react-native-responsive-screen';
import { Navigation } from '../../types';
import {useState } from 'react';
import { TouchableOpacity, StyleSheet, Text, View, ScrollView, Alert } from 'react-native';
import Background from '../../components/Background';
import Logo from '../../components/Logo';
import Header from '../../components/Header';
import Button from '../../components/Button';
import TextInput from '../../components/TextInput';
import BackButton from '../../components/BackButton';
import { theme } from '../../core/theme';
import { emailValidator, passwordValidator } from '../../core/utils';
import { RadioButton } from 'react-native-paper';



type Props = {
    navigation: Navigation;
}


const CreateScreen = ({ navigation }: Props) => {
    const [value, setValue] = React.useState('first');
    
        return (
            <ScrollView>
            <View style={styles.container}>
                <View style={styles.titleArea}>
                    <Text style={styles.title}>Create Misson!!</Text>
                </View>
                <View style={styles.formArea}>
                    <Text>카테고리를 선택해주세요.</Text>

                    <RadioButton.Group onValueChange={value => setValue(value)} value={value}>
                    <RadioButton.Item label="생활 습관" value="first" />
                    <RadioButton.Item label="공부/스터디" value="second" />
                    <RadioButton.Item label="운동/건강" value="third" />
                    </RadioButton.Group>

                    <Text>최대 인원을 선택해주세요.</Text>
                    <TextInput 
                        style={styles.textForm1} 
                        placeholder={" 명 "}/>

                    <Text>미션 기간(최대 90일)을 선택해주세요.</Text>

                    <RadioButton.Group onValueChange={value => setValue(value)} value={value}>
                    <RadioButton.Item label="30일" value="first" />
                    <RadioButton.Item label="60일" value="second" />
                    <RadioButton.Item label="90일" value="third" />
                    </RadioButton.Group>


                    <Text>미션 제목을 작성해주세요.</Text>
                    <TextInput 
                        style={styles.textForm1} 
                        placeholder={" "}/>


                    <Text>미션 내용을 작성해주세요.</Text>
                    <TextInput 
                        style={styles.textForm2} 
                        placeholder={" "}/>

                    <Text>패널티를 선택해주세요.</Text>

                    <RadioButton.Group onValueChange={value => setValue(value)} value={value}>
                    <RadioButton.Item label="강퇴" value="first" />
                    <RadioButton.Item label="인증서 미제공" value="second" />
                    <RadioButton.Item label="랭킹 제외" value="third" />
                    </RadioButton.Group>
                </View>
                <View>
                <TouchableOpacity onPress={() => navigation.navigate('HomeScreen')}>
                    <Text style={styles.wrapButton}>미션 완성</Text>
                </TouchableOpacity>
                </View>
            </View>
            </ScrollView>
        );
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: 'white',
        paddingLeft: wp('10%'),
        paddingRight: wp('10%'),
        justifyContent: 'center',
    },
    titleArea: {
        width: '100%',
        padding: wp('10%'),
        alignItems: 'center',
    },
    title: {
        fontSize: wp('8%'),
        color: theme.colors.primary,
    },
    formArea: {
        width: '100%',
        paddingBottom: wp('10%'),
    },
    textForm1: {
        borderWidth: 0.5,
        borderColor: '#888',
        width: '100%',
        height: hp('5%'),
        paddingLeft: 5,
        paddingRight: 5,
        marginBottom: 5,
    },
    textForm2: {
        borderWidth: 0.5,
        borderColor: '#888',
        width: '100%',
        height: hp('20%'),
        paddingLeft: 5,
        paddingRight: 5,
        marginBottom: 5,
    },
    buttonArea: {
        width: '100%',
        height: hp('5%'),
    },
    wrapButton: {
        backgroundColor: '#46c3ad',
        width: wp('100%'),
        height: hp('5%'),
        paddingTop : 10,
        paddingLeft: wp('30%'),
        paddingRight: hp('8%'),
        marginTop: 20,
        justifyContent: 'center',
        borderBottomWidth: 0.5,
        borderColor: '#ccc',
    },
    button: {
        backgroundColor: "#46c3ad",
        width: "100%",
        height: "100%",
        justifyContent: 'center',
        alignItems: 'center',
    },
    buttonTitle: {
        color: 'white',
    },
  link: {
    fontWeight: 'bold',
    color: theme.colors.primary,
  },
  scrollView: {
    backgroundColor: 'orange',
    marginHorizontal: 20,
  },
})

export default memo(CreateScreen);

 

 

missionDetailScreen - index.tsx : 미션 상세 화면

 

 

import React, {Component, memo} from 'react';
import {widthPercentageToDP as wp, heightPercentageToDP as hp} from 'react-native-responsive-screen';
import { Navigation } from '../../types';
import {useState } from 'react';
import { TouchableOpacity, StyleSheet, Text, View } from 'react-native';
import Background from '../../components/Background';
import Logo from '../../components/Logo';
import Header from '../../components/Header';
import Button from '../../components/Button';
import TextInput from '../../components/TextInput';
import BackButton from '../../components/BackButton';
import { theme } from '../../core/theme';
import { emailValidator, passwordValidator } from '../../core/utils';

type Props = {
    navigation: Navigation;
}

const MissionDetailScreen = ({ navigation }: Props) => {
    
        return (
            <View style={styles.container}>
                <View style={styles.titleArea}>
                    <Text style={styles.title}>algorithm Mission</Text>
                </View>
                <View style={styles.formArea}>
                    <Text>카테고리 : 공부/스터디</Text>

                    <Text>최대 인원 : 15명</Text>

                    <Text>미션 기간 : 90일</Text>

                    <Text>미션 제목 : algorithm Mission </Text>

                    <Text>미션 내용 : 1일 1알고리즘</Text>

                    <Text>패널 티 : 강퇴</Text>
                </View>
                <View style={styles.buttonArea}>
                <TouchableOpacity onPress={() => navigation.navigate('HomeScreen')}>
                    <Text style={styles.link}>미션에 참여하시겠습니까?</Text>
                </TouchableOpacity>
                </View>
            </View>
        );
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: 'white',
        paddingLeft: wp('10%'),
        paddingRight: wp('10%'),
        justifyContent: 'center',
    },
    titleArea: {
        width: '100%',
        padding: wp('10%'),
        alignItems: 'center',
    },
    title: {
        fontSize: wp('6%'),
        color: theme.colors.primary,
    },
    formArea: {
        width: '100%',
        paddingBottom: wp('10%'),
    },
    textForm: {
        borderWidth: 0.5,
        borderColor: '#888',
        width: '100%',
        height: hp('5%'),
        paddingLeft: 5,
        paddingRight: 5,
        marginBottom: 5,
    },
    buttonArea: {
        width: '100%',
        height: hp('5%'),
    },
    button: {
        backgroundColor: "#46c3ad",
        width: "100%",
        height: "100%",
        justifyContent: 'center',
        alignItems: 'center',
    },
    buttonTitle: {
        color: 'white',
    },
  link: {
    fontWeight: 'bold',
    color: theme.colors.primary,
  }
})

export default memo(MissionDetailScreen);

 

 

SomethingScreen - index.tsx : 로그인 하지 않으면 이용 불가

 

 

import React, {Component, memo} from 'react';
import {
    View,
    Text,
    Button,
    StyleSheet
} from 'react-native';
import { Navigation } from '../../types';
import BackButton from '../../components/BackButton';

type Props = {
    navigation: Navigation;
}

const SomethingScreen = ({ navigation }: Props) => {
        return (
            <View style={styles.container}>
                <Text>something</Text>
                <BackButton goBack={() => navigation.navigate('HomeScreen')} />
            </View>
        );
    }

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: 'white',
        justifyContent: 'center',
        alignItems: 'center',
    },
})

export default memo(SomethingScreen);

 

 

 

 

 

실행결과

 

 

 

 

 

 

결론

 

구현목적이였어서

아직 불완전한 소스이니 그냥 소스안에서 사용될만한것들만 쓰시고

전체소스는 아직 안쓰시기 바랍니당 ㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜ

코드도 더 깔끔하게 하고 기능 추가해 제작2에 다시올리겠습니당ㅎㅎㅎㅎㅎ

반응형