This repository has been archived by the owner on Nov 25, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
App.js
207 lines (184 loc) · 7.3 KB
/
App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import AsyncStorage from '@react-native-community/async-storage';
import auth from '@react-native-firebase/auth';
import crashlytics from '@react-native-firebase/crashlytics';
import functions from '@react-native-firebase/functions';
import messaging from '@react-native-firebase/messaging';
import DevBuildBanner from 'dev/DevBuildBanner';
import { _codepushEnabled } from 'dev/index';
import React from 'react';
import { AppState, StatusBar } from 'react-native';
import RNBootSplash from "react-native-bootsplash";
import codePush from "react-native-code-push";
import { ThemeProvider } from 'react-native-elements';
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import ConnectionBanner from 'reusables/ConnectionStatusBanner';
import AccountSetUp from 'screens/Authentication/AccountSetUp';
import AuthDecisionPage from 'screens/Authentication/AuthDecisionPage';
import LandingPage from 'screens/Authentication/LandingPage';
import Login from 'screens/Authentication/Login';
import PasswordReset from 'screens/Authentication/PasswordReset';
import SignUp from 'screens/Authentication/SignUp';
import MainTabNav from 'screens/MainTabNav';
import AddProfilePic from 'screens/Onboarding/AddProfilePic';
import CovidWarningPage from 'screens/Onboarding/CovidWarningPage';
import SwiperOnboarding from 'screens/Onboarding/SwiperOnboarding';
import MainTheme from 'styling/mainTheme';
import { analyticsAppOpen, analyticsScreenVisit, setAnalyticsID } from 'utils/analyticsFunctions';
import { ASYNC_SETUP_KEY, ASYNC_TOKEN_KEY, logError, LONG_TIMEOUT, timedPromise } from 'utils/helpers';
import NavigationService from 'utils/NavigationService';
import { emitEvent, events } from 'utils/subcriptionEvents';
import { TabBarProfilePic } from 'screens/MainTabNav'
export default class App extends React.Component {
constructor(props) {
super(props)
this.topLevelNavigator = null
this.appState = AppState.currentState //will be "active"
this.lastBackgroundedTime = Date.now();
this.backgroundTimeThreshold = 30 * 60 * 1000; //30 minutes of backgorund time = codepush sync
}
componentDidMount = () => {
//Don't unsubscribe, so that if the user is signed out
//(manually or automatically by Firebase), he is still rerouted
auth().onAuthStateChanged(this.handleAuthChange)
//Fresh launch, look for and install codepush updates before removing launch screen.
if (_codepushEnabled()) {
this.syncCodepush(LONG_TIMEOUT)
.catch(err => { if (err.name != "timeout") logError(err) })
.finally(() => this.removeSplashScreen())
} else {
this.removeSplashScreen()
}
AppState.addEventListener("change", this.handleAppStateChange);
}
componentWillUnmount() {
AppState.removeEventListener("change", this.handleAppStateChange);
}
render() {
return (
<ThemeProvider theme={MainTheme}>
<StatusBar backgroundColor={MainTheme.colors.statusBar} barStyle="light-content" />
<Navigator
ref={ref => NavigationService.setTopLevelNavigator(ref)}
onNavigationStateChange={(prevState, currentState) => {
const currentRouteName = this.getActiveRouteName(currentState);
const previousRouteName = this.getActiveRouteName(prevState);
if (previousRouteName !== currentRouteName) {
analyticsScreenVisit(currentRouteName)
}
}} />
<ConnectionBanner />
<DevBuildBanner />
</ThemeProvider>
)
}
// gets the current screen from navigation state
getActiveRouteName = (navigationState) => {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return this.getActiveRouteName(route);
}
return route.routeName;
}
//if there's no internet there's a chance this will fail, so we won't
//include the errors in crashlytics
//TODO: ^improve this
disassociateToken = async () => {
try {
if (!messaging().isDeviceRegisteredForRemoteMessages) return;
//See MainTabNav.js for reeasons being getToken()'s signature
const fcmToken = await messaging().getToken(undefined, '*')
const syncFunction = functions().httpsCallable('disassociateToken')
await timedPromise(syncFunction(fcmToken), LONG_TIMEOUT)
//meh we don't really care for error checking here
} catch (err) {
if (err.name != "timeout") logError(err, false)
}
}
removeSplashScreen = () => {
RNBootSplash.hide({ fade: true })
.then(() => emitEvent(events.SPLASH_SCREEN_DISMISSED))
.catch(err => logError(err));
}
handleAuthChange = async (user) => {
try {
if (!user) {
await AsyncStorage.removeItem(ASYNC_TOKEN_KEY)
await AsyncStorage.removeItem(ASYNC_SETUP_KEY)
this.disassociateToken()
} else {
setAnalyticsID()
crashlytics().setUserId(user.uid).catch(err => logError(err))
this.refreshAuth()
}
TabBarProfilePic.resetTabBarProfilePicImageRef()
NavigationService.navigate("AuthDecisionPage")
} catch (err) {
logError(err)
}
}
//Sync with codepush if you've been in the background long enough.
//Also log for analytics purposes
//Also reloads auth if needed
handleAppStateChange = (nextAppState) => {
//Codepush
if (this.appState.match(/inactive|background/) &&
nextAppState === "active" &&
Date.now() - this.lastBackgroundedTime > this.backgroundTimeThreshold &&
_codepushEnabled()) {
RNBootSplash.show({ fade: false })
.then(_ => this.syncCodepush(LONG_TIMEOUT))
.catch(err => { if (err.name != "timeout") logError(err) })
.finally(() => this.removeSplashScreen())
}
//Timestamp updating
if (nextAppState.match(/inactive|background/)) this.lastBackgroundedTime = Date.now()
//Analytics
if (this.appState == "background" && nextAppState == "active") analyticsAppOpen()
//Refreshing Auth
this.refreshAuth()
this.appState = nextAppState;
};
//Codepush has this problem where if the user is connected to network but not internet
//The syncing just hangs...
//This times out to fix that, but //TODO: this may make it such that there can be a sudden
//codepush-induces JS bundle reset when the user is using the app and internet comes back (?)
syncCodepush = (timeoutInMillis) => {
return timedPromise(codePush.sync({ installMode: codePush.InstallMode.IMMEDIATE }), timeoutInMillis)
}
refreshAuth = async () => {
const currentUser = auth().currentUser
if (!currentUser) return;
//When reloading we only (at the moment) care about changes to email settings
const lastEmail = currentUser.email
const lastVerified = currentUser.emailVerified
await auth().currentUser.reload()
const newCurrentUser = auth().currentUser
if (lastEmail != newCurrentUser?.email || !lastVerified != newCurrentUser?.emailVerified) {
emitEvent(events.NEW_AUTH)
}
}
}
//Using a switch navigator
const Navigator = createAppContainer(
createSwitchNavigator(
{
AuthDecisionPage,
SignUp,
AccountSetUp,
Login,
MainTabNav,
LandingPage,
PasswordReset,
AddProfilePic,
CovidWarningPage,
SwiperOnboarding
},
{
initialRouteName: 'AuthDecisionPage'
}
)
)