First run timing (#83)
* first run time tracking implementation * Fix values in AsyncStorage calls. Some tweaks to Mixpanel event properties. * remove first launch related items in AsyncStorage after final retrieval
This commit is contained in:
parent
77a86da984
commit
90d17604fc
9 changed files with 66 additions and 18 deletions
|
@ -8,6 +8,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lbry-redux": "lbryio/lbry-redux",
|
"lbry-redux": "lbryio/lbry-redux",
|
||||||
|
"moment": "^2.22.1",
|
||||||
"react": "16.2.0",
|
"react": "16.2.0",
|
||||||
"react-native": "0.52.0",
|
"react-native": "0.52.0",
|
||||||
"react-native-vector-icons": "^4.5.0",
|
"react-native-vector-icons": "^4.5.0",
|
||||||
|
|
|
@ -14,7 +14,13 @@ import {
|
||||||
} from 'react-navigation';
|
} from 'react-navigation';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { addListener } from '../utils/redux';
|
import { addListener } from '../utils/redux';
|
||||||
import { AppState, BackHandler, NativeModules, TextInput } from 'react-native';
|
import {
|
||||||
|
AppState,
|
||||||
|
AsyncStorage,
|
||||||
|
BackHandler,
|
||||||
|
NativeModules,
|
||||||
|
TextInput
|
||||||
|
} from 'react-native';
|
||||||
import { SETTINGS } from 'lbry-redux';
|
import { SETTINGS } from 'lbry-redux';
|
||||||
import { makeSelectClientSetting } from '../redux/selectors/settings';
|
import { makeSelectClientSetting } from '../redux/selectors/settings';
|
||||||
import Feather from 'react-native-vector-icons/Feather';
|
import Feather from 'react-native-vector-icons/Feather';
|
||||||
|
@ -102,16 +108,16 @@ class AppWithNavigationState extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleAppStateChange = (nextAppState) => {
|
_handleAppStateChange = (nextAppState) => {
|
||||||
// this is properly handled in native code at the moment
|
// Check if the app was suspended
|
||||||
/*const { keepDaemonRunning } = this.props;
|
if (AppState.currentState && AppState.currentState.match(/inactive|background/)) {
|
||||||
if (AppState.currentState &&
|
AsyncStorage.getItem('firstLaunchTime').then(start => {
|
||||||
AppState.currentState.match(/inactive|background/) &&
|
if (start !== null && !isNaN(parseInt(start, 10))) {
|
||||||
NativeModules.DaemonServiceControl) {
|
// App suspended during first launch?
|
||||||
if (!keepDaemonRunning) {
|
// If so, this needs to be included as a property when tracking
|
||||||
// terminate the daemon background service when is suspended / inactive
|
AsyncStorage.setItem('firstLaunchSuspended', 'true');
|
||||||
//NativeModules.DaemonServiceControl.stopService();
|
}
|
||||||
}
|
});
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -61,7 +61,7 @@ class FileDownloadButton extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity style={[style, fileDownloadButtonStyle.container]} onPress={() => {
|
<TouchableOpacity style={[style, fileDownloadButtonStyle.container]} onPress={() => {
|
||||||
if (NativeModules.Mixpanel) {
|
if (NativeModules.Mixpanel) {
|
||||||
NativeModules.Mixpanel.track('Purchase Uri', { uri });
|
NativeModules.Mixpanel.track('Purchase Uri', { Uri: uri });
|
||||||
}
|
}
|
||||||
purchaseUri(uri);
|
purchaseUri(uri);
|
||||||
}}>
|
}}>
|
||||||
|
|
|
@ -59,7 +59,7 @@ class FileItem extends React.PureComponent {
|
||||||
<View style={style}>
|
<View style={style}>
|
||||||
<TouchableOpacity style={discoverStyle.container} onPress={() => {
|
<TouchableOpacity style={discoverStyle.container} onPress={() => {
|
||||||
if (NativeModules.Mixpanel) {
|
if (NativeModules.Mixpanel) {
|
||||||
NativeModules.Mixpanel.track('Tap', { uri });
|
NativeModules.Mixpanel.track('Discover Tap', { Uri: uri });
|
||||||
}
|
}
|
||||||
navigation.navigate('File', { uri: uri });
|
navigation.navigate('File', { uri: uri });
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ class MediaPlayer extends React.PureComponent {
|
||||||
if (this.state.firstPlay) {
|
if (this.state.firstPlay) {
|
||||||
if (NativeModules.Mixpanel) {
|
if (NativeModules.Mixpanel) {
|
||||||
const { uri } = this.props;
|
const { uri } = this.props;
|
||||||
NativeModules.Mixpanel.track('Play', { uri });
|
NativeModules.Mixpanel.track('Play', { Uri: uri });
|
||||||
}
|
}
|
||||||
this.setState({ firstPlay: false });
|
this.setState({ firstPlay: false });
|
||||||
this.hidePlayerControls();
|
this.hidePlayerControls();
|
||||||
|
|
|
@ -6,7 +6,7 @@ import SearchInput from './view';
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
search: search => {
|
search: search => {
|
||||||
if (NativeModules.Mixpanel) {
|
if (NativeModules.Mixpanel) {
|
||||||
NativeModules.Mixpanel.track('Search', { query: search });
|
NativeModules.Mixpanel.track('Search', { Query: search });
|
||||||
}
|
}
|
||||||
return dispatch(doSearch(search));
|
return dispatch(doSearch(search));
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Provider, connect } from 'react-redux';
|
import { Provider, connect } from 'react-redux';
|
||||||
import DiscoverPage from './page/discover';
|
import DiscoverPage from './page/discover';
|
||||||
import { AppRegistry, AppState, StyleSheet, Text, View, AsyncStorage, NativeModules } from 'react-native';
|
import {
|
||||||
|
AppRegistry,
|
||||||
|
AppState,
|
||||||
|
AsyncStorage,
|
||||||
|
Text,
|
||||||
|
View,
|
||||||
|
NativeModules
|
||||||
|
} from 'react-native';
|
||||||
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
||||||
import {
|
import {
|
||||||
StackNavigator, addNavigationHelpers
|
StackNavigator, addNavigationHelpers
|
||||||
|
@ -21,6 +28,7 @@ import {
|
||||||
walletReducer
|
walletReducer
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import settingsReducer from './redux/reducers/settings';
|
import settingsReducer from './redux/reducers/settings';
|
||||||
|
import moment from 'moment';
|
||||||
import { reactNavigationMiddleware } from './utils/redux';
|
import { reactNavigationMiddleware } from './utils/redux';
|
||||||
|
|
||||||
function isFunction(object) {
|
function isFunction(object) {
|
||||||
|
@ -105,6 +113,16 @@ persistStore(store, persistOptions, err => {
|
||||||
});
|
});
|
||||||
|
|
||||||
class LBRYApp extends React.Component {
|
class LBRYApp extends React.Component {
|
||||||
|
componentDidMount() {
|
||||||
|
AsyncStorage.getItem('hasLaunched').then(value => {
|
||||||
|
if (value == null || value !== 'true') {
|
||||||
|
AsyncStorage.setItem('hasLaunched', 'true');
|
||||||
|
// only set firstLaunchTime since we've determined that this is the first app launch ever
|
||||||
|
AsyncStorage.setItem('firstLaunchTime', String(moment().unix()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
|
|
@ -1,12 +1,35 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import FeaturedCategory from '../../component/featuredCategory';
|
import FeaturedCategory from '../../component/featuredCategory';
|
||||||
import NavigationActions from 'react-navigation';
|
import NavigationActions from 'react-navigation';
|
||||||
import { Text, View, ScrollView } from 'react-native';
|
import { AsyncStorage, NativeModules, ScrollView, Text, View } from 'react-native';
|
||||||
|
import moment from 'moment';
|
||||||
import discoverStyle from '../../styles/discover';
|
import discoverStyle from '../../styles/discover';
|
||||||
import Feather from 'react-native-vector-icons/Feather';
|
import Feather from 'react-native-vector-icons/Feather';
|
||||||
|
|
||||||
class DiscoverPage extends React.PureComponent {
|
class DiscoverPage extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
// Track the total time taken if this is the first launch
|
||||||
|
AsyncStorage.getItem('firstLaunchTime').then(startTime => {
|
||||||
|
if (startTime !== null && !isNaN(parseInt(startTime, 10))) {
|
||||||
|
// We don't need this value anymore once we've retrieved it
|
||||||
|
AsyncStorage.removeItem('firstLaunchTime');
|
||||||
|
|
||||||
|
// We know this is the first app launch because firstLaunchTime is set and it's a valid number
|
||||||
|
const start = parseInt(startTime, 10);
|
||||||
|
const now = moment().unix();
|
||||||
|
const delta = now - start;
|
||||||
|
AsyncStorage.getItem('firstLaunchSuspended').then(suspended => {
|
||||||
|
AsyncStorage.removeItem('firstLaunchSuspended');
|
||||||
|
const appSuspended = (suspended === 'true');
|
||||||
|
if (NativeModules.Mixpanel) {
|
||||||
|
NativeModules.Mixpanel.track('First Run Time', {
|
||||||
|
'Total Seconds': delta, 'App Suspended': appSuspended
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.props.fetchFeaturedUris();
|
this.props.fetchFeaturedUris();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ class FilePage extends React.PureComponent {
|
||||||
this.fetchFileInfo(this.props);
|
this.fetchFileInfo(this.props);
|
||||||
this.fetchCostInfo(this.props);
|
this.fetchCostInfo(this.props);
|
||||||
if (NativeModules.Mixpanel) {
|
if (NativeModules.Mixpanel) {
|
||||||
NativeModules.Mixpanel.track('Open File Page', { uri: this.props.navigation.state.params.uri });
|
NativeModules.Mixpanel.track('Open File Page', { Uri: this.props.navigation.state.params.uri });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue