perform user authentication on first page of first run (#572)

* perform user authentication on first page of first run
* additional firebase events for first run
This commit is contained in:
Akinwale Ariwodola 2019-06-04 05:56:45 +01:00 committed by GitHub
parent 309b1e70e6
commit 7e136faea5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 145 additions and 92 deletions

View file

@ -16,6 +16,7 @@ const Constants = {
KEY_FIRST_RUN_EMAIL: "firstRunEmail", KEY_FIRST_RUN_EMAIL: "firstRunEmail",
KEY_FIRST_RUN_PASSWORD: "firstRunPassword", KEY_FIRST_RUN_PASSWORD: "firstRunPassword",
KEY_FIRST_USER_AUTH: "firstUserAuth",
KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail", KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail",
KEY_EMAIL_VERIFY_PENDING: "emailVerifyPending", KEY_EMAIL_VERIFY_PENDING: "emailVerifyPending",

View file

@ -1,8 +1,6 @@
import React from 'react'; import React from 'react';
import { Lbry } from 'lbry-redux'; import { Lbry } from 'lbry-redux';
import { import {
ActivityIndicator,
Linking,
NativeModules, NativeModules,
Platform, Platform,
Text, Text,
@ -15,25 +13,16 @@ import Constants from 'constants';
import firstRunStyle from 'styles/firstRun'; import firstRunStyle from 'styles/firstRun';
class EmailCollectPage extends React.PureComponent { class EmailCollectPage extends React.PureComponent {
static MAX_STATUS_TRIES = 30;
state = { state = {
email: null, email: null,
authenticationStarted: false,
authenticationFailed: false,
placeholder: 'you@example.com', placeholder: 'you@example.com',
statusTries: 0,
verifying: true verifying: true
}; };
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const { authenticating, authToken, showNextPage } = this.props; const { showNextPage } = this.props;
const { user } = nextProps; const { user } = nextProps;
if (this.state.authenticationStarted && !authenticating && authToken === null) {
this.setState({ authenticationFailed: true, authenticationStarted: false });
}
if (this.state.verifying) { if (this.state.verifying) {
if (user && user.primary_email && user.has_verified_email) { if (user && user.primary_email && user.has_verified_email) {
if (showNextPage) { if (showNextPage) {
@ -45,33 +34,6 @@ class EmailCollectPage extends React.PureComponent {
} }
} }
componentDidMount() {
// call user/new
const { generateAuthToken, authenticating, authToken } = this.props;
if (!authenticating) {
this.startAuthenticating();
}
}
startAuthenticating = () => {
const { authenticate } = this.props;
this.setState({ authenticationStarted: true, authenticationFailed: false });
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
Lbry.status().then(info => {
authenticate(appVersion, Platform.OS);
}).catch(error => {
if (this.state.statusTries >= EmailCollectPage.MAX_STATUS_TRIES) {
this.setState({ authenticationFailed: true });
} else {
setTimeout(() => {
this.startAuthenticating();
this.setState({ statusTries: this.state.statusTries + 1 });
}, 1000); // Retry every second for a maximum of MAX_STATUS_TRIES tries (30 seconds)
}
});
});
}
handleChangeText = (text) => { handleChangeText = (text) => {
// save the value to the state email // save the value to the state email
const { onEmailChanged } = this.props; const { onEmailChanged } = this.props;
@ -84,49 +46,32 @@ class EmailCollectPage extends React.PureComponent {
} }
render() { render() {
const { authenticating, authToken, onEmailChanged, onEmailViewLayout, emailToVerify } = this.props; const { onEmailViewLayout } = this.props;
let content; const content = (
if (this.state.authenticationFailed) { <View onLayout={onEmailViewLayout}>
// Ask the user to try again <Text style={firstRunStyle.title}>Setup account</Text>
content = ( <TextInput style={firstRunStyle.emailInput}
<View> placeholder={this.state.placeholder}
<Text style={firstRunStyle.paragraph}>The LBRY servers were unreachable at this time. Please check your Internet connection and then restart the app to try again.</Text> underlineColorAndroid="transparent"
</View> selectionColor={Colors.NextLbryGreen}
); value={this.state.email}
} else if (!authToken || authenticating || this.state.verifying) { onChangeText={text => this.handleChangeText(text)}
content = ( onFocus={() => {
<View style={firstRunStyle.centered}> if (!this.state.email || this.state.email.length === 0) {
<ActivityIndicator size="large" color={Colors.White} style={firstRunStyle.waiting} /> this.setState({ placeholder: '' });
<Text style={firstRunStyle.paragraph}>Please wait while we get some things ready...</Text> }
</View> }}
); onBlur={() => {
} else { if (!this.state.email || this.state.email.length === 0) {
content = ( this.setState({ placeholder: 'you@example.com' });
<View onLayout={onEmailViewLayout}> }
<Text style={firstRunStyle.title}>Setup account</Text> }}
<TextInput style={firstRunStyle.emailInput} />
placeholder={this.state.placeholder} <Text style={firstRunStyle.paragraph}>An account will allow you to earn rewards and keep your account and settings synced.</Text>
underlineColorAndroid="transparent" <Text style={firstRunStyle.infoParagraph}>This information is disclosed only to LBRY, Inc. and not to the LBRY network.</Text>
selectionColor={Colors.NextLbryGreen} </View>
value={this.state.email} );
onChangeText={text => this.handleChangeText(text)}
onFocus={() => {
if (!this.state.email || this.state.email.length === 0) {
this.setState({ placeholder: '' });
}
}}
onBlur={() => {
if (!this.state.email || this.state.email.length === 0) {
this.setState({ placeholder: 'you@example.com' });
}
}}
/>
<Text style={firstRunStyle.paragraph}>An account will allow you to earn rewards and keep your account and settings synced.</Text>
<Text style={firstRunStyle.infoParagraph}>This information is disclosed only to LBRY, Inc. and not to the LBRY network.</Text>
</View>
);
}
return ( return (
<View style={firstRunStyle.container}> <View style={firstRunStyle.container}>

View file

@ -1,15 +1,108 @@
import React from 'react'; import React from 'react';
import { Lbry } from 'lbry-redux'; import { Lbry } from 'lbry-redux';
import { View, Text, Linking } from 'react-native'; import {
ActivityIndicator,
NativeModules,
Platform,
Text,
View
} from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants';
import firstRunStyle from 'styles/firstRun'; import firstRunStyle from 'styles/firstRun';
class WelcomePage extends React.PureComponent { class WelcomePage extends React.PureComponent {
static MAX_STATUS_TRIES = 60;
state = {
authenticationStarted: false,
authenticationFailed: false,
sdkStarted: false,
statusTries: 0,
};
componentWillReceiveProps(nextProps) {
const { authenticating, authToken } = this.props;
if (this.state.authenticationStarted && !authenticating) {
if (authToken === null) {
this.setState({ authenticationFailed: true, authenticationStarted: false });
} else {
// first_user_auth because it's the first time
AsyncStorage.getItem(Constants.KEY_FIRST_USER_AUTH).then(firstUserAuth => {
if ('true' !== firstUserAuth) {
// first_user_auth
NativeModules.Firebase.track('first_user_auth', null);
AsyncStorage.setItem(Constants.KEY_FIRST_USER_AUTH, 'true');
}
});
}
}
}
componentDidMount() {
// call user/new
const { generateAuthToken, authenticating, authToken } = this.props;
if (!authenticating) {
this.startAuthenticating();
}
}
startAuthenticating = () => {
const { authenticate } = this.props;
this.setState({ authenticationStarted: true, authenticationFailed: false });
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
Lbry.status().then(info => {
this.setState({ sdkStarted: true });
authenticate(appVersion, Platform.OS);
}).catch(error => {
if (this.state.statusTries >= WelcomePage.MAX_STATUS_TRIES) {
this.setState({ authenticationFailed: true });
// sdk_start_failed
NativeModules.Firebase.track('sdk_start_failed', null);
} else {
setTimeout(() => {
this.startAuthenticating();
this.setState({ statusTries: this.state.statusTries + 1 });
}, 1000); // Retry every second for a maximum of MAX_STATUS_TRIES tries (60 seconds)
}
});
});
}
render() { render() {
const { authenticating, authToken, onWelcomePageLayout } = this.props;
let content;
if (this.state.authenticationFailed) {
// Ask the user to try again
content = (
<View>
<Text style={firstRunStyle.paragraph}>The LBRY servers were unreachable at this time. Please check your Internet connection and then restart the app to try again.</Text>
</View>
);
} else if (!authToken || authenticating) {
content = (
<View style={firstRunStyle.centered}>
<ActivityIndicator size="large" color={Colors.White} style={firstRunStyle.waiting} />
<Text style={firstRunStyle.paragraph}>Please wait while we get some things ready...</Text>
</View>
);
} else {
content = (
<View onLayout={onWelcomePageLayout}>
<Text style={firstRunStyle.title}>Welcome to LBRY.</Text>
<Text style={firstRunStyle.paragraph}>LBRY is a community-controlled content platform where you can find and publish videos, music, books, and more.</Text>
</View>
);
}
return ( return (
<View style={firstRunStyle.container}> <View style={firstRunStyle.container}>
<Text style={firstRunStyle.title}>Welcome to LBRY.</Text> {content}
<Text style={firstRunStyle.paragraph}>LBRY is a community-controlled content platform where you can find and publish videos, music, books, and more.</Text>
</View> </View>
); );
} }

View file

@ -37,7 +37,7 @@ class FirstRunScreen extends React.PureComponent {
showSkip: false, showSkip: false,
isEmailVerified: false, isEmailVerified: false,
skipAccountConfirmed: false, skipAccountConfirmed: false,
showBottomContainer: true, showBottomContainer: false,
walletPassword: null, walletPassword: null,
syncApplyStarted: false syncApplyStarted: false
}; };
@ -252,6 +252,10 @@ class FirstRunScreen extends React.PureComponent {
this.setState({ showBottomContainer: true }); this.setState({ showBottomContainer: true });
} }
onWelcomePageLayout = () => {
this.setState({ showBottomContainer: true });
}
onSkipSwitchChanged = (checked) => { onSkipSwitchChanged = (checked) => {
this.setState({ skipAccountConfirmed: checked }); this.setState({ skipAccountConfirmed: checked });
} }
@ -291,16 +295,17 @@ class FirstRunScreen extends React.PureComponent {
let page = null; let page = null;
switch (this.state.currentPage) { switch (this.state.currentPage) {
case Constants.FIRST_RUN_PAGE_WELCOME: case Constants.FIRST_RUN_PAGE_WELCOME:
page = (<WelcomePage />); page = (<WelcomePage
authenticating={authenticating}
authToken={authToken}
authenticate={authenticate}
onWelcomePageLayout={this.onWelcomePageLayout} />);
break; break;
case Constants.FIRST_RUN_PAGE_EMAIL_COLLECT: case Constants.FIRST_RUN_PAGE_EMAIL_COLLECT:
page = (<EmailCollectPage page = (<EmailCollectPage
user={user} user={user}
showNextPage={this.showNextPage} showNextPage={this.showNextPage}
authenticating={authenticating}
authToken={authToken}
authenticate={authenticate}
onEmailChanged={this.onEmailChanged} onEmailChanged={this.onEmailChanged}
onEmailViewLayout={this.onEmailViewLayout} />); onEmailViewLayout={this.onEmailViewLayout} />);
break; break;

View file

@ -241,9 +241,10 @@ class SplashScreen extends React.PureComponent {
} }
}); });
// Start measuring the first launch time from the splash screen (time from daemon start to user interaction) // Start measuring the first launch time from the splash screen
// (time to first user interaction - after first run completed)
AsyncStorage.getItem('hasLaunched').then(value => { AsyncStorage.getItem('hasLaunched').then(value => {
if (value == null || value !== 'true') { if ('true' !== value) {
AsyncStorage.setItem('hasLaunched', 'true'); AsyncStorage.setItem('hasLaunched', 'true');
// only set firstLaunchTime since we've determined that this is the first app launch ever // only set firstLaunchTime since we've determined that this is the first app launch ever
AsyncStorage.setItem('firstLaunchTime', String(moment().unix())); AsyncStorage.setItem('firstLaunchTime', String(moment().unix()));

View file

@ -22,7 +22,6 @@ class VerificationScreen extends React.PureComponent {
state = { state = {
currentPage: null, currentPage: null,
emailSubmitted: false, emailSubmitted: false,
isFirstRun: false,
launchUrl: null, launchUrl: null,
showSkip: false, showSkip: false,
skipAccountConfirmed: false, skipAccountConfirmed: false,

View file

@ -4,12 +4,15 @@ import android.content.Context;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle;
import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReactMethod;
import com.google.firebase.analytics.FirebaseAnalytics;
import io.lbry.browser.MainActivity; import io.lbry.browser.MainActivity;
public class FirstRunModule extends ReactContextBaseJavaModule { public class FirstRunModule extends ReactContextBaseJavaModule {
@ -40,5 +43,11 @@ public class FirstRunModule extends ReactContextBaseJavaModule {
SharedPreferences.Editor editor = sp.edit(); SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("firstRun", false); editor.putBoolean("firstRun", false);
editor.commit(); editor.commit();
FirebaseAnalytics firebase = FirebaseAnalytics.getInstance(context);
if (firebase != null) {
Bundle bundle = new Bundle();
firebase.logEvent("first_run_completed", bundle);
}
} }
} }