Merge pull request #166 from lbryio/first-run-improvements

implement extendable first run experience starting with welcome page
This commit is contained in:
Akinwale Ariwodola 2018-06-11 04:41:55 +01:00 committed by GitHub
commit 31168d9ed8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 333 additions and 78 deletions

View file

@ -2,6 +2,7 @@ import React from 'react';
import AboutPage from '../page/about'; import AboutPage from '../page/about';
import DiscoverPage from '../page/discover'; import DiscoverPage from '../page/discover';
import FilePage from '../page/file'; import FilePage from '../page/file';
import FirstRunScreen from '../page/firstRun';
import SearchPage from '../page/search'; import SearchPage from '../page/search';
import TrendingPage from '../page/trending'; import TrendingPage from '../page/trending';
import SettingsPage from '../page/settings'; import SettingsPage from '../page/settings';
@ -99,6 +100,12 @@ const drawer = DrawerNavigator({
}); });
export const AppNavigator = new StackNavigator({ export const AppNavigator = new StackNavigator({
FirstRun: {
screen: FirstRunScreen,
navigationOptions: {
drawerLockMode: 'locked-closed'
}
},
Splash: { Splash: {
screen: SplashScreen, screen: SplashScreen,
navigationOptions: { navigationOptions: {
@ -114,7 +121,7 @@ export const AppNavigator = new StackNavigator({
class AppWithNavigationState extends React.Component { class AppWithNavigationState extends React.Component {
static supportedDisplayTypes = ['toast']; static supportedDisplayTypes = ['toast'];
componentWillMount() { componentWillMount() {
AppState.addEventListener('change', this._handleAppStateChange); AppState.addEventListener('change', this._handleAppStateChange);
BackHandler.addEventListener('hardwareBackPress', function() { BackHandler.addEventListener('hardwareBackPress', function() {
@ -129,7 +136,7 @@ class AppWithNavigationState extends React.Component {
return true; return true;
} }
if (nav.routes[0].routeName === 'Main') { if (nav.routes[0].routeName === 'Main') {
if (nav.routes[0].routes[0].routes[0].index > 0) { if (nav.routes[0].routes[0].routes[0].index > 0) {
dispatch(NavigationActions.back()); dispatch(NavigationActions.back());
return true; return true;
} }
@ -138,17 +145,17 @@ class AppWithNavigationState extends React.Component {
return false; return false;
}.bind(this)); }.bind(this));
} }
componentDidMount() { componentDidMount() {
Linking.addEventListener('url', this._handleUrl); Linking.addEventListener('url', this._handleUrl);
} }
componentWillUnmount() { componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange); AppState.removeEventListener('change', this._handleAppStateChange);
BackHandler.removeEventListener('hardwareBackPress'); BackHandler.removeEventListener('hardwareBackPress');
Linking.removeEventListener('url', this._handleUrl); Linking.removeEventListener('url', this._handleUrl);
} }
componentWillUpdate(nextProps) { componentWillUpdate(nextProps) {
const { dispatch } = this.props; const { dispatch } = this.props;
const { notification } = nextProps; const { notification } = nextProps;
@ -166,15 +173,15 @@ class AppWithNavigationState extends React.Component {
} else if (AppWithNavigationState.supportedDisplayTypes.indexOf(displayType) > -1) { } else if (AppWithNavigationState.supportedDisplayTypes.indexOf(displayType) > -1) {
currentDisplayType = displayType; currentDisplayType = displayType;
} }
if ('toast' === currentDisplayType) { if ('toast' === currentDisplayType) {
ToastAndroid.show(message, ToastAndroid.SHORT); ToastAndroid.show(message, ToastAndroid.SHORT);
} }
dispatch(doHideNotification()); dispatch(doHideNotification());
} }
} }
_handleAppStateChange = (nextAppState) => { _handleAppStateChange = (nextAppState) => {
// Check if the app was suspended // Check if the app was suspended
if (AppState.currentState && AppState.currentState.match(/inactive|background/)) { if (AppState.currentState && AppState.currentState.match(/inactive|background/)) {
@ -187,7 +194,7 @@ class AppWithNavigationState extends React.Component {
}); });
} }
} }
_handleUrl = (evt) => { _handleUrl = (evt) => {
const { dispatch } = this.props; const { dispatch } = this.props;
if (evt.url) { if (evt.url) {
@ -199,7 +206,7 @@ class AppWithNavigationState extends React.Component {
dispatch(navigateAction); dispatch(navigateAction);
} }
} }
render() { render() {
const { dispatch, nav } = this.props; const { dispatch, nav } = this.props;
return ( return (
@ -220,5 +227,5 @@ const mapStateToProps = state => ({
keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state), keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state) showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state)
}); });
export default connect(mapStateToProps)(AppWithNavigationState); export default connect(mapStateToProps)(AppWithNavigationState);

View file

@ -61,7 +61,7 @@ function enableBatching(reducer) {
} }
const router = AppNavigator.router; const router = AppNavigator.router;
const navAction = router.getActionForPathAndParams('Splash'); const navAction = router.getActionForPathAndParams('FirstRun');
const initialNavState = router.getStateForAction(navAction); const initialNavState = router.getStateForAction(navAction);
const navigatorReducer = (state = initialNavState, action) => { const navigatorReducer = (state = initialNavState, action) => {
const nextState = AppNavigator.router.getStateForAction(action, state); const nextState = AppNavigator.router.getStateForAction(action, state);
@ -116,21 +116,11 @@ 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}>
<AppWithNavigationState /> <AppWithNavigationState />
</Provider> </Provider>
); );
} }
} }

