diff --git a/app/package.json b/app/package.json index 013b4a1..d302018 100644 --- a/app/package.json +++ b/app/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "lbry-redux": "lbryio/lbry-redux", + "moment": "^2.22.1", "react": "16.2.0", "react-native": "0.52.0", "react-native-vector-icons": "^4.5.0", diff --git a/app/src/component/AppNavigator.js b/app/src/component/AppNavigator.js index a4e4242..d5251bb 100644 --- a/app/src/component/AppNavigator.js +++ b/app/src/component/AppNavigator.js @@ -14,7 +14,13 @@ import { } from 'react-navigation'; import { connect } from 'react-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 { makeSelectClientSetting } from '../redux/selectors/settings'; import Feather from 'react-native-vector-icons/Feather'; @@ -102,16 +108,16 @@ class AppWithNavigationState extends React.Component { } _handleAppStateChange = (nextAppState) => { - // this is properly handled in native code at the moment - /*const { keepDaemonRunning } = this.props; - if (AppState.currentState && - AppState.currentState.match(/inactive|background/) && - NativeModules.DaemonServiceControl) { - if (!keepDaemonRunning) { - // terminate the daemon background service when is suspended / inactive - //NativeModules.DaemonServiceControl.stopService(); - } - }*/ + // Check if the app was suspended + if (AppState.currentState && AppState.currentState.match(/inactive|background/)) { + AsyncStorage.getItem('firstLaunchTime').then(start => { + if (start !== null && !isNaN(parseInt(start, 10))) { + // App suspended during first launch? + // If so, this needs to be included as a property when tracking + AsyncStorage.setItem('firstLaunchSuspended', 'true'); + } + }); + } } render() { diff --git a/app/src/component/fileDownloadButton/view.js b/app/src/component/fileDownloadButton/view.js index 7453d8e..1a7354e 100644 --- a/app/src/component/fileDownloadButton/view.js +++ b/app/src/component/fileDownloadButton/view.js @@ -61,7 +61,7 @@ class FileDownloadButton extends React.PureComponent { return ( { if (NativeModules.Mixpanel) { - NativeModules.Mixpanel.track('Purchase Uri', { uri }); + NativeModules.Mixpanel.track('Purchase Uri', { Uri: uri }); } purchaseUri(uri); }}> diff --git a/app/src/component/fileItem/view.js b/app/src/component/fileItem/view.js index 7ececf2..8a7bedf 100644 --- a/app/src/component/fileItem/view.js +++ b/app/src/component/fileItem/view.js @@ -59,7 +59,7 @@ class FileItem extends React.PureComponent { { if (NativeModules.Mixpanel) { - NativeModules.Mixpanel.track('Tap', { uri }); + NativeModules.Mixpanel.track('Discover Tap', { Uri: uri }); } navigation.navigate('File', { uri: uri }); } diff --git a/app/src/component/mediaPlayer/view.js b/app/src/component/mediaPlayer/view.js index 39929d0..413ec35 100644 --- a/app/src/component/mediaPlayer/view.js +++ b/app/src/component/mediaPlayer/view.js @@ -85,7 +85,7 @@ class MediaPlayer extends React.PureComponent { if (this.state.firstPlay) { if (NativeModules.Mixpanel) { const { uri } = this.props; - NativeModules.Mixpanel.track('Play', { uri }); + NativeModules.Mixpanel.track('Play', { Uri: uri }); } this.setState({ firstPlay: false }); this.hidePlayerControls(); diff --git a/app/src/component/searchInput/index.js b/app/src/component/searchInput/index.js index ab89d5a..a7cefcb 100644 --- a/app/src/component/searchInput/index.js +++ b/app/src/component/searchInput/index.js @@ -6,7 +6,7 @@ import SearchInput from './view'; const perform = dispatch => ({ search: search => { if (NativeModules.Mixpanel) { - NativeModules.Mixpanel.track('Search', { query: search }); + NativeModules.Mixpanel.track('Search', { Query: search }); } return dispatch(doSearch(search)); }, diff --git a/app/src/index.js b/app/src/index.js index a889a3e..390decc 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -1,7 +1,14 @@ import React from 'react'; import { Provider, connect } from 'react-redux'; 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 { StackNavigator, addNavigationHelpers @@ -21,6 +28,7 @@ import { walletReducer } from 'lbry-redux'; import settingsReducer from './redux/reducers/settings'; +import moment from 'moment'; import { reactNavigationMiddleware } from './utils/redux'; function isFunction(object) { @@ -105,6 +113,16 @@ persistStore(store, persistOptions, err => { }); 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() { return ( diff --git a/app/src/page/discover/view.js b/app/src/page/discover/view.js index 2132cb7..f837961 100644 --- a/app/src/page/discover/view.js +++ b/app/src/page/discover/view.js @@ -1,12 +1,35 @@ import React from 'react'; import FeaturedCategory from '../../component/featuredCategory'; 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 Feather from 'react-native-vector-icons/Feather'; class DiscoverPage extends React.PureComponent { 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(); } diff --git a/app/src/page/file/view.js b/app/src/page/file/view.js index 9520922..0535043 100644 --- a/app/src/page/file/view.js +++ b/app/src/page/file/view.js @@ -33,7 +33,7 @@ class FilePage extends React.PureComponent { this.fetchFileInfo(this.props); this.fetchCostInfo(this.props); 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 }); } }