├── _universe
├── entry.js
├── app.json
├── rn-cli.config.js
└── transformer.js
├── components
├── .DS_Store
├── AppComponents
│ ├── TestComponent.js
│ ├── index.js
│ ├── EmailTextInput.js
│ ├── PasswordTextInput.js
│ └── PhoneButton.js
└── __tests__
│ └── StyledText-test.js
├── assets
├── images
│ ├── icon.png
│ ├── splash.png
│ ├── robot-dev.png
│ └── robot-prod.png
└── fonts
│ └── SpaceMono-Regular.ttf
├── constants
├── ApiKeys.demo.js
└── Colors.js
├── README.md
├── __tests__
└── App-test.js
├── package.json
├── app.json
├── api
└── registerForPushNotificationsAsync.js
├── navigation
├── MainTabNavigator.js
└── RootNavigation.js
├── screens
├── auth
│ ├── ForgotPasswordScreen.js
│ ├── LoginScreen.js
│ └── SignupScreen.js
└── TestScreen.js
└── App.js
/_universe/entry.js:
--------------------------------------------------------------------------------
1 | import Expo from 'expo';
2 | import App from '../App.js';
3 |
4 | Expo.registerRootComponent(App);
5 |
--------------------------------------------------------------------------------
/components/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProProgramming101/expo-firebase-auth-starter/HEAD/components/.DS_Store
--------------------------------------------------------------------------------
/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProProgramming101/expo-firebase-auth-starter/HEAD/assets/images/icon.png
--------------------------------------------------------------------------------
/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProProgramming101/expo-firebase-auth-starter/HEAD/assets/images/splash.png
--------------------------------------------------------------------------------
/assets/images/robot-dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProProgramming101/expo-firebase-auth-starter/HEAD/assets/images/robot-dev.png
--------------------------------------------------------------------------------
/assets/images/robot-prod.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProProgramming101/expo-firebase-auth-starter/HEAD/assets/images/robot-prod.png
--------------------------------------------------------------------------------
/assets/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProProgramming101/expo-firebase-auth-starter/HEAD/assets/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/constants/ApiKeys.demo.js:
--------------------------------------------------------------------------------
1 | export default {
2 | FirebaseConfig: {
3 | apiKey: "",
4 | authDomain: "",
5 | databaseURL: "",
6 | projectId: "",
7 | storageBucket: "",
8 | messagingSenderId: ""
9 | }
10 | }
--------------------------------------------------------------------------------
/components/AppComponents/TestComponent.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { View, Text } from 'react-native';
4 |
5 | export default class TestComponent extends React.Component {
6 |
7 | render() {
8 | return TestComponent
9 | }
10 |
11 | }
--------------------------------------------------------------------------------
/components/AppComponents/index.js:
--------------------------------------------------------------------------------
1 |
2 | import TestComponent from './TestComponent';
3 | import EmailTextInput from './EmailTextInput';
4 | import PasswordTextInput from './PasswordTextInput';
5 | import PhoneButton from './PhoneButton';
6 |
7 | export { TestComponent, EmailTextInput, PasswordTextInput, PhoneButton };
--------------------------------------------------------------------------------
/components/__tests__/StyledText-test.js:
--------------------------------------------------------------------------------
1 | import 'react-native';
2 | import React from 'react';
3 | import { MonoText } from '../StyledText';
4 | import renderer from 'react-test-renderer';
5 |
6 | it('renders correctly', () => {
7 | const tree = renderer.create(Snapshot test!).toJSON();
8 |
9 | expect(tree).toMatchSnapshot();
10 | });
11 |
--------------------------------------------------------------------------------
/constants/Colors.js:
--------------------------------------------------------------------------------
1 | const tintColor = '#2f95dc';
2 |
3 | export default {
4 | tintColor,
5 | tabIconDefault: '#ccc',
6 | tabIconSelected: tintColor,
7 | tabBar: '#fefefe',
8 | errorBackground: 'red',
9 | errorText: '#fff',
10 | warningBackground: '#EAEB5E',
11 | warningText: '#666804',
12 | noticeBackground: tintColor,
13 | noticeText: '#fff',
14 | };
15 |
--------------------------------------------------------------------------------
/components/AppComponents/EmailTextInput.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { TextInput } from 'react-native';
4 |
5 | export default class EmailTextInput extends React.Component {
6 |
7 | render() {
8 | return ;
13 | }
14 |
15 | }
--------------------------------------------------------------------------------
/components/AppComponents/PasswordTextInput.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { TextInput } from 'react-native';
4 |
5 | export default class PasswordTextInput extends React.Component {
6 |
7 | render() {
8 | return ;
13 | }
14 |
15 | }
--------------------------------------------------------------------------------
/components/AppComponents/PhoneButton.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { Button, Linking } from 'react-native';
4 |
5 | export default class PhoneButton extends React.Component {
6 |
7 | onPress = () => {
8 | var url = "tel:" + this.props.title;
9 | Linking.openURL(url);
10 | }
11 |
12 | render() {
13 | return
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # expo-firebase-auth-starter
2 |
3 | ## Starter project for using:
4 | - React Native
5 | - Expo
6 | - Firebase
7 |
8 | ## Setup:
9 |
10 | - Create a `constants/ApiKeys.js` file and put your actual firebase config values:
11 | ```
12 | export default {
13 | FirebaseConfig: {
14 | apiKey: "",
15 | authDomain: "",
16 | databaseURL: "",
17 | projectId: "",
18 | storageBucket: "",
19 | messagingSenderId: ""
20 | }
21 | }
22 | ```
23 | - Run `npm install`
24 |
--------------------------------------------------------------------------------
/__tests__/App-test.js:
--------------------------------------------------------------------------------
1 | import 'react-native';
2 | import React from 'react';
3 | import App from '../App';
4 | import renderer from 'react-test-renderer';
5 |
6 | it('renders the loading screen', async () => {
7 | const tree = renderer.create().toJSON();
8 | expect(tree).toMatchSnapshot();
9 | });
10 |
11 | it('renders the root without loading screen', async () => {
12 | const tree = renderer.create().toJSON();
13 | expect(tree).toMatchSnapshot();
14 | });
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "private": true,
4 | "scripts": {
5 | "test": "node ./node_modules/jest/bin/jest.js --watchAll"
6 | },
7 | "jest": {
8 | "preset": "jest-expo"
9 | },
10 | "dependencies": {
11 | "@expo/samples": "2.1.1",
12 | "expo": "^25.0.0",
13 | "firebase": "^4.10.1",
14 | "react": "16.2.0",
15 | "react-native": "https://github.com/expo/react-native/archive/sdk-25.0.0.tar.gz",
16 | "react-navigation": "^1.0.0-beta.27"
17 | },
18 | "devDependencies": {
19 | "jest-expo": "^25.0.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "expo-firebase-auth-starter",
4 | "description": "A very interesting project.",
5 | "slug": "expo-firebase-auth-starter",
6 | "privacy": "public",
7 | "sdkVersion": "25.0.0",
8 | "platforms": ["ios", "android"],
9 | "version": "1.0.0",
10 | "orientation": "portrait",
11 | "icon": "./assets/images/icon.png",
12 | "splash": {
13 | "image": "./assets/images/splash.png",
14 | "resizeMode": "contain",
15 | "backgroundColor": "#ffffff"
16 | },
17 | "ios": {
18 | "supportsTablet": true
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/_universe/app.json:
--------------------------------------------------------------------------------
1 | {
2 | expo: {
3 | name: "My New Project",
4 | description: "No description",
5 | slug: "my-new-project",
6 | privacy: "unlisted",
7 | sdkVersion: "UNVERSIONED",
8 | version: "1.0.0",
9 | orientation: "portrait",
10 | primaryColor: "#cccccc",
11 | icon: "./assets/images/icon.png",
12 | splash: {
13 | image: "./assets/images/splash.png",
14 | resizeMode: "contain",
15 | backgroundColor: "#ffffff"
16 | },
17 | ios: {
18 | supportsTablet: true
19 | },
20 | packagerOpts: {
21 | config: "./_universe/rn-cli.config.js"
22 | },
23 | entryPoint: "_universe/entry.js"
24 | }
25 | }
--------------------------------------------------------------------------------
/api/registerForPushNotificationsAsync.js:
--------------------------------------------------------------------------------
1 | import { Constants, Permissions, Notifications } from 'expo';
2 |
3 | // Example server, implemented in Rails: https://git.io/vKHKv
4 | const PUSH_ENDPOINT = 'https://expo-push-server.herokuapp.com/tokens';
5 |
6 | export default (async function registerForPushNotificationsAsync() {
7 | // Remote notifications do not work in simulators, only on device
8 | if (!Constants.isDevice) {
9 | return;
10 | }
11 |
12 | // Android remote notification permissions are granted during the app
13 | // install, so this will only ask on iOS
14 | let { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS);
15 |
16 | // Stop here if the user did not grant permissions
17 | if (status !== 'granted') {
18 | return;
19 | }
20 |
21 | // Get the token that uniquely identifies this device
22 | let token = await Notifications.getExpoPushTokenAsync();
23 |
24 | // POST the token to our backend so we can use it to send pushes from there
25 | return fetch(PUSH_ENDPOINT, {
26 | method: 'POST',
27 | headers: {
28 | Accept: 'application/json',
29 | 'Content-Type': 'application/json',
30 | },
31 | body: JSON.stringify({
32 | token: {
33 | value: token,
34 | },
35 | }),
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/navigation/MainTabNavigator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Platform } from 'react-native';
3 | import { Ionicons } from '@expo/vector-icons';
4 | import { TabNavigator, TabBarBottom } from 'react-navigation';
5 | import Colors from '../constants/Colors';
6 | import TestScreen from '../screens/TestScreen';
7 |
8 | export default TabNavigator(
9 | {
10 | Test: {
11 | screen: TestScreen,
12 | },
13 | },
14 | {
15 | navigationOptions: ({ navigation }) => ({
16 | tabBarIcon: ({ focused }) => {
17 | const { routeName } = navigation.state;
18 | let iconName;
19 | switch (routeName) {
20 | case 'Test':
21 | iconName =
22 | Platform.OS === 'ios'
23 | ? `ios-information-circle${focused ? '' : '-outline'}`
24 | : 'md-information-circle';
25 | break;
26 | }
27 | return (
28 |
34 | );
35 | },
36 | }),
37 | tabBarComponent: TabBarBottom,
38 | tabBarPosition: 'bottom',
39 | animationEnabled: false,
40 | swipeEnabled: false,
41 | }
42 | );
43 |
--------------------------------------------------------------------------------
/screens/auth/ForgotPasswordScreen.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { StyleSheet, View, Text, TextInput, Button, Alert } from 'react-native';
4 | import { NavigationActions } from 'react-navigation';
5 | import * as firebase from 'firebase';
6 |
7 | export default class ForgotPasswordScreen extends React.Component {
8 |
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | email: "",
13 | };
14 | }
15 |
16 | onResetPasswordPress = () => {
17 | firebase.auth().sendPasswordResetEmail(this.state.email)
18 | .then(() => {
19 | Alert.alert("Password reset email has been sent.");
20 | }, (error) => {
21 | Alert.alert(error.message);
22 | });
23 | }
24 |
25 | onBackToLoginPress = () => {
26 | var navActions = NavigationActions.reset({
27 | index: 0,
28 | actions: [NavigationActions.navigate({routeName: "Login"})]
29 | });
30 | this.props.navigation.dispatch(navActions);
31 | }
32 |
33 | render() {
34 | return (
35 |
36 |
37 | Forgot Password
38 |
39 | { this.setState({email: text}) }}
42 | placeholder="Email"
43 | keyboardType="email-address"
44 | autoCapitalize="none"
45 | autoCorrect={false}
46 | />
47 |
48 |
49 |
50 |
51 | );
52 | }
53 | }
54 |
55 | const styles = StyleSheet.create({
56 |
57 | });
--------------------------------------------------------------------------------
/navigation/RootNavigation.js:
--------------------------------------------------------------------------------
1 | import { Notifications } from 'expo';
2 | import React from 'react';
3 | import { StackNavigator } from 'react-navigation';
4 | import MainTabNavigator from './MainTabNavigator';
5 | import registerForPushNotificationsAsync from '../api/registerForPushNotificationsAsync';
6 |
7 | import LoginScreen from './../screens/auth/LoginScreen';
8 | import SignupScreen from './../screens/auth/SignupScreen';
9 | import ForgotPasswordScreen from './../screens/auth/ForgotPasswordScreen';
10 |
11 | const RootStackNavigator = StackNavigator(
12 | {
13 | Login: { screen: LoginScreen },
14 | Signup: { screen: SignupScreen },
15 | ForgotPassword: { screen: ForgotPasswordScreen },
16 |
17 | Main: { screen: MainTabNavigator, },
18 | },
19 | {
20 | navigationOptions: () => ({
21 | headerTitleStyle: {
22 | fontWeight: 'normal',
23 | },
24 | }),
25 | }
26 | );
27 |
28 | export default class RootNavigator extends React.Component {
29 | componentDidMount() {
30 | this._notificationSubscription = this._registerForPushNotifications();
31 | }
32 |
33 | componentWillUnmount() {
34 | this._notificationSubscription && this._notificationSubscription.remove();
35 | }
36 |
37 | render() {
38 | return ;
39 | }
40 |
41 | _registerForPushNotifications() {
42 | // Send our push token over to our backend so we can receive notifications
43 | // You can comment the following line out if you want to stop receiving
44 | // a notification every time you open the app. Check out the source
45 | // for this function in api/registerForPushNotificationsAsync.js
46 | registerForPushNotificationsAsync();
47 |
48 | // Watch for incoming notifications
49 | this._notificationSubscription = Notifications.addListener(this._handleNotification);
50 | }
51 |
52 | _handleNotification = ({ origin, data }) => {
53 | console.log(`Push notification ${origin} with data: ${JSON.stringify(data)}`);
54 | };
55 | }
56 |
--------------------------------------------------------------------------------
/_universe/rn-cli.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @noflow
3 | */
4 |
5 | const fs = require('fs');
6 | const path = require('path');
7 |
8 | let metroBundlerRequirePath;
9 | const rootPath = path.resolve('.');
10 | const nodeModulePath = path.join(rootPath, 'node_modules');
11 | const reactNativeModulePath = path.join(nodeModulePath, 'react-native');
12 | const stats = fs.lstatSync(reactNativeModulePath);
13 | if (stats.isSymbolicLink()) {
14 | metroBundlerRequirePath = 'react-native/node_modules/metro-bundler';
15 | } else {
16 | metroBundlerRequirePath = 'metro-bundler';
17 | }
18 |
19 | const blacklist = require(metroBundlerRequirePath + '/src/blacklist');
20 | const babelRegisterOnly = require(metroBundlerRequirePath + '/src/babelRegisterOnly');
21 |
22 | const registeredTransformModulePaths = new Set();
23 |
24 | module.exports = {
25 | getBlacklistRE() {
26 | return blacklist([/__internal__\/(.*)/]);
27 | },
28 | getProvidesModuleNodeModules() {
29 | return [];
30 | },
31 | getTransformModulePath() {
32 | const modulePath = path.join(__dirname, 'transformer.js');
33 | if (!registeredTransformModulePaths.has(modulePath)) {
34 | babelRegisterOnly([modulePath]);
35 | registeredTransformModulePaths.add(modulePath);
36 | }
37 | return modulePath;
38 | },
39 | extraNodeModules: getNodeModulesForDirectory(path.resolve('.')),
40 | };
41 |
42 | function getNodeModulesForDirectory(rootPath) {
43 | const nodeModulePath = path.join(rootPath, 'node_modules');
44 | const folders = fs.readdirSync(nodeModulePath);
45 | return folders.reduce((modules, folderName) => {
46 | const folderPath = path.join(nodeModulePath, folderName);
47 | if (folderName.startsWith('@')) {
48 | const scopedModuleFolders = fs.readdirSync(folderPath);
49 | const scopedModules = scopedModuleFolders.reduce((scopedModules, scopedFolderName) => {
50 | scopedModules[`${folderName}/${scopedFolderName}`] = maybeResolveSymlink(
51 | path.join(folderPath, scopedFolderName)
52 | );
53 | return scopedModules;
54 | }, {});
55 | return Object.assign({}, modules, scopedModules);
56 | }
57 | modules[folderName] = maybeResolveSymlink(folderPath);
58 | return modules;
59 | }, {});
60 | }
61 |
62 | function maybeResolveSymlink(maybeSymlinkPath) {
63 | if (fs.lstatSync(maybeSymlinkPath).isSymbolicLink()) {
64 | const resolved = path.resolve(
65 | path.dirname(maybeSymlinkPath),
66 | fs.readlinkSync(maybeSymlinkPath)
67 | );
68 | return resolved;
69 | }
70 | return maybeSymlinkPath;
71 | }
72 |
--------------------------------------------------------------------------------
/screens/auth/LoginScreen.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { StyleSheet, View, Text, TextInput, Button, Alert } from 'react-native';
4 | import { NavigationActions } from 'react-navigation';
5 | import * as firebase from 'firebase';
6 |
7 | export default class LoginScreen extends React.Component {
8 |
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | email: "",
13 | password: "",
14 | };
15 | }
16 |
17 | onLoginPress = () => {
18 | firebase.auth().signInWithEmailAndPassword(this.state.email, this.state.password)
19 | .then(() => { }, (error) => { Alert.alert(error.message); });
20 | }
21 |
22 | onCreateAccountPress = () => {
23 | var navActions = NavigationActions.reset({
24 | index: 0,
25 | actions: [NavigationActions.navigate({routeName: "Signup"})]
26 | });
27 | this.props.navigation.dispatch(navActions);
28 | }
29 |
30 | onForgotPasswordPress = () => {
31 | var navActions = NavigationActions.reset({
32 | index: 0,
33 | actions: [NavigationActions.navigate({routeName: "ForgotPassword"})]
34 | });
35 | this.props.navigation.dispatch(navActions);
36 | }
37 |
38 | render() {
39 | return (
40 |
41 |
42 | Login
43 |
44 | { this.setState({email: text}) }}
47 | placeholder="Email"
48 | keyboardType="email-address"
49 | autoCapitalize="none"
50 | autoCorrect={false}
51 | />
52 |
53 |
54 |
55 | { this.setState({password: text}) }}
58 | placeholder="Password"
59 | secureTextEntry={true}
60 | autoCapitalize="none"
61 | autoCorrect={false}
62 | />
63 |
64 |
65 |
66 |
67 |
68 | );
69 | }
70 | }
71 |
72 | const styles = StyleSheet.create({
73 |
74 | });
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Platform, StatusBar, StyleSheet, View } from 'react-native';
3 | import { AppLoading, Asset, Font } from 'expo';
4 | import { Ionicons } from '@expo/vector-icons';
5 | import RootNavigation from './navigation/RootNavigation';
6 | import MainTabNavigator from './navigation/MainTabNavigator';
7 | import ApiKeys from './constants/ApiKeys';
8 | import * as firebase from 'firebase';
9 |
10 | export default class App extends React.Component {
11 |
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | isLoadingComplete: false,
16 | isAuthenticationReady: false,
17 | isAuthenticated: false,
18 | };
19 |
20 | // Initialize firebase...
21 | if (!firebase.apps.length) { firebase.initializeApp(ApiKeys.FirebaseConfig); }
22 | firebase.auth().onAuthStateChanged(this.onAuthStateChanged);
23 | }
24 |
25 | onAuthStateChanged = (user) => {
26 | this.setState({isAuthenticationReady: true});
27 | this.setState({isAuthenticated: !!user});
28 | }
29 |
30 | render() {
31 | if ( (!this.state.isLoadingComplete || !this.state.isAuthenticationReady) && !this.props.skipLoadingScreen) {
32 | return (
33 |
38 | );
39 | } else {
40 | return (
41 |
42 | {Platform.OS === 'ios' && }
43 | {Platform.OS === 'android' && }
44 | {(this.state.isAuthenticated) ? : }
45 |
46 | );
47 | }
48 | }
49 |
50 | _loadResourcesAsync = async () => {
51 | return Promise.all([
52 | Asset.loadAsync([
53 | require('./assets/images/robot-dev.png'),
54 | require('./assets/images/robot-prod.png'),
55 | ]),
56 | Font.loadAsync({
57 | // This is the font that we are using for our tab bar
58 | ...Ionicons.font,
59 | // We include SpaceMono because we use it in HomeScreen.js. Feel free
60 | // to remove this if you are not using it in your app
61 | 'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'),
62 | }),
63 | ]);
64 | };
65 |
66 | _handleLoadingError = error => {
67 | // In this case, you might want to report the error to your error
68 | // reporting service, for example Sentry
69 | console.warn(error);
70 | };
71 |
72 | _handleFinishLoading = () => {
73 | this.setState({ isLoadingComplete: true });
74 | };
75 | }
76 |
77 | const styles = StyleSheet.create({
78 | container: {
79 | flex: 1,
80 | backgroundColor: '#fff',
81 | },
82 | statusBarUnderlay: {
83 | height: 24,
84 | backgroundColor: 'rgba(0,0,0,0.2)',
85 | },
86 | });
87 |
--------------------------------------------------------------------------------
/screens/auth/SignupScreen.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { StyleSheet, View, Text, TextInput, Button, Alert } from 'react-native';
4 | import { NavigationActions } from 'react-navigation';
5 | import * as firebase from 'firebase';
6 |
7 | export default class SignupScreen extends React.Component {
8 |
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | email: "",
13 | password: "",
14 | passwordConfirm: "",
15 | };
16 | }
17 |
18 | onSignupPress = () => {
19 | if (this.state.password !== this.state.passwordConfirm) {
20 | Alert.alert("Passwords do not match");
21 | return;
22 | }
23 |
24 | firebase.auth().createUserWithEmailAndPassword(this.state.email, this.state.password)
25 | .then(() => { }, (error) => { Alert.alert(error.message); });
26 | }
27 |
28 | onBackToLoginPress = () => {
29 | var navActions = NavigationActions.reset({
30 | index: 0,
31 | actions: [NavigationActions.navigate({routeName: "Login"})]
32 | });
33 | this.props.navigation.dispatch(navActions);
34 | }
35 |
36 | render() {
37 | return (
38 |
39 |
40 | Signup
41 |
42 | { this.setState({email: text}) }}
45 | placeholder="Email"
46 | keyboardType="email-address"
47 | autoCapitalize="none"
48 | autoCorrect={false}
49 | />
50 |
51 |
52 |
53 | { this.setState({password: text}) }}
56 | placeholder="Password"
57 | secureTextEntry={true}
58 | autoCapitalize="none"
59 | autoCorrect={false}
60 | />
61 |
62 |
63 |
64 | { this.setState({passwordConfirm: text}) }}
67 | placeholder="Password (confirm)"
68 | secureTextEntry={true}
69 | autoCapitalize="none"
70 | autoCorrect={false}
71 | />
72 |
73 |
74 |
75 |
76 |
77 | );
78 | }
79 | }
80 |
81 | const styles = StyleSheet.create({
82 |
83 | });
--------------------------------------------------------------------------------
/screens/TestScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ScrollView, StyleSheet, Text, View, TextInput, Button, Linking, Alert, } from 'react-native';
3 | import { TestComponent, PhoneButton } from './../components/AppComponents';
4 | import * as firebase from 'firebase';
5 |
6 | export default class TestScreen extends React.Component {
7 | static navigationOptions = {
8 | header: null,
9 | };
10 |
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | currentPassword: "",
15 | newPassword: "",
16 | newEmail: "",
17 | };
18 | }
19 |
20 | // Occurs when signout is pressed...
21 | onSignoutPress = () => {
22 | firebase.auth().signOut();
23 | }
24 |
25 | // Reauthenticates the current user and returns a promise...
26 | reauthenticate = (currentPassword) => {
27 | var user = firebase.auth().currentUser;
28 | var cred = firebase.auth.EmailAuthProvider.credential(user.email, currentPassword);
29 | return user.reauthenticateWithCredential(cred);
30 | }
31 |
32 | // Changes user's password...
33 | onChangePasswordPress = () => {
34 | this.reauthenticate(this.state.currentPassword).then(() => {
35 | var user = firebase.auth().currentUser;
36 | user.updatePassword(this.state.newPassword).then(() => {
37 | Alert.alert("Password was changed");
38 | }).catch((error) => { console.log(error.message); });
39 | }).catch((error) => { console.log(error.message) });
40 | }
41 |
42 | // Changes user's email...
43 | onChangeEmailPress = () => {
44 | this.reauthenticate(this.state.currentPassword).then(() => {
45 | var user = firebase.auth().currentUser;
46 | user.updateEmail(this.state.newEmail).then(() => {
47 | Alert.alert("Email was changed");
48 | }).catch((error) => { console.log(error.message); });
49 | }).catch((error) => { console.log(error.message) });
50 | }
51 |
52 | render() {
53 | return (
54 |
55 |
56 |
57 | { this.setState({currentPassword: text}) }}
60 | />
61 |
62 | { this.setState({newPassword: text}) }}
65 | />
66 |
67 |
68 |
69 | { this.setState({newEmail: text}) }}
72 | />
73 |
74 |
75 |
76 |
77 | );
78 | }
79 | }
80 |
81 | const styles = StyleSheet.create({
82 | text: { color: "white", fontWeight: "bold", textAlign: "center", fontSize: 20, },
83 | textInput: { borderWidth:1, borderColor:"gray", marginVertical: 20, padding:10, height:40, alignSelf: "stretch", fontSize: 18, },
84 | });
85 |
--------------------------------------------------------------------------------
/_universe/transformer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * Note: This is a fork of the fb-specific transform.js
10 | *
11 | */
12 | 'use strict';
13 |
14 | /**
15 | * [Expo] This transformer was based on React Native's transformer with the
16 | * following changes:
17 | * - Makes the packager use react-native-lab's copy of react-native
18 | * - Rewrites the paths of this module's dependencies so we load the
19 | * dependencies from react-native-lab's copy of react-native, to simulate
20 | * if we hadn't forked the transformer at all
21 | */
22 |
23 | const fs = require('fs');
24 | const path = require('path');
25 |
26 | let moduleBasePath = '';
27 | let isLinked = false;
28 | const rootPath = path.resolve('.');
29 | const nodeModulePath = path.join(rootPath, 'node_modules');
30 | const reactNativeModulePath = path.join(nodeModulePath, 'react-native');
31 | const stats = fs.lstatSync(reactNativeModulePath);
32 | if (stats.isSymbolicLink()) {
33 | isLinked = true;
34 | moduleBasePath = path.join(reactNativeModulePath, 'node_modules') + path.sep;
35 | }
36 |
37 | const babel = require(moduleBasePath + 'babel-core');
38 | const crypto = require('crypto');
39 | const externalHelpersPlugin = require(moduleBasePath + 'babel-plugin-external-helpers');
40 | const generate = require(moduleBasePath + 'babel-generator').default;
41 | const inlineRequiresPlugin = require(moduleBasePath + 'babel-preset-fbjs/plugins/inline-requires');
42 | const makeHMRConfig = require(moduleBasePath + 'babel-preset-react-native/configs/hmr');
43 | const resolvePlugins = require(moduleBasePath + 'babel-preset-react-native/lib/resolvePlugins');
44 |
45 | const { compactMapping } = require(moduleBasePath + 'metro-bundler/src/Bundler/source-map');
46 |
47 | const cacheKeyParts = [
48 | fs.readFileSync(__filename),
49 | require(moduleBasePath + 'babel-plugin-external-helpers/package.json').version,
50 | require(moduleBasePath + 'babel-preset-fbjs/package.json').version,
51 | require(moduleBasePath + 'babel-preset-react-native/package.json').version,
52 | ];
53 |
54 | const EXPO_REACT_NATIVE_PATH = isLinked
55 | ? path.join(process.env.EXPO_UNIVERSE_DIR, 'react-native-lab', 'react-native')
56 | : reactNativeModulePath;
57 | if (!fs.existsSync(EXPO_REACT_NATIVE_PATH)) {
58 | throw new Error(
59 | `Expo copy of React Native could not be found. Are you sure it exists at: ${EXPO_REACT_NATIVE_PATH}?`
60 | );
61 | }
62 | let EXPO_REACT_PATH = isLinked
63 | ? path.join(EXPO_REACT_NATIVE_PATH, 'node_modules/react')
64 | : path.join(nodeModulePath, 'react');
65 | if (!fs.existsSync(EXPO_REACT_PATH)) {
66 | throw new Error(
67 | `React Native's "react" peer could not be found. Are you sure it exists at: ${EXPO_REACT_PATH}?`
68 | );
69 | }
70 |
71 | /**
72 | * Return a memoized function that checks for the existence of a
73 | * project level .babelrc file, and if it doesn't exist, reads the
74 | * default RN babelrc file and uses that.
75 | */
76 | const getBabelRC = (function() {
77 | let babelRC = null;
78 |
79 | return function _getBabelRC(projectRoot) {
80 | if (babelRC !== null) {
81 | return babelRC;
82 | }
83 |
84 | babelRC = { plugins: [] };
85 |
86 | // Let's look for the .babelrc in the project root.
87 | // In the future let's look into adding a command line option to specify
88 | // this location.
89 | let projectBabelRCPath;
90 | if (projectRoot) {
91 | projectBabelRCPath = path.resolve(projectRoot, '.babelrc');
92 | }
93 |
94 | // If a .babelrc file doesn't exist in the project,
95 | // use the Babel config provided with react-native.
96 | if (!projectBabelRCPath || !fs.existsSync(projectBabelRCPath)) {
97 | babelRC = {
98 | presets: [require('babel-preset-react-native')],
99 | plugins: [],
100 | };
101 |
102 | // Require the babel-preset's listed in the default babel config
103 | // $FlowFixMe: dynamic require can't be avoided
104 | babelRC.presets = babelRC.presets.map(preset => require('babel-preset-' + preset));
105 | babelRC.plugins = resolvePlugins(babelRC.plugins);
106 | } else {
107 | // if we find a .babelrc file we tell babel to use it
108 | babelRC.extends = projectBabelRCPath;
109 | }
110 |
111 | return babelRC;
112 | };
113 | })();
114 |
115 | /**
116 | * Given a filename and options, build a Babel
117 | * config object with the appropriate plugins.
118 | */
119 | function buildBabelConfig(filename, options) {
120 | const babelRC = getBabelRC(options.projectRoot);
121 |
122 | const extraConfig = {
123 | // [Expo] We add the module resolver plugin (as a preset) to make sure
124 | // we're looking up peer deps (like react-native and react) in the
125 | // right place
126 | presets: [...(babelRC.presets || []), buildModuleResolverPreset()],
127 | // [Expo] When running in universe, we don't want to disable
128 | // babelRC lookup for dependencies
129 | babelrc: false,
130 | code: false,
131 | filename,
132 | };
133 |
134 | let config = Object.assign({}, babelRC, extraConfig);
135 |
136 | // Add extra plugins
137 | const extraPlugins = [externalHelpersPlugin];
138 |
139 | var inlineRequires = options.inlineRequires;
140 | var blacklist = typeof inlineRequires === 'object' ? inlineRequires.blacklist : null;
141 | if (inlineRequires && !(blacklist && filename in blacklist)) {
142 | extraPlugins.push(inlineRequiresPlugin);
143 | }
144 |
145 | config.plugins = extraPlugins.concat(config.plugins);
146 |
147 | if (options.dev && options.hot) {
148 | const hmrConfig = makeHMRConfig(options, filename);
149 | config = Object.assign({}, config, hmrConfig);
150 | }
151 |
152 | return Object.assign({}, babelRC, config);
153 | }
154 |
155 | function transform({ filename, options, src }) {
156 | options = options || { platform: '', projectRoot: '', inlineRequires: false };
157 |
158 | const OLD_BABEL_ENV = process.env.BABEL_ENV;
159 | process.env.BABEL_ENV = options.dev ? 'development' : 'production';
160 |
161 | try {
162 | const babelConfig = buildBabelConfig(filename, options);
163 | const { ast, ignored } = babel.transform(src, babelConfig);
164 |
165 | if (ignored) {
166 | return {
167 | ast: null,
168 | code: src,
169 | filename,
170 | map: null,
171 | };
172 | } else {
173 | const result = generate(
174 | ast,
175 | {
176 | comments: false,
177 | compact: false,
178 | filename,
179 | retainLines: !!options.retainLines,
180 | sourceFileName: filename,
181 | sourceMaps: true,
182 | },
183 | src
184 | );
185 |
186 | return {
187 | ast,
188 | code: result.code,
189 | filename,
190 | map: options.generateSourceMaps ? result.map : result.rawMappings.map(compactMapping),
191 | };
192 | }
193 | } catch (e) {
194 | console.error(e);
195 | throw e;
196 | } finally {
197 | process.env.BABEL_ENV = OLD_BABEL_ENV;
198 | }
199 | }
200 |
201 | /**
202 | * [Expo] Returns an Expo-internal Babel preset for aliasing react-native and
203 | * react imports
204 | */
205 | function buildModuleResolverPreset() {
206 | return {
207 | plugins: [
208 | [
209 | require('babel-plugin-module-resolver').default,
210 | {
211 | alias: {
212 | react: EXPO_REACT_PATH,
213 | 'react-native': EXPO_REACT_NATIVE_PATH,
214 | },
215 | },
216 | ],
217 | ],
218 | };
219 | }
220 |
221 | function getCacheKey() {
222 | var key = crypto.createHash('md5');
223 | cacheKeyParts.forEach(part => key.update(part));
224 | return key.digest('hex');
225 | }
226 |
227 | module.exports = {
228 | transform,
229 | getCacheKey,
230 | };
231 |
--------------------------------------------------------------------------------