View file

@ -0,0 +1,6 @@
import { connect } from 'react-redux';
import FirstRun from './view';
const perform = dispatch => ({});
export default connect(null, perform)(FirstRun);

View file

@ -0,0 +1,21 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import { View, Text, Linking } from 'react-native';
import Colors from '../../../styles/colors';
import firstRunStyle from '../../../styles/firstRun';
class WelcomePage extends React.PureComponent {
render() {
return (
<View style={firstRunStyle.container}>
<Text style={firstRunStyle.title}>Welcome to LBRY.</Text>
<Text style={firstRunStyle.paragraph}>LBRY is a decentralized peer-to-peer content sharing platform where
you can upload and download videos, music, ebooks and other forms of digital content.</Text>
<Text style={firstRunStyle.paragraph}>We make use of a blockchain which needs to be synchronized before
you can use the app. Synchronization may take a while because this is the first app launch.</Text>
</View>
);
}
}
export default WelcomePage;

View file

@ -0,0 +1,96 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import {
Linking,
NativeModules,
Text,
TouchableOpacity,
View
} from 'react-native';
import { NavigationActions } from 'react-navigation';
import Colors from '../../styles/colors';
import WelcomePage from './internal/welcome-page';
import firstRunStyle from '../../styles/firstRun';
class FirstRunScreen extends React.PureComponent {
static pages = ['welcome'];
constructor() {
super();
this.state = {
currentPage: null,
launchUrl: null,
isFirstRun: false
}
}
componentDidMount() {
Linking.getInitialURL().then((url) => {
if (url) {
this.setState({ launchUrl: url });
}
});
if (NativeModules.FirstRun) {
NativeModules.FirstRun.isFirstRun().then(firstRun => {
this.setState({ isFirstRun: firstRun });
if (firstRun) {
this.setState({ currentPage: FirstRunScreen.pages[0] });
} else {
// Not the first run. Navigate to the splash screen right away
this.launchSplashScreen();
}
});
} else {
// The first run module was not detected. Go straight to the splash screen.
this.launchSplashScreen();
}
}
launchSplashScreen() {
const { navigation } = this.props;
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Splash', params: { launchUri: this.state.launchUri } })
]
});
navigation.dispatch(resetAction);
}
handleContinuePressed = () => {
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
if (pageIndex === (FirstRunScreen.pages.length - 1)) {
// Final page. Let the app know that first run experience is completed.
if (NativeModules.FirstRun) {
NativeModules.FirstRun.firstRunCompleted();
}
// Navigate to the splash screen
this.launchSplashScreen();
} else {
// TODO: Page transition animation?
this.state.currentPage = FirstRunScreen.pages[pageIndex + 1];
}
}
render() {
let page = null;
if (this.state.currentPage === 'welcome') {
// show welcome page
page = (<WelcomePage />);
}
return (
<View style={firstRunStyle.screenContainer}>
{page}
{this.state.currentPage &&
<TouchableOpacity style={firstRunStyle.button} onPress={this.handleContinuePressed}>
<Text style={firstRunStyle.buttonText}>Continue</Text>
</TouchableOpacity>}
</View>
)
}
}
export default FirstRunScreen;

