From 468c41c526cb937b35c44b3e28fec1da7b8fb150 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Fri, 8 Jun 2018 09:13:45 +0100 Subject: [PATCH] implement extendable first run experience starting with welcome page --- app/src/component/AppNavigator.js | 29 +++--- app/src/index.js | 16 +--- app/src/page/firstRun/index.js | 6 ++ .../page/firstRun/internal/welcome-page.js | 21 ++++ app/src/page/firstRun/view.js | 96 +++++++++++++++++++ app/src/page/splash/view.js | 38 ++++++-- app/src/styles/firstRun.js | 44 +++++++++ app/src/styles/splash.js | 12 ++- buildozer.spec.sample | 2 +- buildozer.spec.travis | 2 +- .../bootstraps/lbry/build/build.py | 14 ++- .../build/templates/AndroidManifest.tmpl.xml | 10 +- .../lbry/build/templates/colors.tmpl.xml | 1 + .../lbry/build/templates/themes.tmpl.xml | 5 + .../java/io/lbry/browser/MainActivity.java | 57 ++++++----- .../DaemonServiceControlModule.java | 5 + .../browser/reactmodules/FirstRunModule.java | 44 +++++++++ .../reactmodules/VersionInfoModule.java | 5 +- .../reactpackages/LbryReactPackage.java | 4 +- 19 files changed, 333 insertions(+), 78 deletions(-) create mode 100644 app/src/page/firstRun/index.js create mode 100644 app/src/page/firstRun/internal/welcome-page.js create mode 100644 app/src/page/firstRun/view.js create mode 100644 app/src/styles/firstRun.js create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/themes.tmpl.xml create mode 100644 src/main/java/io/lbry/browser/reactmodules/FirstRunModule.java diff --git a/app/src/component/AppNavigator.js b/app/src/component/AppNavigator.js index 218fd89..91a4f0a 100644 --- a/app/src/component/AppNavigator.js +++ b/app/src/component/AppNavigator.js @@ -2,6 +2,7 @@ import React from 'react'; import AboutPage from '../page/about'; import DiscoverPage from '../page/discover'; import FilePage from '../page/file'; +import FirstRunScreen from '../page/firstRun'; import SearchPage from '../page/search'; import TrendingPage from '../page/trending'; import SettingsPage from '../page/settings'; @@ -99,6 +100,12 @@ const drawer = DrawerNavigator({ }); export const AppNavigator = new StackNavigator({ + FirstRun: { + screen: FirstRunScreen, + navigationOptions: { + drawerLockMode: 'locked-closed' + } + }, Splash: { screen: SplashScreen, navigationOptions: { @@ -114,7 +121,7 @@ export const AppNavigator = new StackNavigator({ class AppWithNavigationState extends React.Component { static supportedDisplayTypes = ['toast']; - + componentWillMount() { AppState.addEventListener('change', this._handleAppStateChange); BackHandler.addEventListener('hardwareBackPress', function() { @@ -129,7 +136,7 @@ class AppWithNavigationState extends React.Component { return true; } 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()); return true; } @@ -138,17 +145,17 @@ class AppWithNavigationState extends React.Component { return false; }.bind(this)); } - + componentDidMount() { Linking.addEventListener('url', this._handleUrl); } - + componentWillUnmount() { AppState.removeEventListener('change', this._handleAppStateChange); BackHandler.removeEventListener('hardwareBackPress'); Linking.removeEventListener('url', this._handleUrl); } - + componentWillUpdate(nextProps) { const { dispatch } = this.props; const { notification } = nextProps; @@ -166,15 +173,15 @@ class AppWithNavigationState extends React.Component { } else if (AppWithNavigationState.supportedDisplayTypes.indexOf(displayType) > -1) { currentDisplayType = displayType; } - + if ('toast' === currentDisplayType) { ToastAndroid.show(message, ToastAndroid.SHORT); } - + dispatch(doHideNotification()); } } - + _handleAppStateChange = (nextAppState) => { // Check if the app was suspended if (AppState.currentState && AppState.currentState.match(/inactive|background/)) { @@ -187,7 +194,7 @@ class AppWithNavigationState extends React.Component { }); } } - + _handleUrl = (evt) => { const { dispatch } = this.props; if (evt.url) { @@ -199,7 +206,7 @@ class AppWithNavigationState extends React.Component { dispatch(navigateAction); } } - + render() { const { dispatch, nav } = this.props; return ( @@ -220,5 +227,5 @@ const mapStateToProps = state => ({ keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state), showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state) }); - + export default connect(mapStateToProps)(AppWithNavigationState); diff --git a/app/src/index.js b/app/src/index.js index a371b91..8e3f74e 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -61,7 +61,7 @@ function enableBatching(reducer) { } const router = AppNavigator.router; -const navAction = router.getActionForPathAndParams('Splash'); +const navAction = router.getActionForPathAndParams('FirstRun'); const initialNavState = router.getStateForAction(navAction); const navigatorReducer = (state = initialNavState, action) => { const nextState = AppNavigator.router.getStateForAction(action, state); @@ -116,21 +116,11 @@ 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/firstRun/index.js b/app/src/page/firstRun/index.js new file mode 100644 index 0000000..5e9ec0c --- /dev/null +++ b/app/src/page/firstRun/index.js @@ -0,0 +1,6 @@ +import { connect } from 'react-redux'; +import FirstRun from './view'; + +const perform = dispatch => ({}); + +export default connect(null, perform)(FirstRun); \ No newline at end of file diff --git a/app/src/page/firstRun/internal/welcome-page.js b/app/src/page/firstRun/internal/welcome-page.js new file mode 100644 index 0000000..abc0eae --- /dev/null +++ b/app/src/page/firstRun/internal/welcome-page.js @@ -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 ( + + Welcome to LBRY. + 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. + 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. + + ); + } +} + +export default WelcomePage; diff --git a/app/src/page/firstRun/view.js b/app/src/page/firstRun/view.js new file mode 100644 index 0000000..2e86837 --- /dev/null +++ b/app/src/page/firstRun/view.js @@ -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 = (); + } + + return ( + + {page} + {this.state.currentPage && + + Continue + } + + ) + } +} + +export default FirstRunScreen; diff --git a/app/src/page/splash/view.js b/app/src/page/splash/view.js index b1318ca..931446e 100644 --- a/app/src/page/splash/view.js +++ b/app/src/page/splash/view.js @@ -1,15 +1,16 @@ import React from 'react'; 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 PropTypes from 'prop-types'; +import Colors from '../../styles/colors'; import splashStyle from '../../styles/splash'; class SplashScreen extends React.PureComponent { static navigationOptions = { title: 'Splash' }; - + componentWillMount() { this.setState({ details: 'Starting daemon', @@ -18,6 +19,21 @@ class SplashScreen extends React.PureComponent { isLagging: false, 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() { @@ -25,7 +41,7 @@ class SplashScreen extends React.PureComponent { this._updateStatusCallback(status); }); } - + _updateStatusCallback(status) { const startupStatus = status.startup_status; if (startupStatus.code == 'started') { @@ -44,7 +60,7 @@ class SplashScreen extends React.PureComponent { // Leave the splash screen const { balanceSubscribe, navigation } = this.props; balanceSubscribe(); - + const resetAction = NavigationActions.reset({ index: 0, actions: [ @@ -52,9 +68,10 @@ class SplashScreen extends React.PureComponent { ] }); navigation.dispatch(resetAction); - - if (this.state.launchUrl) { - navigation.navigate({ routeName: 'File', key: this.state.launchUrl, params: { uri: this.state.launchUrl } }); + + const launchUrl = navigation.state.params.launchUrl || this.state.launchUrl; + if (launchUrl) { + navigation.navigate({ routeName: 'File', key: launchUrl, params: { uri: launchUrl } }); } }); return; @@ -78,7 +95,7 @@ class SplashScreen extends React.PureComponent { this.updateStatus(); }, 500); } - + componentDidMount() { if (NativeModules.Mixpanel) { NativeModules.Mixpanel.track('App Launch', null); @@ -89,7 +106,7 @@ class SplashScreen extends React.PureComponent { this.setState({ launchUrl: url }); } }); - + Lbry .connect() .then(() => { @@ -107,10 +124,11 @@ class SplashScreen extends React.PureComponent { render() { const { message, details, isLagging, isRunning } = this.state; - + return ( LBRY + {message} {details} diff --git a/app/src/styles/firstRun.js b/app/src/styles/firstRun.js new file mode 100644 index 0000000..c5d3f4c --- /dev/null +++ b/app/src/styles/firstRun.js @@ -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; diff --git a/app/src/styles/splash.js b/app/src/styles/splash.js index dec85d7..f1b442e 100644 --- a/app/src/styles/splash.js +++ b/app/src/styles/splash.js @@ -1,30 +1,34 @@ import { StyleSheet } from 'react-native'; +import Colors from './colors'; const splashStyle = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', - backgroundColor: '#40b89a' + backgroundColor: Colors.LbryGreen }, title: { fontFamily: 'Metropolis-Bold', fontSize: 64, textAlign: 'center', marginBottom: 48, - color: '#ffffff' + color: Colors.White + }, + loading: { + marginBottom: 36 }, details: { fontFamily: 'Metropolis-Regular', fontSize: 14, marginLeft: 16, marginRight: 16, - color: '#ffffff', + color: Colors.White, textAlign: 'center' }, message: { fontFamily: 'Metropolis-Bold', fontSize: 18, - color: '#ffffff', + color: Colors.White, marginLeft: 16, marginRight: 16, marginBottom: 4, diff --git a/buildozer.spec.sample b/buildozer.spec.sample index 64b879d..4707c06 100644 --- a/buildozer.spec.sample +++ b/buildozer.spec.sample @@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py # (list) Application requirements # 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 # Sets custom source for any requirements with recipes diff --git a/buildozer.spec.travis b/buildozer.spec.travis index 64b879d..4707c06 100644 --- a/buildozer.spec.travis +++ b/buildozer.spec.travis @@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py # (list) Application requirements # 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 # Sets custom source for any requirements with recipes diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/build.py b/p4a/pythonforandroid/bootstraps/lbry/build/build.py index 67bf345..6c64175 100755 --- a/p4a/pythonforandroid/bootstraps/lbry/build/build.py +++ b/p4a/pythonforandroid/bootstraps/lbry/build/build.py @@ -371,7 +371,7 @@ main.py that loads it.''') args=args, url_scheme=url_scheme, private_version=str(time.time())) - + # add colors.xml render( 'colors.tmpl.xml', @@ -380,6 +380,14 @@ main.py that loads it.''') 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 render( 'activity_service_control.xml', @@ -396,12 +404,12 @@ main.py that loads it.''') aars=aars, android_api=android_api, build_tools_version=build_tools_version) - + render( 'settings.tmpl.gradle', 'settings.gradle' ) - + # copy icon drawables for folder in ('drawable-hdpi', 'drawable-mdpi', 'drawable-xhdpi', 'drawable-xxhdpi', 'drawable-xxxhdpi'): shutil.copy( diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml b/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml index 0b93dc0..198f4a4 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml @@ -52,7 +52,7 @@ {% for m in args.meta_data %} @@ -64,7 +64,7 @@ android:configChanges="keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}" android:screenOrientation="{{ args.orientation }}" --> - + - + @@ -99,9 +99,9 @@ {{- args.intent_filters -}} {%- endif -%} - + - + {% if args.launcher %} #FF0000 #00C000 + #40B89A \ No newline at end of file diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/themes.tmpl.xml b/p4a/pythonforandroid/bootstraps/lbry/build/templates/themes.tmpl.xml new file mode 100644 index 0000000..0b50d99 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/themes.tmpl.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/main/java/io/lbry/browser/MainActivity.java b/src/main/java/io/lbry/browser/MainActivity.java index 853056a..11b5f84 100644 --- a/src/main/java/io/lbry/browser/MainActivity.java +++ b/src/main/java/io/lbry/browser/MainActivity.java @@ -27,27 +27,27 @@ import io.lbry.browser.reactpackages.LbryReactPackage; import io.lbry.browser.reactmodules.DownloadManagerModule; public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { - + 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 ReactInstanceManager mReactInstanceManager; - + public static final String SHARED_PREFERENCES_NAME = "LBRY"; - + /** * Flag which indicates whether or not the service is running. Will be updated in the * onResume method. */ private boolean serviceRunning; - + protected String getMainComponentName() { return "LBRYApp"; } - + @Override protected void onCreate(Bundle savedInstanceState) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -63,15 +63,15 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand } } } - + super.onCreate(savedInstanceState); - + // Start the daemon service if it is not started serviceRunning = isServiceRunning(LbrynetService.class); if (!serviceRunning) { ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice"); } - + mReactRootView = new ReactRootView(this); mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) @@ -87,7 +87,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand setContentView(mReactRootView); } - + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == OVERLAY_PERMISSION_REQ_CODE) { @@ -98,7 +98,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand } } } - + @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { @@ -127,30 +127,35 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand public void invokeDefaultOnBackPressed() { super.onBackPressed(); } - + @Override protected void onPause() { super.onPause(); - + if (mReactInstanceManager != null) { mReactInstanceManager.onHostPause(this); } } - + @Override protected void onResume() { super.onResume(); - - serviceRunning = isServiceRunning(LbrynetService.class); - if (!serviceRunning) { - ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice"); + + SharedPreferences sp = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); + if (!sp.getBoolean("firstRun", true)) { + // 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) { mReactInstanceManager.onHostResume(this, this); } } - + @Override protected void onDestroy() { // check service running setting and end it here @@ -164,7 +169,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand } super.onDestroy(); - + if (mReactInstanceManager != null) { mReactInstanceManager.onHostDestroy(this); } @@ -178,7 +183,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand super.onBackPressed(); } } - + @Override public void onNewIntent(Intent intent) { if (mReactInstanceManager != null) { @@ -186,7 +191,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand } super.onNewIntent(intent); } - + private boolean isServiceRunning(Class serviceClass) { ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) { diff --git a/src/main/java/io/lbry/browser/reactmodules/DaemonServiceControlModule.java b/src/main/java/io/lbry/browser/reactmodules/DaemonServiceControlModule.java index a8d859c..eddff23 100644 --- a/src/main/java/io/lbry/browser/reactmodules/DaemonServiceControlModule.java +++ b/src/main/java/io/lbry/browser/reactmodules/DaemonServiceControlModule.java @@ -24,6 +24,11 @@ public class DaemonServiceControlModule extends ReactContextBaseJavaModule { return "DaemonServiceControl"; } + @ReactMethod + public void startService() { + ServiceHelper.start(context, "", LbrynetService.class, "lbrynetservice"); + } + @ReactMethod public void stopService() { ServiceHelper.stop(context, LbrynetService.class); diff --git a/src/main/java/io/lbry/browser/reactmodules/FirstRunModule.java b/src/main/java/io/lbry/browser/reactmodules/FirstRunModule.java new file mode 100644 index 0000000..6e8b2b8 --- /dev/null +++ b/src/main/java/io/lbry/browser/reactmodules/FirstRunModule.java @@ -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(); + } +} diff --git a/src/main/java/io/lbry/browser/reactmodules/VersionInfoModule.java b/src/main/java/io/lbry/browser/reactmodules/VersionInfoModule.java index ed52dad..387f1d9 100644 --- a/src/main/java/io/lbry/browser/reactmodules/VersionInfoModule.java +++ b/src/main/java/io/lbry/browser/reactmodules/VersionInfoModule.java @@ -1,15 +1,14 @@ package io.lbry.browser.reactmodules; 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.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; - public class VersionInfoModule extends ReactContextBaseJavaModule { private Context context; diff --git a/src/main/java/io/lbry/browser/reactpackages/LbryReactPackage.java b/src/main/java/io/lbry/browser/reactpackages/LbryReactPackage.java index b2bd006..6fa4c9f 100644 --- a/src/main/java/io/lbry/browser/reactpackages/LbryReactPackage.java +++ b/src/main/java/io/lbry/browser/reactpackages/LbryReactPackage.java @@ -7,6 +7,7 @@ import com.facebook.react.uimanager.ViewManager; import io.lbry.browser.reactmodules.DaemonServiceControlModule; import io.lbry.browser.reactmodules.DownloadManagerModule; +import io.lbry.browser.reactmodules.FirstRunModule; import io.lbry.browser.reactmodules.MixpanelModule; import io.lbry.browser.reactmodules.ScreenOrientationModule; import io.lbry.browser.reactmodules.VersionInfoModule; @@ -27,10 +28,11 @@ public class LbryReactPackage implements ReactPackage { modules.add(new DaemonServiceControlModule(reactContext)); modules.add(new DownloadManagerModule(reactContext)); + modules.add(new FirstRunModule(reactContext)); modules.add(new MixpanelModule(reactContext)); modules.add(new ScreenOrientationModule(reactContext)); modules.add(new VersionInfoModule(reactContext)); - + return modules; } } \ No newline at end of file