diff --git a/app/package-lock.json b/app/package-lock.json index 61801120..5b17c504 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -3965,6 +3965,23 @@ "reselect": "^3.0.0" } }, + "lbryinc": { + "version": "github:lbryio/lbryinc#8e33473daa56ebe80b12509ac5374c5884efef90", + "from": "github:lbryio/lbryinc#authentication-flow", + "requires": { + "reselect": "^3.0.0" + }, + "dependencies": { + "lbry-redux": { + "version": "github:lbryio/lbry-redux#467e48c77b8004cef738e950bdcc67654748ae9f", + "from": "github:lbryio/lbry-redux#467e48c77b8004cef738e950bdcc67654748ae9f", + "requires": { + "proxy-polyfill": "0.1.6", + "reselect": "^3.0.0" + } + } + } + }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", diff --git a/app/package.json b/app/package.json index 16f2b6b2..45169703 100644 --- a/app/package.json +++ b/app/package.json @@ -6,7 +6,9 @@ "start": "node node_modules/react-native/local-cli/cli.js start" }, "dependencies": { + "base-64": "^0.1.0", "lbry-redux": "lbryio/lbry-redux", + "lbryinc": "lbryio/lbryinc#authentication-flow", "moment": "^2.22.1", "react": "16.2.0", "react-native": "0.55.3", diff --git a/app/src/component/AppNavigator.js b/app/src/component/AppNavigator.js index 735cbe3c..479f90db 100644 --- a/app/src/component/AppNavigator.js +++ b/app/src/component/AppNavigator.js @@ -27,12 +27,22 @@ import { TextInput, ToastAndroid } from 'react-native'; -import { SETTINGS, doHideNotification, selectNotification } from 'lbry-redux'; +import { SETTINGS, doHideNotification, doNotify, selectNotification } from 'lbry-redux'; +import { + doUserEmailVerify, + doUserEmailVerifyFailure, + selectEmailToVerify, + selectEmailVerifyIsPending, + selectEmailVerifyErrorMessage, + selectUser +} from 'lbryinc'; import { makeSelectClientSetting } from '../redux/selectors/settings'; +import { decode as atob } from 'base-64'; import Icon from 'react-native-vector-icons/FontAwesome5'; +import Constants from '../constants'; import discoverStyle from '../styles/discover'; import searchStyle from '../styles/search'; -import SearchRightHeaderIcon from "../component/searchRightHeaderIcon"; +import SearchRightHeaderIcon from '../component/searchRightHeaderIcon'; const discoverStack = StackNavigator({ Discover: { @@ -122,6 +132,13 @@ export const AppNavigator = new StackNavigator({ class AppWithNavigationState extends React.Component { static supportedDisplayTypes = ['toast']; + constructor() { + super(); + this.state = { + emailVerifyDone: false + }; + } + componentWillMount() { AppState.addEventListener('change', this._handleAppStateChange); BackHandler.addEventListener('hardwareBackPress', function() { @@ -144,7 +161,7 @@ class AppWithNavigationState extends React.Component { } } } - return false;fo + return false; }.bind(this)); } @@ -160,7 +177,14 @@ class AppWithNavigationState extends React.Component { componentWillUpdate(nextProps) { const { dispatch } = this.props; - const { notification } = nextProps; + const { + notification, + emailToVerify, + emailVerifyPending, + emailVerifyErrorMessage, + user + } = nextProps; + if (notification) { const { displayType, message } = notification; let currentDisplayType; @@ -182,6 +206,20 @@ class AppWithNavigationState extends React.Component { dispatch(doHideNotification()); } + + if (user && + !emailVerifyPending && + !this.state.emailVerifyDone && + (emailToVerify || emailVerifyErrorMessage)) { + AsyncStorage.getItem(Constants.KEY_SHOULD_VERIFY_EMAIL).then(shouldVerify => { + if ('true' === shouldVerify) { + this.setState({ emailVerifyDone: true }); + const message = emailVerifyErrorMessage ? + String(emailVerifyErrorMessage) : 'Your email address was successfully verified.'; + dispatch(doNotify({ message, displayType: ['toast'] })); + } + }); + } } _handleAppStateChange = (nextAppState) => { @@ -200,12 +238,38 @@ class AppWithNavigationState extends React.Component { _handleUrl = (evt) => { const { dispatch } = this.props; if (evt.url) { - const navigateAction = NavigationActions.navigate({ - routeName: 'File', - key: evt.url, - params: { uri: evt.url } - }); - dispatch(navigateAction); + if (evt.url.startsWith('lbry://?verify=')) { + this.setState({ emailVerifyDone: false }); + let verification = {}; + try { + verification = JSON.parse(atob(evt.url.substring(15))); + } catch (error) { + console.log(error); + } + + if (verification.token && verification.recaptcha) { + AsyncStorage.setItem(Constants.KEY_SHOULD_VERIFY_EMAIL, 'true'); + try { + dispatch(doUserEmailVerify(verification.token, verification.recaptcha)); + } catch (error) { + const message = 'Invalid Verification Token'; + dispatch(doUserEmailVerifyFailure(message)); + dispatch(doNotify({ message, displayType: ['toast'] })); + } + } else { + dispatch(doNotify({ + message: 'Invalid Verification URI', + displayType: ['toast'], + })); + } + } else { + const navigateAction = NavigationActions.navigate({ + routeName: 'File', + key: evt.url, + params: { uri: evt.url } + }); + dispatch(navigateAction); + } } } @@ -224,10 +288,14 @@ class AppWithNavigationState extends React.Component { } const mapStateToProps = state => ({ + keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state), nav: state.nav, notification: selectNotification(state), - keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state), - showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state) + emailToVerify: selectEmailToVerify(state), + emailVerifyPending: selectEmailVerifyIsPending(state), + emailVerifyErrorMessage: selectEmailVerifyErrorMessage(state), + showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state), + user: selectUser(state), }); export default connect(mapStateToProps)(AppWithNavigationState); diff --git a/app/src/constants.js b/app/src/constants.js index a0c3d277..c58f0a85 100644 --- a/app/src/constants.js +++ b/app/src/constants.js @@ -1,5 +1,10 @@ const Constants = { - SETTING_ALPHA_UNDERSTANDS_RISKS: "ALPHA_UNDERSTANDS_RISKS" + KEY_FIRST_RUN_EMAIL: "firstRunEmail", + KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail", + + SETTING_ALPHA_UNDERSTANDS_RISKS: "alphaUnderstandRisks", + + ACTION_FIRST_RUN_PAGE_CHANGED: "FIRST_RUN_PAGE_CHANGED", }; export default Constants; diff --git a/app/src/index.js b/app/src/index.js index d3939553..fa0ee2c8 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -17,6 +17,7 @@ import { searchReducer, walletReducer } from 'lbry-redux'; +import { authReducer, userReducer } from 'lbryinc'; import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; import { createLogger } from 'redux-logger'; import { StackNavigator, addNavigationHelpers } from 'react-navigation'; @@ -68,6 +69,7 @@ const navigatorReducer = (state = initialNavState, action) => { }; const reducers = combineReducers({ + auth: authReducer, claims: claimsReducer, costInfo: costInfoReducer, fileInfo: fileInfoReducer, @@ -75,7 +77,8 @@ const reducers = combineReducers({ search: searchReducer, wallet: walletReducer, nav: navigatorReducer, - settings: settingsReducer + settings: settingsReducer, + user: userReducer }); const bulkThunk = createBulkThunkMiddleware(); @@ -93,18 +96,20 @@ const store = createStore( applyMiddleware(...middleware) ) ); +window.store = store; const compressor = createCompressor(); +const authFilter = createFilter('auth', ['authToken']); const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']); const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']); const settingsFilter = createFilter('settings', ['clientSettings']); const walletFilter = createFilter('wallet', ['receiveAddress']); const persistOptions = { - whitelist: ['claims', 'subscriptions', 'settings', 'wallet'], + whitelist: ['auth', 'claims', 'subscriptions', 'settings', 'wallet'], // Order is important. Needs to be compressed last or other transforms can't // read the data - transforms: [saveClaimsFilter, subscriptionsFilter, settingsFilter, walletFilter, compressor], + transforms: [authFilter, saveClaimsFilter, subscriptionsFilter, settingsFilter, walletFilter, compressor], debounce: 10000, storage: FilesystemStorage }; diff --git a/app/src/page/about/view.js b/app/src/page/about/view.js index b2196ca2..7766f2fd 100644 --- a/app/src/page/about/view.js +++ b/app/src/page/about/view.js @@ -24,7 +24,6 @@ class AboutPage extends React.PureComponent { }); }); Lbry.status().then(info => { - console.log(info); this.setState({ lbryId: info.installation_id, }); diff --git a/app/src/page/firstRun/index.js b/app/src/page/firstRun/index.js index 5e9ec0c1..14ad9603 100644 --- a/app/src/page/firstRun/index.js +++ b/app/src/page/firstRun/index.js @@ -1,6 +1,28 @@ import { connect } from 'react-redux'; +import { doNotify } from 'lbry-redux'; +import { + doGenerateAuthToken, + doUserEmailNew, + selectAuthToken, + selectEmailNewErrorMessage, + selectEmailNewIsPending, + selectEmailToVerify, + selectIsAuthenticating +} from 'lbryinc'; import FirstRun from './view'; -const perform = dispatch => ({}); +const select = (state) => ({ + authenticating: selectIsAuthenticating(state), + authToken: selectAuthToken(state), + emailToVerify: selectEmailToVerify(state), + emailNewErrorMessage: selectEmailNewErrorMessage(state), + emailNewPending: selectEmailNewIsPending(state), +}); -export default connect(null, perform)(FirstRun); \ No newline at end of file +const perform = dispatch => ({ + addUserEmail: email => dispatch(doUserEmailNew(email)), + generateAuthToken: installationId => dispatch(doGenerateAuthToken(installationId)), + notify: data => dispatch(doNotify(data)) +}); + +export default connect(select, perform)(FirstRun); \ No newline at end of file diff --git a/app/src/page/firstRun/internal/email-collect-page.js b/app/src/page/firstRun/internal/email-collect-page.js new file mode 100644 index 00000000..0f8aac2a --- /dev/null +++ b/app/src/page/firstRun/internal/email-collect-page.js @@ -0,0 +1,113 @@ +import React from 'react'; +import { Lbry } from 'lbry-redux'; +import { + ActivityIndicator, + AsyncStorage, + Linking, + Text, + TextInput, + View +} from 'react-native'; +import Colors from '../../../styles/colors'; +import Constants from '../../../constants'; +import firstRunStyle from '../../../styles/firstRun'; + +class EmailCollectPage extends React.PureComponent { + static MAX_STATUS_TRIES = 15; + + state = { + email: null, + authenticationStarted: false, + authenticationFailed: false, + statusTries: 0 + }; + + componentWillReceiveProps(nextProps) { + const { authenticating, authToken } = this.props; + + if (this.state.authenticationStarted && !authenticating && authToken === null) { + this.setState({ authenticationFailed: true, authenticationStarted: false }); + } + } + + componentDidMount() { + // call user/new + const { generateAuthToken, authenticating, authToken } = this.props; + if (!authToken && !authenticating) { + this.startAuthenticating(); + } + + AsyncStorage.getItem('firstRunEmail').then(email => { + if (email) { + this.setState({ email }); + } + }); + } + + startAuthenticating = () => { + const { generateAuthToken } = this.props; + this.setState({ authenticationStarted: true, authenticationFailed: false }); + Lbry.status().then(info => { + generateAuthToken(info.installation_id) + }).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 (15 seconds) + } + }); + } + + handleChangeText = (text) => { + // save the value to the state email + this.setState({ email: text }); + AsyncStorage.setItem(Constants.KEY_FIRST_RUN_EMAIL, text); + } + + render() { + const { authenticating, authToken, onEmailViewLayout, emailToVerify } = this.props; + + let content; + if (this.state.authenticationFailed) { + // Ask the user to try again + content = ( + + The LBRY servers were unreachable at this time. Please check your Internet connection and then restart the app to try again. + + ); + } else if (!authToken || authenticating) { + content = ( + + + Please wait while we get some things ready... + + ); + } else { + content = ( + + Rewards. + You can earn LBRY Credits (LBC) rewards by completing various tasks in the app. + Please provide a valid email address below to be able to claim your rewards. + this.handleChangeText(text)} + /> + This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards and may be used to sync usage data across devices. + + ); + } + + return ( + + {content} + + ); + } +} + +export default EmailCollectPage; diff --git a/app/src/page/firstRun/view.js b/app/src/page/firstRun/view.js index 2e868376..e668083e 100644 --- a/app/src/page/firstRun/view.js +++ b/app/src/page/firstRun/view.js @@ -1,6 +1,8 @@ import React from 'react'; import { Lbry } from 'lbry-redux'; import { + ActivityIndicator, + AsyncStorage, Linking, NativeModules, Text, @@ -9,20 +11,24 @@ import { } from 'react-native'; import { NavigationActions } from 'react-navigation'; import Colors from '../../styles/colors'; +import Constants from '../../constants'; import WelcomePage from './internal/welcome-page'; +import EmailCollectPage from './internal/email-collect-page'; import firstRunStyle from '../../styles/firstRun'; class FirstRunScreen extends React.PureComponent { - static pages = ['welcome']; + static pages = [ + 'welcome', + 'email-collect' + ]; - constructor() { - super(); - this.state = { - currentPage: null, - launchUrl: null, - isFirstRun: false - } - } + state = { + currentPage: null, + emailSubmitted: false, + isFirstRun: false, + launchUrl: null, + showBottomContainer: true + }; componentDidMount() { Linking.getInitialURL().then((url) => { @@ -47,6 +53,21 @@ class FirstRunScreen extends React.PureComponent { } } + componentWillReceiveProps(nextProps) { + const { emailNewErrorMessage, emailNewPending } = nextProps; + const { notify } = this.props; + + if (this.state.emailSubmitted && !emailNewPending) { + this.setState({ emailSubmitted: false }); + if (emailNewErrorMessage) { + notify ({ message: String(emailNewErrorMessage), displayType: ['toast']}); + } else { + // Request successful. Navigate to discover. + this.closeFinalPage(); + } + } + } + launchSplashScreen() { const { navigation } = this.props; const resetAction = NavigationActions.reset({ @@ -60,36 +81,92 @@ class FirstRunScreen extends React.PureComponent { 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(); + if (this.state.currentPage !== 'email-collect' && + pageIndex === (FirstRunScreen.pages.length - 1)) { + this.closeFinalPage(); } else { - // TODO: Page transition animation? - this.state.currentPage = FirstRunScreen.pages[pageIndex + 1]; + // TODO: Actions and page verification for specific pages + if (this.state.currentPage === 'email-collect') { + // handle email collect + this.handleEmailCollectPageContinue(); + } else { + this.showNextPage(); + } } } + handleEmailCollectPageContinue() { + const { notify, addUserEmail } = this.props; + // validate the email + AsyncStorage.getItem(Constants.KEY_FIRST_RUN_EMAIL).then(email => { + if (!email || email.trim().length === 0 || email.indexOf('@') === -1) { + return notify({ + message: 'Please provide a valid email address to continue.', + displayType: ['toast'], + }); + } + + addUserEmail(email); + this.setState({ emailSubmitted: true }); + }); + } + + showNextPage() { + const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage); + const nextPage = FirstRunScreen.pages[pageIndex + 1]; + this.setState({ currentPage: nextPage }); + if (nextPage === 'email-collect') { + // do not show the buttons (because we're waiting to get things ready) + this.setState({ showBottomContainer: false }); + } + } + + closeFinalPage() { + // 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(); + } + render() { + const { + authenticating, + authToken, + generateAuthToken, + emailNewErrorMessage, + emailNewPending, + emailToVerify + } = this.props; + let page = null; if (this.state.currentPage === 'welcome') { // show welcome page page = (); + } else if (this.state.currentPage === 'email-collect') { + page = ( this.setState({ showBottomContainer: true })} />); } return ( {page} - {this.state.currentPage && - - Continue - } + {this.state.currentPage && this.state.showBottomContainer && + + {emailNewPending && + } + + {!emailNewPending && + + Continue + } + } - ) + ); } } diff --git a/app/src/page/splash/index.js b/app/src/page/splash/index.js index d50dc5a0..f1818532 100644 --- a/app/src/page/splash/index.js +++ b/app/src/page/splash/index.js @@ -1,9 +1,18 @@ import { connect } from 'react-redux'; -import { doBalanceSubscribe } from 'lbry-redux'; +import { doBalanceSubscribe, doNotify } from 'lbry-redux'; +import { doAuthenticate, doUserEmailVerify, doUserEmailVerifyFailure, selectUser } from 'lbryinc'; import SplashScreen from './view'; -const perform = dispatch => ({ - balanceSubscribe: () => dispatch(doBalanceSubscribe()) +const select = state => ({ + user: selectUser(state), }); -export default connect(null, perform)(SplashScreen); \ No newline at end of file +const perform = dispatch => ({ + authenticate: (appVersion, deviceId) => dispatch(doAuthenticate(appVersion, deviceId)), + balanceSubscribe: () => dispatch(doBalanceSubscribe()), + notify: data => dispatch(doNotify(data)), + verifyUserEmail: (token, recaptcha) => dispatch(doUserEmailVerify(token, recaptcha)), + verifyUserEmailFailure: error => dispatch(doUserEmailVerifyFailure(error)), +}); + +export default connect(select, perform)(SplashScreen); diff --git a/app/src/page/splash/view.js b/app/src/page/splash/view.js index 45173cfa..c8394079 100644 --- a/app/src/page/splash/view.js +++ b/app/src/page/splash/view.js @@ -2,6 +2,7 @@ import React from 'react'; import { Lbry } from 'lbry-redux'; import { ActivityIndicator, + AsyncStorage, Linking, NativeModules, Platform, @@ -10,8 +11,10 @@ import { View } from 'react-native'; import { NavigationActions } from 'react-navigation'; +import { decode as atob } from 'base-64'; import PropTypes from 'prop-types'; import Colors from '../../styles/colors'; +import Constants from '../../constants'; import splashStyle from '../../styles/splash'; class SplashScreen extends React.PureComponent { @@ -21,13 +24,15 @@ class SplashScreen extends React.PureComponent { componentWillMount() { this.setState({ + daemonReady: false, details: 'Starting daemon', message: 'Connecting', isRunning: false, isLagging: false, launchUrl: null, isDownloadingHeaders: false, - headersDownloadProgress: 0 + headersDownloadProgress: 0, + shouldAuthenticate: false }); if (NativeModules.DaemonServiceControl) { @@ -52,6 +57,50 @@ class SplashScreen extends React.PureComponent { }); } + componentWillUpdate(nextProps) { + const { navigation, verifyUserEmail, verifyUserEmailFailure } = this.props; + const { user } = nextProps; + if (this.state.daemonReady && this.state.shouldAuthenticate && user && user.id) { + // user is authenticated, navigate to the main view + const resetAction = NavigationActions.reset({ + index: 0, + actions: [ + NavigationActions.navigate({ routeName: 'Main'}) + ] + }); + navigation.dispatch(resetAction); + + const launchUrl = navigation.state.params.launchUrl || this.state.launchUrl; + if (launchUrl) { + if (launchUrl.startsWith('lbry://?verify=')) { + let verification = {}; + try { + verification = JSON.parse(atob(launchUrl.substring(15))); + } catch (error) { + console.log(error); + } + if (verification.token && verification.recaptcha) { + AsyncStorage.setItem(Constants.KEY_SHOULD_VERIFY_EMAIL, 'true'); + try { + verifyUserEmail(verification.token, verification.recaptcha); + } catch (error) { + const message = 'Invalid Verification Token'; + verifyUserEmailFailure(message); + notify({ message, displayType: ['toast'] }); + } + } else { + notify({ + message: 'Invalid Verification URI', + displayType: ['toast'], + }); + } + } else { + navigation.navigate({ routeName: 'File', key: launchUrl, params: { uri: launchUrl } }); + } + } + } + } + _updateStatusCallback(status) { const startupStatus = status.startup_status; // At the minimum, wallet should be started and blocks_behind equal to 0 before calling resolve @@ -62,6 +111,7 @@ class SplashScreen extends React.PureComponent { // TODO: This is a hack, and the logic should live in the daemon // to give us a better sense of when we are actually started this.setState({ + daemonReady: true, message: 'Testing Network', details: 'Waiting for name resolution', isLagging: false, @@ -70,21 +120,25 @@ class SplashScreen extends React.PureComponent { Lbry.resolve({ uri: 'lbry://one' }).then(() => { // Leave the splash screen - const { balanceSubscribe, navigation } = this.props; + const { + authenticate, + balanceSubscribe, + navigation, + notify + } = this.props; + balanceSubscribe(); - - const resetAction = NavigationActions.reset({ - index: 0, - actions: [ - NavigationActions.navigate({ routeName: 'Main'}) - ] + NativeModules.VersionInfo.getAppVersion().then(appVersion => { + if (NativeModules.UtilityModule) { + // authenticate with the device ID if the method is available + NativeModules.UtilityModule.getDeviceId().then(deviceId => { + authenticate(`android-${appVersion}`, deviceId); + }); + } else { + authenticate(appVersion); + } + this.setState({ shouldAuthenticate: true }); }); - navigation.dispatch(resetAction); - - const launchUrl = navigation.state.params.launchUrl || this.state.launchUrl; - if (launchUrl) { - navigation.navigate({ routeName: 'File', key: launchUrl, params: { uri: launchUrl } }); - } }); return; } diff --git a/app/src/redux/actions/settings.js b/app/src/redux/actions/settings.js index aa9f9e61..9a80bc77 100644 --- a/app/src/redux/actions/settings.js +++ b/app/src/redux/actions/settings.js @@ -9,3 +9,4 @@ export function doSetClientSetting(key, value) { }, }; } + diff --git a/app/src/redux/reducers/settings.js b/app/src/redux/reducers/settings.js index bab57f18..1e6f70f1 100644 --- a/app/src/redux/reducers/settings.js +++ b/app/src/redux/reducers/settings.js @@ -1,7 +1,5 @@ -import { AsyncStorage } from 'react-native'; import { ACTIONS } from 'lbry-redux'; - const reducers = {}; const defaultState = { clientSettings: {} diff --git a/app/src/styles/firstRun.js b/app/src/styles/firstRun.js index c5d3f4c6..562e2268 100644 --- a/app/src/styles/firstRun.js +++ b/app/src/styles/firstRun.js @@ -28,16 +28,56 @@ const firstRunStyle = StyleSheet.create({ marginBottom: 20, color: Colors.White }, - button: { + infoParagraph: { + fontFamily: 'Metropolis-Regular', + fontSize: 14, + lineHeight: 20, + marginLeft: 32, + marginRight: 32, + marginBottom: 20, + color: Colors.White + }, + emailInput: { + fontFamily: 'Metropolis-Regular', + fontSize: 24, + lineHeight: 24, + marginLeft: 32, + marginRight: 32, + marginBottom: 20, + textAlign: 'center' + }, + leftButton: { flex: 1, + alignSelf: 'flex-start', + marginLeft: 32, + marginRight: 32 + }, + bottomContainer: { + flex: 1 + }, + actionButton: { + backgroundColor: Colors.White, + alignSelf: 'center', + fontFamily: 'Metropolis-Regular', + fontSize: 12, + paddingLeft: 16, + paddingRight: 16 + }, + button: { alignSelf: 'flex-end', marginLeft: 32, marginRight: 32 }, buttonText: { fontFamily: 'Metropolis-Regular', - fontSize: 28, + fontSize: 24, color: Colors.White + }, + waiting: { + marginBottom: 24 + }, + pageWaiting: { + alignSelf: 'center' } }); diff --git a/src/main/java/io/lbry/browser/MainActivity.java b/src/main/java/io/lbry/browser/MainActivity.java index 9bca57bf..f27a8d8b 100644 --- a/src/main/java/io/lbry/browser/MainActivity.java +++ b/src/main/java/io/lbry/browser/MainActivity.java @@ -172,19 +172,10 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand Toast.makeText(context, "Rewards cannot be claimed because we could not identify your device.", Toast.LENGTH_LONG).show(); } - try { - MessageDigest md = MessageDigest.getInstance("SHA-384"); - md.update(id.getBytes("UTF-8")); - String hash = new BigInteger(1, md.digest()).toString(16); - - SharedPreferences sp = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sp.edit(); - editor.putString(DEVICE_ID_KEY, hash); - editor.commit(); - } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) { - // SHA-384 not found, UTF-8 encoding not supported - Toast.makeText(context, "Rewards cannot be claimed because we could not identify your device.", Toast.LENGTH_LONG).show(); - } + SharedPreferences sp = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sp.edit(); + editor.putString(DEVICE_ID_KEY, id); + editor.commit(); return id; } diff --git a/src/main/java/io/lbry/browser/reactmodules/UtilityModule.java b/src/main/java/io/lbry/browser/reactmodules/UtilityModule.java index e3804aa2..7cc6595c 100644 --- a/src/main/java/io/lbry/browser/reactmodules/UtilityModule.java +++ b/src/main/java/io/lbry/browser/reactmodules/UtilityModule.java @@ -2,13 +2,17 @@ package io.lbry.browser.reactmodules; import android.app.Activity; import android.content.Context; +import android.content.SharedPreferences; import android.view.View; import android.view.WindowManager; +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 UtilityModule extends ReactContextBaseJavaModule { private Context context; @@ -71,4 +75,11 @@ public class UtilityModule extends ReactContextBaseJavaModule { View.SYSTEM_UI_FLAG_VISIBLE); } } + + @ReactMethod + public void getDeviceId(final Promise promise) { + SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); + String deviceId = sp.getString(MainActivity.DEVICE_ID_KEY, null); + promise.resolve(deviceId); + } }