View file

@ -1,15 +1,16 @@
import React from 'react'; import React from 'react';
import { Lbry } from 'lbry-redux'; import { Lbry } from 'lbry-redux';
import { View, Text, Linking, NativeModules } from 'react-native'; import { ActivityIndicator, View, Text, Linking, NativeModules } from 'react-native';
import { NavigationActions } from 'react-navigation'; import { NavigationActions } from 'react-navigation';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Colors from '../../styles/colors';
import splashStyle from '../../styles/splash'; import splashStyle from '../../styles/splash';
class SplashScreen extends React.PureComponent { class SplashScreen extends React.PureComponent {
static navigationOptions = { static navigationOptions = {
title: 'Splash' title: 'Splash'
}; };
componentWillMount() { componentWillMount() {
this.setState({ this.setState({
details: 'Starting daemon', details: 'Starting daemon',
@ -18,6 +19,21 @@ class SplashScreen extends React.PureComponent {
isLagging: false, isLagging: false,
launchUrl: null launchUrl: null
}); });
if (NativeModules.DaemonServiceControl) {
NativeModules.DaemonServiceControl.startService();
}
}
componentDidMount() {
// Start measuring the first launch time from the splash screen (time from daemon start to user interaction)
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()));
}
});
} }
updateStatus() { updateStatus() {
@ -25,7 +41,7 @@ class SplashScreen extends React.PureComponent {
this._updateStatusCallback(status); this._updateStatusCallback(status);
}); });
} }
_updateStatusCallback(status) { _updateStatusCallback(status) {
const startupStatus = status.startup_status; const startupStatus = status.startup_status;
if (startupStatus.code == 'started') { if (startupStatus.code == 'started') {
@ -44,7 +60,7 @@ class SplashScreen extends React.PureComponent {
// Leave the splash screen // Leave the splash screen
const { balanceSubscribe, navigation } = this.props; const { balanceSubscribe, navigation } = this.props;
balanceSubscribe(); balanceSubscribe();
const resetAction = NavigationActions.reset({ const resetAction = NavigationActions.reset({
index: 0, index: 0,
actions: [ actions: [
@ -52,9 +68,10 @@ class SplashScreen extends React.PureComponent {
] ]
}); });
navigation.dispatch(resetAction); navigation.dispatch(resetAction);
if (this.state.launchUrl) { const launchUrl = navigation.state.params.launchUrl || this.state.launchUrl;
navigation.navigate({ routeName: 'File', key: this.state.launchUrl, params: { uri: this.state.launchUrl } }); if (launchUrl) {
navigation.navigate({ routeName: 'File', key: launchUrl, params: { uri: launchUrl } });
} }
}); });
return; return;
@ -78,7 +95,7 @@ class SplashScreen extends React.PureComponent {
this.updateStatus(); this.updateStatus();
}, 500); }, 500);
} }
componentDidMount() { componentDidMount() {
if (NativeModules.Mixpanel) { if (NativeModules.Mixpanel) {
NativeModules.Mixpanel.track('App Launch', null); NativeModules.Mixpanel.track('App Launch', null);
@ -89,7 +106,7 @@ class SplashScreen extends React.PureComponent {
this.setState({ launchUrl: url }); this.setState({ launchUrl: url });
} }
}); });
Lbry Lbry
.connect() .connect()
.then(() => { .then(() => {
@ -107,10 +124,11 @@ class SplashScreen extends React.PureComponent {
render() { render() {
const { message, details, isLagging, isRunning } = this.state; const { message, details, isLagging, isRunning } = this.state;
return ( return (
<View style={splashStyle.container}> <View style={splashStyle.container}>
<Text style={splashStyle.title}>LBRY</Text> <Text style={splashStyle.title}>LBRY</Text>
<ActivityIndicator color={Colors.White} style={splashStyle.loading} size={"small"} />
<Text style={splashStyle.message}>{message}</Text> <Text style={splashStyle.message}>{message}</Text>
<Text style={splashStyle.details}>{details}</Text> <Text style={splashStyle.details}>{details}</Text>
</View> </View>

View file

@ -0,0 +1,44 @@
import { StyleSheet } from 'react-native';
import Colors from './colors';
const firstRunStyle = StyleSheet.create({
screenContainer: {
flex: 1,
backgroundColor: Colors.LbryGreen
},
container: {
flex: 9,
justifyContent: 'center',
backgroundColor: Colors.LbryGreen
},
title: {
fontFamily: 'Metropolis-SemiBold',
fontSize: 40,
marginLeft: 32,
marginRight: 32,
marginBottom: 32,
color: Colors.White
},
paragraph: {
fontFamily: 'Metropolis-Regular',
fontSize: 18,
lineHeight: 24,
marginLeft: 32,
marginRight: 32,
marginBottom: 20,
color: Colors.White
},
button: {
flex: 1,
alignSelf: 'flex-end',
marginLeft: 32,
marginRight: 32
},
buttonText: {
fontFamily: 'Metropolis-Regular',
fontSize: 28,
color: Colors.White
}
});
export default firstRunStyle;

View file

@ -1,30 +1,34 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import Colors from './colors';
const splashStyle = StyleSheet.create({ const splashStyle = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
backgroundColor: '#40b89a' backgroundColor: Colors.LbryGreen
}, },
title: { title: {
fontFamily: 'Metropolis-Bold', fontFamily: 'Metropolis-Bold',
fontSize: 64, fontSize: 64,
textAlign: 'center', textAlign: 'center',
marginBottom: 48, marginBottom: 48,
color: '#ffffff' color: Colors.White
},
loading: {
marginBottom: 36
}, },
details: { details: {
fontFamily: 'Metropolis-Regular', fontFamily: 'Metropolis-Regular',
fontSize: 14, fontSize: 14,
marginLeft: 16, marginLeft: 16,
marginRight: 16, marginRight: 16,
color: '#ffffff', color: Colors.White,
textAlign: 'center' textAlign: 'center'
}, },
message: { message: {
fontFamily: 'Metropolis-Bold', fontFamily: 'Metropolis-Bold',
fontSize: 18, fontSize: 18,
color: '#ffffff', color: Colors.White,
marginLeft: 16, marginLeft: 16,
marginRight: 16, marginRight: 16,
marginBottom: 4, marginBottom: 4,

View file

@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py
# (list) Application requirements # (list) Application requirements
# comma seperated e.g. requirements = sqlite3,kivy # comma seperated e.g. requirements = sqlite3,kivy
requirements = openssl, sqlite3, hostpython2, android, pyjnius, certifi==2018.4.16, constantly, incremental, functools32, miniupnpc==1.9, gmpy==1.17, twisted==16.6.0, appdirs==1.4.3, argparse==1.2.1, docopt==0.6.2, base58==0.2.2, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse==0.2.0, jsonrpc==1.2, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2==1.3, pyyaml==3.12, qrcode==5.2.2, requests==2.9.1, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, slowaes==0.1a1, txJSON-RPC==0.5, wsgiref==0.1.2, zope.interface==4.3.3, protobuf==3.2.0, keyring==10.4.0, git+https://github.com/lbryio/lbryschema.git@v0.0.15#egg=lbryschema, git+https://github.com/lbryio/lbryum.git#egg=lbryum, git+https://github.com/lbryio/lbry.git#egg=lbrynet, asn1crypto, cryptography==2.2.2, pyopenssl==17.4.0, treq==17.8.0, funcsigs, mock, pbr, unqlite requirements = openssl, sqlite3, hostpython2, android, distro, pyjnius, certifi==2018.4.16, constantly, incremental, functools32, miniupnpc==1.9, gmpy==1.17, twisted==16.6.0, appdirs==1.4.3, argparse==1.2.1, docopt==0.6.2, base58==0.2.2, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse==0.2.0, jsonrpc==1.2, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2==1.3, pyyaml==3.12, qrcode==5.2.2, requests==2.9.1, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, slowaes==0.1a1, txJSON-RPC==0.5, wsgiref==0.1.2, zope.interface==4.3.3, protobuf==3.2.0, keyring==10.4.0, git+https://github.com/lbryio/lbryschema.git@v0.0.15#egg=lbryschema, git+https://github.com/lbryio/lbryum.git#egg=lbryum, git+https://github.com/lbryio/lbry.git#egg=lbrynet, asn1crypto, cryptography==2.2.2, pyopenssl==17.4.0, treq==17.8.0, funcsigs, mock, pbr, unqlite
# (str) Custom source folders for requirements # (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes # Sets custom source for any requirements with recipes

View file

@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py
# (list) Application requirements # (list) Application requirements
# comma seperated e.g. requirements = sqlite3,kivy # comma seperated e.g. requirements = sqlite3,kivy
requirements = openssl, sqlite3, hostpython2, android, pyjnius, certifi==2018.4.16, constantly, incremental, functools32, miniupnpc==1.9, gmpy==1.17, twisted==16.6.0, appdirs==1.4.3, argparse==1.2.1, docopt==0.6.2, base58==0.2.2, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse==0.2.0, jsonrpc==1.2, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2==1.3, pyyaml==3.12, qrcode==5.2.2, requests==2.9.1, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, slowaes==0.1a1, txJSON-RPC==0.5, wsgiref==0.1.2, zope.interface==4.3.3, protobuf==3.2.0, keyring==10.4.0, git+https://github.com/lbryio/lbryschema.git@v0.0.15#egg=lbryschema, git+https://github.com/lbryio/lbryum.git#egg=lbryum, git+https://github.com/lbryio/lbry.git#egg=lbrynet, asn1crypto, cryptography==2.2.2, pyopenssl==17.4.0, treq==17.8.0, funcsigs, mock, pbr, unqlite requirements = openssl, sqlite3, hostpython2, android, distro, pyjnius, certifi==2018.4.16, constantly, incremental, functools32, miniupnpc==1.9, gmpy==1.17, twisted==16.6.0, appdirs==1.4.3, argparse==1.2.1, docopt==0.6.2, base58==0.2.2, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse==0.2.0, jsonrpc==1.2, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2==1.3, pyyaml==3.12, qrcode==5.2.2, requests==2.9.1, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, slowaes==0.1a1, txJSON-RPC==0.5, wsgiref==0.1.2, zope.interface==4.3.3, protobuf==3.2.0, keyring==10.4.0, git+https://github.com/lbryio/lbryschema.git@v0.0.15#egg=lbryschema, git+https://github.com/lbryio/lbryum.git#egg=lbryum, git+https://github.com/lbryio/lbry.git#egg=lbrynet, asn1crypto, cryptography==2.2.2, pyopenssl==17.4.0, treq==17.8.0, funcsigs, mock, pbr, unqlite
# (str) Custom source folders for requirements # (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes # Sets custom source for any requirements with recipes

View file

@ -371,7 +371,7 @@ main.py that loads it.''')
args=args, args=args,
url_scheme=url_scheme, url_scheme=url_scheme,
private_version=str(time.time())) private_version=str(time.time()))
# add colors.xml # add colors.xml
render( render(
'colors.tmpl.xml', 'colors.tmpl.xml',
@ -380,6 +380,14 @@ main.py that loads it.''')
url_scheme=url_scheme, url_scheme=url_scheme,
) )
# add themes.xml
render(
'themes.tmpl.xml',
'src/main/res/values/themes.xml',
args=args,
url_scheme=url_scheme,
)
# add activity_service_control # add activity_service_control
render( render(
'activity_service_control.xml', 'activity_service_control.xml',
@ -396,12 +404,12 @@ main.py that loads it.''')
aars=aars, aars=aars,
android_api=android_api, android_api=android_api,
build_tools_version=build_tools_version) build_tools_version=build_tools_version)
render( render(
'settings.tmpl.gradle', 'settings.tmpl.gradle',
'settings.gradle' 'settings.gradle'
) )
# copy icon drawables # copy icon drawables
for folder in ('drawable-hdpi', 'drawable-mdpi', 'drawable-xhdpi', 'drawable-xxhdpi', 'drawable-xxxhdpi'): for folder in ('drawable-hdpi', 'drawable-mdpi', 'drawable-xhdpi', 'drawable-xxhdpi', 'drawable-xxxhdpi'):
shutil.copy( shutil.copy(

View file

@ -52,7 +52,7 @@
<application android:label="@string/app_name" <application android:label="@string/app_name"
android:icon="@drawable/icon" android:icon="@drawable/icon"
android:allowBackup="true" android:allowBackup="true"
android:theme="@android:style/Theme.Material.Light" android:theme="@style/LbryAppTheme"
android:hardwareAccelerated="true"> android:hardwareAccelerated="true">
{% for m in args.meta_data %} {% for m in args.meta_data %}
@ -64,7 +64,7 @@
android:configChanges="keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}" android:configChanges="keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}"
android:screenOrientation="{{ args.orientation }}" android:screenOrientation="{{ args.orientation }}"
--> -->
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<activity android:name="io.lbry.browser.MainActivity" <activity android:name="io.lbry.browser.MainActivity"
@ -74,7 +74,7 @@
android:screenOrientation="{{ args.orientation }}" android:screenOrientation="{{ args.orientation }}"
android:launchMode="singleInstance" android:launchMode="singleInstance"
> >
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@ -99,9 +99,9 @@
{{- args.intent_filters -}} {{- args.intent_filters -}}
{%- endif -%} {%- endif -%}
</activity> </activity>
<receiver android:name="io.lbry.browser.receivers.NotificationDeletedReceiver" /> <receiver android:name="io.lbry.browser.receivers.NotificationDeletedReceiver" />
{% if args.launcher %} {% if args.launcher %}
<activity android:name="org.kivy.android.launcher.ProjectChooser" <activity android:name="org.kivy.android.launcher.ProjectChooser"
android:icon="@drawable/icon" android:icon="@drawable/icon"

View file

@ -6,4 +6,5 @@
<color name="red">#FF0000</color> <color name="red">#FF0000</color>
<color name="green">#00C000</color> <color name="green">#00C000</color>
<color name="lbrygreen">#40B89A</color>
</resources> </resources>

View file

@ -0,0 +1,5 @@
<resources>
<style name="LbryAppTheme" parent="@android:style/Theme.Material.Light">
<item name="android:windowBackground">@color/lbrygreen</item>
</style>
</resources>

View file

@ -27,27 +27,27 @@ import io.lbry.browser.reactpackages.LbryReactPackage;
import io.lbry.browser.reactmodules.DownloadManagerModule; import io.lbry.browser.reactmodules.DownloadManagerModule;
public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler {
private static final int OVERLAY_PERMISSION_REQ_CODE = 101; private static final int OVERLAY_PERMISSION_REQ_CODE = 101;
private static final int STORAGE_PERMISSION_REQ_CODE = 201; private static final int STORAGE_PERMISSION_REQ_CODE = 201;
private ReactRootView mReactRootView; private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager; private ReactInstanceManager mReactInstanceManager;
public static final String SHARED_PREFERENCES_NAME = "LBRY"; public static final String SHARED_PREFERENCES_NAME = "LBRY";
/** /**
* Flag which indicates whether or not the service is running. Will be updated in the * Flag which indicates whether or not the service is running. Will be updated in the
* onResume method. * onResume method.
*/ */
private boolean serviceRunning; private boolean serviceRunning;
protected String getMainComponentName() { protected String getMainComponentName() {
return "LBRYApp"; return "LBRYApp";
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -63,15 +63,15 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
} }
} }
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Start the daemon service if it is not started // Start the daemon service if it is not started
serviceRunning = isServiceRunning(LbrynetService.class); serviceRunning = isServiceRunning(LbrynetService.class);
if (!serviceRunning) { if (!serviceRunning) {
ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice"); ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice");
} }
mReactRootView = new ReactRootView(this); mReactRootView = new ReactRootView(this);
mReactInstanceManager = ReactInstanceManager.builder() mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication()) .setApplication(getApplication())
@ -87,7 +87,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
setContentView(mReactRootView); setContentView(mReactRootView);
} }
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) { if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
@ -98,7 +98,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
} }
} }
} }
@Override @Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) { switch (requestCode) {
@ -127,30 +127,35 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
public void invokeDefaultOnBackPressed() { public void invokeDefaultOnBackPressed() {
super.onBackPressed(); super.onBackPressed();
} }
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
if (mReactInstanceManager != null) { if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause(this); mReactInstanceManager.onHostPause(this);
} }
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
serviceRunning = isServiceRunning(LbrynetService.class); SharedPreferences sp = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
if (!serviceRunning) { if (!sp.getBoolean("firstRun", true)) {
ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice"); // We're not showing the welcome page, so it's okay to start the daemon service
// because this is not the first run experience
serviceRunning = isServiceRunning(LbrynetService.class);
if (!serviceRunning) {
ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice");
}
} }
if (mReactInstanceManager != null) { if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this); mReactInstanceManager.onHostResume(this, this);
} }
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
// check service running setting and end it here // check service running setting and end it here
@ -164,7 +169,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
} }
super.onDestroy(); super.onDestroy();
if (mReactInstanceManager != null) { if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy(this); mReactInstanceManager.onHostDestroy(this);
} }
@ -178,7 +183,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
super.onBackPressed(); super.onBackPressed();
} }
} }
@Override @Override
public void onNewIntent(Intent intent) { public void onNewIntent(Intent intent) {
if (mReactInstanceManager != null) { if (mReactInstanceManager != null) {
@ -186,7 +191,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
} }
super.onNewIntent(intent); super.onNewIntent(intent);
} }
private boolean isServiceRunning(Class<?> serviceClass) { private boolean isServiceRunning(Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) { for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) {

View file

@ -24,6 +24,11 @@ public class DaemonServiceControlModule extends ReactContextBaseJavaModule {
return "DaemonServiceControl"; return "DaemonServiceControl";
} }
@ReactMethod
public void startService() {
ServiceHelper.start(context, "", LbrynetService.class, "lbrynetservice");
}
@ReactMethod @ReactMethod
public void stopService() { public void stopService() {
ServiceHelper.stop(context, LbrynetService.class); ServiceHelper.stop(context, LbrynetService.class);

View file

@ -0,0 +1,44 @@
package io.lbry.browser.reactmodules;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.SharedPreferences;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import io.lbry.browser.MainActivity;
public class FirstRunModule extends ReactContextBaseJavaModule {
private Context context;
private SharedPreferences sp;
public FirstRunModule(ReactApplicationContext reactContext) {
super(reactContext);
this.context = reactContext;
this.sp = reactContext.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
}
@Override
public String getName() {
return "FirstRun";
}
@ReactMethod
public void isFirstRun(final Promise promise) {
// If firstRun flag does not exist, default to true
boolean firstRun = sp.getBoolean("firstRun", true);
promise.resolve(firstRun);
}
@ReactMethod
public void firstRunCompleted() {
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("firstRun", false);
editor.commit();
}
}

View file

@ -1,15 +1,14 @@
package io.lbry.browser.reactmodules; package io.lbry.browser.reactmodules;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
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 android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
public class VersionInfoModule extends ReactContextBaseJavaModule { public class VersionInfoModule extends ReactContextBaseJavaModule {
private Context context; private Context context;

View file

@ -7,6 +7,7 @@ import com.facebook.react.uimanager.ViewManager;
import io.lbry.browser.reactmodules.DaemonServiceControlModule; import io.lbry.browser.reactmodules.DaemonServiceControlModule;
import io.lbry.browser.reactmodules.DownloadManagerModule; import io.lbry.browser.reactmodules.DownloadManagerModule;
import io.lbry.browser.reactmodules.FirstRunModule;
import io.lbry.browser.reactmodules.MixpanelModule; import io.lbry.browser.reactmodules.MixpanelModule;
import io.lbry.browser.reactmodules.ScreenOrientationModule; import io.lbry.browser.reactmodules.ScreenOrientationModule;
import io.lbry.browser.reactmodules.VersionInfoModule; import io.lbry.browser.reactmodules.VersionInfoModule;
@ -27,10 +28,11 @@ public class LbryReactPackage implements ReactPackage {
modules.add(new DaemonServiceControlModule(reactContext)); modules.add(new DaemonServiceControlModule(reactContext));
modules.add(new DownloadManagerModule(reactContext)); modules.add(new DownloadManagerModule(reactContext));
modules.add(new FirstRunModule(reactContext));
modules.add(new MixpanelModule(reactContext)); modules.add(new MixpanelModule(reactContext));
modules.add(new ScreenOrientationModule(reactContext)); modules.add(new ScreenOrientationModule(reactContext));
modules.add(new VersionInfoModule(reactContext)); modules.add(new VersionInfoModule(reactContext));
return modules; return modules;
} }
} }