diff --git a/src/component/spinner.js b/src/component/spinner.js index a13972ec5..cc5691033 100644 --- a/src/component/spinner.js +++ b/src/component/spinner.js @@ -34,13 +34,10 @@ export const SmallSpinner = ({ ...props }) => ( // Load Network Spinner // -const sizeM = 80; -const progressWidthM = 3; +const size = 80; +const progressWidth = 3; const loadNetworkStyles = StyleSheet.create({ - spinner: { - margin: 20, - }, bolt: { height: 126 / 4.5, width: 64 / 4.5, @@ -53,12 +50,12 @@ const loadNetworkStyles = StyleSheet.create({ }, }); -export const LoadNetworkSpinner = ({ percentage, msg }) => ( - +export const LoadNetworkSpinner = ({ percentage, msg, style }) => ( + ( LoadNetworkSpinner.propTypes = { percentage: PropTypes.number.isRequired, msg: PropTypes.string.isRequired, + style: ViewPropTypes.style, }; // @@ -88,7 +86,7 @@ const resizeableStyles = StyleSheet.create({ }, }); -const ResizeableSpinner = ({ +export const ResizeableSpinner = ({ percentage, size, gradient, @@ -140,7 +138,7 @@ const Gradients = () => ( - + @@ -187,11 +185,11 @@ SpinnerFill.propTypes = { const generateArc = (percentage, radius) => { if (percentage === 0) { - percentage = 1; - } else if (percentage === 100) { - percentage = 99.999; + percentage = 0.001; + } else if (percentage === 1) { + percentage = 0.999; } - const a = percentage * 2 * Math.PI / 100; // angle (in radian) depends on percentage + const a = percentage * 2 * Math.PI; // angle (in radian) depends on percentage const r = radius; // radius of the circle var rx = r, ry = r, @@ -200,7 +198,7 @@ const generateArc = (percentage, radius) => { sweepFlag = 1, x = r + r * Math.sin(a), y = r - r * Math.cos(a); - if (percentage <= 50) { + if (percentage <= 0.5) { largeArcFlag = 0; } else { largeArcFlag = 1; diff --git a/src/component/style.js b/src/component/style.js index 64a7fb22b..021ef7c53 100644 --- a/src/component/style.js +++ b/src/component/style.js @@ -14,7 +14,6 @@ export const color = { loadNetworkLightPurple: '#A95BDC', loadNetworkMedPurple: '#651399', loadNetworkMedDarkPurple: '#610F96', - openChansLightPurple: '#A540CD', openChansDarkPurple: '#6B249C', orange: '#F66B1C', blackDark: '#252F4A', diff --git a/src/computed/loader-msg.js b/src/computed/loader-msg.js new file mode 100644 index 000000000..a29e9f626 --- /dev/null +++ b/src/computed/loader-msg.js @@ -0,0 +1,41 @@ +import { computed, extendObservable } from 'mobx'; + +const ComputedLoaderMsg = store => { + extendObservable(store, { + loadingMsg: computed(() => { + const { percentSynced: percent } = store; + return getLoadingMsg(percent); + }), + }); +}; + +export const LOADING_COPY_START = 'Loading network...'; +export const LOADING_COPY_MID = 'Almost done...'; +export const LOADING_COPY_END = 'Just a few seconds...'; +export const LOADING_PERCENT_MID = 0.5; +export const LOADING_PERCENT_END = 0.95; + +/** + * Retrieve the loading message corresponding to the percent + * @param {number} percent The percentage the network is synced. + * @return {string} The message corresponding to the percent. + */ +const getLoadingMsg = percent => { + percent = Number(percent); + if (isNaN(percent)) { + percent = 0; + } else if (percent < 0) { + percent = 0; + } else if (percent > 1) { + percent = 1; + } + if (percent < LOADING_PERCENT_MID) { + return LOADING_COPY_START; + } else if (percent < LOADING_PERCENT_END) { + return LOADING_COPY_MID; + } else { + return LOADING_COPY_END; + } +}; + +export default ComputedLoaderMsg; diff --git a/src/store.js b/src/store.js index b7567dc52..724b71a10 100644 --- a/src/store.js +++ b/src/store.js @@ -1,4 +1,5 @@ import { extendObservable } from 'mobx'; +import ComputedLoaderMsg from './computed/loader-msg'; import ComputedWallet from './computed/wallet'; import ComputedTransaction from './computed/transaction'; import ComputedChannel from './computed/channel'; @@ -17,6 +18,7 @@ export class Store { walletUnlocked: false, // Is the wallet unlocked lndReady: false, // Is lnd process running syncedToChain: false, // Is lnd synced to blockchain + percentSynced: 0, // Expects 0-1 range route: DEFAULT_ROUTE, blockHeight: null, balanceSatoshis: 0, @@ -72,6 +74,7 @@ export class Store { } init() { + ComputedLoaderMsg(this); ComputedWallet(this); ComputedTransaction(this); ComputedChannel(this); diff --git a/src/view/loader-syncing.js b/src/view/loader-syncing.js new file mode 100644 index 000000000..b1c8d552f --- /dev/null +++ b/src/view/loader-syncing.js @@ -0,0 +1,73 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import PropTypes from 'prop-types'; +import Background from '../component/background'; +import { H1Text, CopyText } from '../component/text'; +import MainContent from '../component/main-content'; +import { LoadNetworkSpinner } from '../component/spinner'; +import { DownButton } from '../component/button'; +import { color } from '../component/style'; + +const styles = StyleSheet.create({ + spinner: { + marginTop: 40, + }, + downBtn: { + margin: 25, + }, +}); + +const LoaderSyncingView = ({ store }) => ( + + + + + {}} style={styles.downBtn}> + Learn More + + + +); + +LoaderSyncingView.propTypes = { + store: PropTypes.object.isRequired, +}; + +// +// Copy Section +// + +const copyStyles = StyleSheet.create({ + wrapper: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + title: { + textAlign: 'center', + marginTop: 30, + }, + copyTxt: { + textAlign: 'center', + marginTop: 10, + maxWidth: 450, + paddingBottom: 30, + }, +}); + +const CopySection = () => ( + + Almost there + + { + "Why not learn more about what we're doing at Lightning Labs? Or grab a coffee. This could take about 30 minutes." + } + + +); + +export default LoaderSyncingView; diff --git a/stories/component/spinner.js b/stories/component/spinner.js index b4c72d464..5f01074d2 100644 --- a/stories/component/spinner.js +++ b/stories/component/spinner.js @@ -1,7 +1,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import MainContent from '../../src/component/main-content'; -import { LoadNetworkSpinner, SmallSpinner } from '../../src/component/spinner'; +import { SmallSpinner, LoadNetworkSpinner } from '../../src/component/spinner'; import { color } from '../../src/component/style'; import Background from '../../src/component/background'; @@ -17,8 +17,20 @@ storiesOf('Spinner', module) )) .add('Load Network Spinner', () => ( - - - + + + )); diff --git a/stories/screen.js b/stories/screen.js index ce76e25c6..03880e9bd 100644 --- a/stories/screen.js +++ b/stories/screen.js @@ -37,6 +37,7 @@ import PayBitcoinConfirm from '../src/view/pay-bitcoin-confirm'; import PayBitcoinDone from '../src/view/pay-bitcoin-done'; import NoRoute from '../src/view/no-route'; import Loader from '../src/view/loader'; +import LoaderSyncing from '../src/view/loader-syncing'; import SeedSuccess from '../src/view/seed-success'; import Seed from '../src/view/seed'; import SeedVerify from '../src/view/seed-verify'; @@ -92,6 +93,7 @@ storiesOf('Screens', module) )) .add('Wait', () => ) + .add('Loader - Syncing Chain', () => ) .add('Home', () => ( ({ status: i % 2 === 0 ? 'pending-closing' : 'pending-open', })); store.selectedChannel = store.computedChannels && store.computedChannels[0]; +store.percentSynced = 0.3; store.seedMnemonic = [ 'empower', 'neglect', diff --git a/test/unit/computed/loader-msg.spec.js b/test/unit/computed/loader-msg.spec.js new file mode 100644 index 000000000..7d16cf0c9 --- /dev/null +++ b/test/unit/computed/loader-msg.spec.js @@ -0,0 +1,50 @@ +import { observable, useStrict } from 'mobx'; +import ComputedLoaderMsg from '../../../src/computed/loader-msg'; +import { + LOADING_COPY_START, + LOADING_COPY_MID, + LOADING_COPY_END, + LOADING_PERCENT_MID, + LOADING_PERCENT_END, +} from '../../../src/computed/loader-msg'; + +describe('Computed Loader Msg Unit Tests', () => { + let store; + + beforeEach(() => { + useStrict(false); + store = observable({ + percentSynced: 0, + }); + }); + + // TODO: figure out how error handling will work on network error + describe('ComputedLoaderMsg()', () => { + it('should work with non-numerical values', () => { + store.percentSynced = null; + ComputedLoaderMsg(store); + expect(store.loadingMsg, 'to equal', LOADING_COPY_START); + }); + + it('should work for < 0 and > 1 percentages', () => { + store.percentSynced = -1; + ComputedLoaderMsg(store); + expect(store.loadingMsg, 'to equal', LOADING_COPY_START); + store.percentSynced = 1.01; + ComputedLoaderMsg(store); + expect(store.loadingMsg, 'to equal', LOADING_COPY_END); + }); + + it('should work for each milestone percentage', () => { + store.percentSynced = 0.1; + ComputedLoaderMsg(store); + expect(store.loadingMsg, 'to equal', LOADING_COPY_START); + store.percentSynced = LOADING_PERCENT_MID; + ComputedLoaderMsg(store); + expect(store.loadingMsg, 'to equal', LOADING_COPY_MID); + store.percentSynced = LOADING_PERCENT_END; + ComputedLoaderMsg(store); + expect(store.loadingMsg, 'to equal', LOADING_COPY_END); + }); + }); +});