<react-native>mission 앱 제작1_메인화면, 로그인, 홈 화면, ...
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에 다시올리겠습니당ㅎㅎㅎㅎㅎ