diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-07-18 15:07:37 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-08-15 17:39:38 +0200 |
| commit | 71592249b2dd669b6f24f37bfb7b0f4e43b74998 (patch) | |
| tree | a6097dc7e5d94d06e99c65fdfe160e824395f50c /app/components | |
| parent | e84e87f4ce5a8c242f756566cdc6fb59a45f7bea (diff) | |
| download | mullvadvpn-71592249b2dd669b6f24f37bfb7b0f4e43b74998.tar.xz mullvadvpn-71592249b2dd669b6f24f37bfb7b0f4e43b74998.zip | |
Add workspaces
Diffstat (limited to 'app/components')
44 files changed, 0 insertions, 5453 deletions
diff --git a/app/components/Accordion.js b/app/components/Accordion.js deleted file mode 100644 index 8197fd0e61..0000000000 --- a/app/components/Accordion.js +++ /dev/null @@ -1,142 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Component, View, Styles, Animated, UserInterface } from 'reactxp'; -import { log } from '../lib/platform'; - -export type AccordionProps = { - height: number | 'auto', - animationDuration?: number, - children?: React.Node, -}; - -export type AccordionState = { - animatedValue: ?Animated.Value, -}; - -const containerOverflowStyle = Styles.createViewStyle({ overflow: 'hidden' }); - -export default class Accordion extends Component<AccordionProps, AccordionState> { - static defaultProps = { - height: 'auto', - animationDuration: 350, - }; - - state: AccordionState = { - animatedValue: null, - animation: null, - }; - - _containerView: ?React.Node; - _contentHeight = 0; - _animation = (null: ?Animated.CompositeAnimation); - - constructor(props: AccordionProps) { - super(props); - - // set the initial height if it's known - if (typeof props.height === 'number') { - this.state = { - animatedValue: Animated.createValue(props.height), - }; - } - } - - componentWillUnmount() { - if (this._animation) { - this._animation.stop(); - } - } - - shouldComponentUpdate(nextProps: AccordionProps, nextState: AccordionState) { - return ( - nextState.animatedValue !== this.state.animatedValue || - nextProps.height !== this.props.height || - nextProps.children !== this.props.children - ); - } - - componentDidUpdate(prevProps: AccordionProps, _prevState: AccordionState) { - if (prevProps.height !== this.props.height) { - this._animateHeightChanges(); - } - } - - render() { - const { - style: style, - height: _height, - children, - animationDuration: _animationDuration, - ...otherProps - } = this.props; - const containerStyles = [style]; - - if (this.state.animatedValue !== null) { - const animatedStyle = Styles.createAnimatedViewStyle({ - height: this.state.animatedValue, - }); - - containerStyles.push(containerOverflowStyle, animatedStyle); - } - - return ( - <Animated.View - {...otherProps} - style={containerStyles} - ref={(node) => (this._containerView = node)}> - <View onLayout={this._contentLayoutDidChange}>{children}</View> - </Animated.View> - ); - } - - async _animateHeightChanges() { - const containerView = this._containerView; - if (!containerView) { - return; - } - - if (this._animation) { - this._animation.stop(); - this._animation = null; - } - - try { - const layout = await UserInterface.measureLayoutRelativeToWindow(containerView); - const fromValue = this.state.animatedValue || Animated.createValue(layout.height); - const toValue = this.props.height === 'auto' ? this._contentHeight : this.props.height; - - // calculate the animation duration based on travel distance - const multiplier = Math.abs(toValue - layout.height) / Math.max(1, this._contentHeight); - const duration = Math.ceil(this.props.animationDuration * multiplier); - - const animation = Animated.timing(fromValue, { - toValue: toValue, - easing: Animated.Easing.InOut(), - duration: duration, - useNativeDriver: true, - }); - - this._animation = animation; - this.setState({ animatedValue: fromValue }, () => { - animation.start(this._onAnimationEnd); - }); - } catch (error) { - log.error(`Failed to measure the layout of Accordion: ${error.message}`); - } - } - - _onAnimationEnd = ({ finished }) => { - if (finished) { - this._animation = null; - - // reset height after transition to let element layout naturally - // if animation finished without interruption - if (this.props.height === 'auto') { - this.setState({ animatedValue: null }); - } - } - }; - - _contentLayoutDidChange = ({ height }) => (this._contentHeight = height); -} diff --git a/app/components/Account.js b/app/components/Account.js deleted file mode 100644 index 41aea6d894..0000000000 --- a/app/components/Account.js +++ /dev/null @@ -1,159 +0,0 @@ -// @flow - -import moment from 'moment'; -import * as React from 'react'; -import { Component, Text, View } from 'reactxp'; -import * as AppButton from './AppButton'; -import { Layout, Container } from './Layout'; -import NavigationBar, { BackBarItem } from './NavigationBar'; -import SettingsHeader, { HeaderTitle } from './SettingsHeader'; -import styles from './AccountStyles'; -import Img from './Img'; -import { formatAccount } from '../lib/formatters'; -import WindowStateObserver from '../lib/window-state-observer'; - -import type { AccountToken } from '../lib/daemon-rpc'; - -type Props = { - accountToken: AccountToken, - accountExpiry: string, - expiryLocale: string, - updateAccountExpiry: () => Promise<void>, - onLogout: () => void, - onClose: () => void, - onCopyAccountToken: () => void, - onBuyMore: () => void, -}; - -type State = { - isRefreshingExpiry: boolean, - showAccountTokenCopiedMessage: boolean, -}; - -export default class Account extends Component<Props, State> { - state = { - isRefreshingExpiry: false, - showAccountTokenCopiedMessage: false, - }; - - _isMounted = false; - _copyTimer: ?TimeoutID; - _windowStateObserver = new WindowStateObserver(); - - componentDidMount() { - this._isMounted = true; - this._refreshAccountExpiry(); - - this._windowStateObserver.onShow = () => { - this._refreshAccountExpiry(); - }; - } - - componentWillUnmount() { - this._isMounted = false; - - if (this._copyTimer) { - clearTimeout(this._copyTimer); - } - - this._windowStateObserver.dispose(); - } - - onAccountTokenClick() { - if (this._copyTimer) { - clearTimeout(this._copyTimer); - } - this._copyTimer = setTimeout( - () => this.setState({ showAccountTokenCopiedMessage: false }), - 3000, - ); - this.setState({ showAccountTokenCopiedMessage: true }); - this.props.onCopyAccountToken(); - } - - render() { - const expiry = moment(this.props.accountExpiry); - const isOutOfTime = expiry.isSameOrBefore(moment()); - const formattedAccountToken = formatAccount(this.props.accountToken || ''); - const formattedExpiry = expiry.toDate().toLocaleString(this.props.expiryLocale, { - day: 'numeric', - month: 'long', - year: 'numeric', - hour: 'numeric', - minute: 'numeric', - }); - - return ( - <Layout> - <Container> - <View style={styles.account}> - <NavigationBar> - <BackBarItem action={this.props.onClose} title={'Settings'} /> - </NavigationBar> - - <View style={styles.account__container}> - <SettingsHeader> - <HeaderTitle>Account</HeaderTitle> - </SettingsHeader> - - <View style={styles.account__content}> - <View style={styles.account__main}> - <View style={styles.account__row}> - <Text style={styles.account__row_label}>Account ID</Text> - <Text - style={styles.account__row_value} - onPress={this.onAccountTokenClick.bind(this)}> - {this.state.showAccountTokenCopiedMessage - ? 'COPIED TO CLIPBOARD!' - : formattedAccountToken} - </Text> - </View> - - <View style={styles.account__row}> - <Text style={styles.account__row_label}>Paid until</Text> - {isOutOfTime ? ( - <Text style={styles.account__out_of_time} testName="account__out_of_time"> - {'OUT OF TIME'} - </Text> - ) : ( - <Text style={styles.account__row_value}>{formattedExpiry}</Text> - )} - </View> - - <View style={styles.account__footer}> - <AppButton.GreenButton - style={styles.account__buy_button} - onPress={this.props.onBuyMore} - text="Buy more credit" - icon="icon-extLink" - testName="account__buymore"> - <AppButton.Label>Buy more credit</AppButton.Label> - <Img source="icon-extLink" height={16} width={16} /> - </AppButton.GreenButton> - <AppButton.RedButton onPress={this.props.onLogout} testName="account__logout"> - {'Log out'} - </AppButton.RedButton> - </View> - </View> - </View> - </View> - </View> - </Container> - </Layout> - ); - } - - async _refreshAccountExpiry() { - this.setState({ isRefreshingExpiry: true }); - - try { - await this.props.updateAccountExpiry(); - } catch (e) { - // TODO: Report the error to user - } - - if (this._isMounted) { - this.setState({ isRefreshingExpiry: false }); - } - } -} diff --git a/app/components/AccountInput.js b/app/components/AccountInput.js deleted file mode 100644 index 6e9dc34a7f..0000000000 --- a/app/components/AccountInput.js +++ /dev/null @@ -1,307 +0,0 @@ -// @flow - -import * as React from 'react'; -import { TextInput } from 'reactxp'; -import { formatAccount } from '../lib/formatters'; -import { colors } from '../config'; - -// ESLint issue: https://github.com/babel/babel-eslint/issues/445 -declare class ClipboardEvent extends Event { - clipboardData: DataTransfer; -} - -export type AccountInputProps = { - value: string, - onEnter: ?() => void, - onChange: ?(newValue: string) => void, -}; - -type AccountInputState = { - value: string, - selectionRange: SelectionRange, -}; - -type SelectionRange = [number, number]; - -export default class AccountInput extends React.Component<AccountInputProps, AccountInputState> { - static defaultProps = { - value: '', - onEnter: null, - onChange: null, - }; - - state = { - value: '', - selectionRange: [0, 0], - }; - - _ref: ?TextInput; - - constructor(props: AccountInputProps) { - super(props); - - // selection range holds selection converted from DOM selection range to - // internal unformatted representation of account number - const val = this.sanitize(props.value); - - this.state = { - value: val, - selectionRange: [val.length, val.length], - }; - } - - componentWillReceiveProps(nextProps: AccountInputProps) { - const nextVal = this.sanitize(nextProps.value); - if (nextVal !== this.state.value) { - const len = nextVal.length; - this.setState({ value: nextVal, selectionRange: [len, len] }); - } - } - - shouldComponentUpdate(nextProps: AccountInputProps, nextState: AccountInputState) { - const mergedProps = { ...this.props, ...nextProps }; - const hasPropChanges = Object.keys(mergedProps).some((key) => { - return this.props[key] !== nextProps[key]; - }); - - return ( - hasPropChanges || - this.state.value !== nextState.value || - this.state.selectionRange[0] !== nextState.selectionRange[0] || - this.state.selectionRange[1] !== nextState.selectionRange[1] - ); - } - - render() { - const displayString = formatAccount(this.state.value || ''); - const { value: _value, onChange: _onChange, onEnter: _onEnter, ...otherProps } = this.props; - return ( - <TextInput - {...otherProps} - value={displayString} - onSelectionChange={this.onSelect} - onPaste={this.onPaste} - onCut={this.onCut} - ref={(ref) => this.onRef(ref)} - autoCorrect={false} - onChangeText={() => {}} - onKeyPress={this.onKeyPress} - returnKeyType="done" - keyboardType="numeric" - placeholderTextColor={colors.blue20} - testName="AccountInput" - /> - ); - } - - // Private - - /** - * Modify original string inserting substring using selection range - */ - sanitize(val: ?string): string { - return (val || '').replace(/[^0-9]/g, ''); - } - - /** - * Modify original string inserting substring using selection range - * - * @private - * @param {String} val original string - * @param {String} insert insertion string - * @param {Array} selRange selection range ([x,y]) - * @returns {Object} - */ - insert(val: string, insert: string, selRange: SelectionRange): AccountInputState { - const head = val.slice(0, selRange[0]); - const tail = val.slice(selRange[1], val.length); - const newVal = head + insert + tail; - const selectionOffset = head.length + insert.length; - - return { value: newVal, selectionRange: [selectionOffset, selectionOffset] }; - } - - /** - * Modify string by removing single character or range of characters based on selection range. - * - * @private - * @param {String} val original string - * @param {Array} selRange selection range ([x,y]) - * @returns {Object} - * - * @memberOf AccountInput - */ - remove(val: string, selRange: SelectionRange): AccountInputState { - let newVal, selectionOffset; - - if (selRange[0] === selRange[1]) { - const oneOff = Math.max(0, selRange[0] - 1); - const head = val.slice(0, oneOff); - const tail = val.slice(selRange[0], val.length); - newVal = head + tail; - selectionOffset = head.length; - } else { - const head = val.slice(0, selRange[0]); - const tail = val.slice(selRange[1], val.length); - newVal = head + tail; - selectionOffset = head.length; - } - - return { value: newVal, selectionRange: [selectionOffset, selectionOffset] }; - } - - /** - * Convert DOM selection range to internal selection range - * - * @private - * @param {String} val original string - * @param {Array} domRange selection range from DOM - * @returns {Object} - * - * @memberOf AccountInput - */ - toInternalSelectionRange(val: string, domRange: SelectionRange): SelectionRange { - const countSpaces = (val) => { - return (val.match(/\s/g) || []).length; - }; - - const fmt = formatAccount(val || ''); - let start = domRange[0]; - let end = domRange[1]; - const before = countSpaces(fmt.slice(0, start)); - const within = countSpaces(fmt.slice(start, end)); - - start -= before; - end -= before + within; - - return [start, end]; - } - - /** - * Convert internal selection range to DOM selection range - * - * @private - * @param {String} val original string - * @param {Array} selRange selection range - * @returns {Object} - * - * @memberOf AccountInput - */ - toDomSelection(val: string, selRange: SelectionRange): SelectionRange { - const countSpaces = (val, untilIndex) => { - if (val.length > 12) { - return 0; - } - return Math.floor(untilIndex / 4); // groups of 4 digits - }; - - let start = selRange[0]; - let end = selRange[1]; - const startSpaces = countSpaces(val, start); - const endSpaces = countSpaces(val, end); - - start += startSpaces; - end += startSpaces + (endSpaces - startSpaces); - - return [start, end]; - } - - // Events - _ignoreSelect: boolean; - onKeyPress = (e: KeyboardEvent) => { - const { value, selectionRange } = this.state; - - if (e.which === 8) { - // backspace - this._ignoreSelect = true; - const result = this.remove(value, selectionRange); - e.preventDefault(); - - this.setState(result, () => { - this._ignoreSelect = false; - if (this.props.onChange) { - this.props.onChange(result.value); - } - }); - } else if (/^[0-9]$/.test(e.key)) { - // digits or cmd+v - this._ignoreSelect = true; - const result = this.insert(value, e.key, selectionRange); - e.preventDefault(); - - this.setState(result, () => { - this._ignoreSelect = false; - if (this.props.onChange) { - this.props.onChange(result.value); - } - }); - } else if (e.which === 13 && this.props.onEnter) { - this.props.onEnter(); - } - }; - - onSelect = (start: number, end: number) => { - if (this._ignoreSelect) { - return; - } - const selRange = this.toInternalSelectionRange(this.sanitize(this.state.value), [start, end]); - this.setState({ selectionRange: selRange }); - }; - - onPaste = (e: ClipboardEvent) => { - const { value, selectionRange } = this.state; - const pastedData = e.clipboardData.getData('text'); - const filteredData = this.sanitize(pastedData); - const result = this.insert(value, filteredData, selectionRange); - e.preventDefault(); - this.setState(result, () => { - if (this.props.onChange) { - this.props.onChange(result.value); - } - }); - }; - - onCut = (e: ClipboardEvent) => { - const target = e.target; - if (!(target instanceof HTMLInputElement)) { - throw new Error('ref must be an instance of HTMLInputElement'); - } - - const { value, selectionRange } = this.state; - - e.preventDefault(); - - // range is not empty? - if (selectionRange[0] !== selectionRange[1]) { - const result = this.remove(value, selectionRange); - const domSelectionRange = this.toDomSelection(value, selectionRange); - const slice = target.value.slice(domSelectionRange[0], domSelectionRange[1]); - - e.clipboardData.setData('text', slice); - - this.setState(result, () => { - if (this.props.onChange) { - this.props.onChange(result.value); - } - }); - } - }; - - onRef = (ref: ?TextInput) => { - this._ref = ref; - if (!ref) { - return; - } - - const { value, selectionRange } = this.state; - const domRange = this.toDomSelection(value, selectionRange); - - ref.selectRange(domRange[0], domRange[1]); - }; - - focus() { - if (this._ref) { - this._ref.focus(); - } - } -} diff --git a/app/components/AccountStyles.js b/app/components/AccountStyles.js deleted file mode 100644 index a9cc1bf6ef..0000000000 --- a/app/components/AccountStyles.js +++ /dev/null @@ -1,74 +0,0 @@ -// @flow - -import { Styles } from 'reactxp'; -import { colors } from '../config'; - -export default { - account: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - flex: 1, - }), - account__container: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - paddingBottom: 48, - }), - account__scrollview: Styles.createViewStyle({ - flexGrow: 1, - flexShrink: 1, - flexBasis: '100%', - }), - account__content: Styles.createViewStyle({ - flexDirection: 'column', - flexGrow: 1, - flexShrink: 0, - flexBasis: 'auto', - }), - account__main: Styles.createViewStyle({ - marginBottom: 24, - }), - account__row: Styles.createViewStyle({ - paddingTop: 0, - paddingBottom: 0, - paddingLeft: 24, - paddingRight: 24, - marginBottom: 24, - }), - account__footer: Styles.createViewStyle({ - paddingLeft: 24, - paddingRight: 24, - }), - account__buy_button: Styles.createViewStyle({ - marginBottom: 24, - }), - account__row_label: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - lineHeight: 20, - letterSpacing: -0.2, - color: colors.white60, - marginBottom: 9, - }), - account__row_value: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 16, - lineHeight: 19, - fontWeight: '800', - color: colors.white, - }), - account__out_of_time: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 16, - fontWeight: '800', - color: colors.red, - }), - account__footer_label: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - lineHeight: 20, - letterSpacing: -0.2, - color: colors.white80, - }), -}; diff --git a/app/components/AdvancedSettings.js b/app/components/AdvancedSettings.js deleted file mode 100644 index 3763e9687d..0000000000 --- a/app/components/AdvancedSettings.js +++ /dev/null @@ -1,178 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Button, Component, Text, View } from 'reactxp'; -import { Layout, Container } from './Layout'; -import NavigationBar, { BackBarItem } from './NavigationBar'; -import SettingsHeader, { HeaderTitle } from './SettingsHeader'; -import CustomScrollbars from './CustomScrollbars'; -import Switch from './Switch'; -import styles from './AdvancedSettingsStyles'; -import Img from './Img'; - -type AdvancedSettingsProps = { - enableIpv6: boolean, - protocol: string, - port: string | number, - setEnableIpv6: (boolean) => void, - onUpdate: (protocol: string, port: string | number) => void, - onClose: () => void, -}; - -export class AdvancedSettings extends Component<AdvancedSettingsProps> { - render() { - let portSelector = null; - let protocol = this.props.protocol.toUpperCase(); - - if (protocol === 'AUTOMATIC') { - protocol = 'Automatic'; - } else { - portSelector = this._createPortSelector(); - } - - return ( - <Layout> - <Container> - <View style={styles.advanced_settings}> - <NavigationBar> - <BackBarItem action={this.props.onClose} title={'Settings'} /> - </NavigationBar> - - <View style={styles.advanced_settings__container}> - <SettingsHeader> - <HeaderTitle>Advanced</HeaderTitle> - </SettingsHeader> - <CustomScrollbars style={styles.advanced_settings__scrollview} autoHide={true}> - <View style={styles.advanced_settings__ipv6}> - <View style={styles.advanced_settings__cell_label_container}> - <Text style={styles.advanced_settings__cell_label}>Enable IPv6</Text> - </View> - <View style={styles.advanced_settings__ipv6_accessory}> - <Switch isOn={this.props.enableIpv6} onChange={this.props.setEnableIpv6} /> - </View> - </View> - <View style={styles.advanced_settings__cell_footer}> - <Text style={styles.advanced_settings__cell_footer_label}> - {'Enable IPv6 communication through the tunnel.'} - </Text> - </View> - - <View style={styles.advanced_settings__content}> - <Selector - title={'Network protocols'} - values={['Automatic', 'UDP', 'TCP']} - value={protocol} - onSelect={(protocol) => { - this.props.onUpdate(protocol, 'Automatic'); - }} - /> - - <View style={styles.advanced_settings__cell_spacer} /> - - {portSelector} - </View> - </CustomScrollbars> - </View> - </View> - </Container> - </Layout> - ); - } - - _createPortSelector() { - const protocol = this.props.protocol.toUpperCase(); - const ports = - protocol === 'TCP' - ? ['Automatic', 80, 443] - : ['Automatic', 1194, 1195, 1196, 1197, 1300, 1301, 1302]; - - return ( - <Selector - title={protocol + ' port'} - values={ports} - value={this.props.port} - onSelect={(port) => { - this.props.onUpdate(protocol, port); - }} - /> - ); - } -} - -type SelectorProps<T> = { - title: string, - values: Array<T>, - value: T, - onSelect: (T) => void, -}; - -type SelectorState = { - hoveredButtonIndex: number, -}; - -class Selector extends Component<SelectorProps<*>, SelectorState> { - state = { hoveredButtonIndex: -1 }; - - handleButtonHover = (value) => { - this.setState({ hoveredButtonIndex: value }); - }; - - render() { - return ( - <View> - <View style={styles.advanced_settings__section_title}>{this.props.title}</View> - - {this.props.values.map((value) => this._renderCell(value))} - </View> - ); - } - - _renderCell(value) { - const selected = value === this.props.value; - if (selected) { - return this._renderSelectedCell(value); - } else { - return this._renderUnselectedCell(value); - } - } - - _renderSelectedCell(value) { - return ( - <Button - style={[ - styles.advanced_settings__cell, - value === this.state.hoveredButtonIndex - ? styles.advanced_settings__cell_selected_hover - : null, - ]} - onPress={() => this.props.onSelect(value)} - onHoverStart={() => this.handleButtonHover(value)} - onHoverEnd={() => this.handleButtonHover(-1)} - key={value}> - <Img - style={styles.advanced_settings__cell_icon} - source="icon-tick" - tintColor="currentColor" - /> - <Text style={styles.advanced_settings__cell_label}>{value}</Text> - </Button> - ); - } - - _renderUnselectedCell(value) { - return ( - <Button - style={[ - styles.advanced_settings__cell_dimmed, - value === this.state.hoveredButtonIndex ? styles.advanced_settings__cell_hover : null, - ]} - onPress={() => this.props.onSelect(value)} - onHoverStart={() => this.handleButtonHover(value)} - onHoverEnd={() => this.handleButtonHover(-1)} - key={value}> - <View style={styles.advanced_settings__cell_icon} /> - <Text style={styles.advanced_settings__cell_label}>{value}</Text> - </Button> - ); - } -} diff --git a/app/components/AdvancedSettingsStyles.js b/app/components/AdvancedSettingsStyles.js deleted file mode 100644 index cb3923e282..0000000000 --- a/app/components/AdvancedSettingsStyles.js +++ /dev/null @@ -1,117 +0,0 @@ -// @flow - -import { Styles } from 'reactxp'; -import { colors } from '../config'; - -export default { - advanced_settings: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - flex: 1, - }), - advanced_settings__container: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - }), - advanced_settings__scrollview: Styles.createViewStyle({ - flexGrow: 1, - flexShrink: 1, - flexBasis: '100%', - }), - advanced_settings__content: Styles.createViewStyle({ - flexDirection: 'column', - flexGrow: 1, - flexShrink: 0, - flexBasis: 'auto', - overflow: 'visible', - }), - advanced_settings__ipv6: Styles.createViewStyle({ - backgroundColor: colors.blue, - flexDirection: 'row', - alignItems: 'center', - }), - advanced_settings__ipv6_accessory: Styles.createViewStyle({ - marginRight: 12, - }), - advanced_settings__cell: Styles.createViewStyle({ - cursor: 'default', - backgroundColor: colors.green, - flexDirection: 'row', - paddingTop: 14, - paddingBottom: 14, - paddingLeft: 24, - paddingRight: 24, - marginBottom: 1, - justifyContent: 'flex-start', - }), - advanced_settings__cell_hover: Styles.createViewStyle({ - backgroundColor: colors.blue80, - }), - advanced_settings__cell_selected_hover: Styles.createViewStyle({ - backgroundColor: colors.green, - }), - advanced_settings__cell_spacer: Styles.createViewStyle({ - height: 24, - }), - advanced_settings__cell_icon: Styles.createViewStyle({ - width: 24, - height: 24, - marginRight: 8, - flex: 0, - color: colors.white80, - }), - advanced_settings__cell_dimmed: Styles.createViewStyle({ - cursor: 'default', - paddingTop: 14, - paddingBottom: 14, - paddingLeft: 24, - paddingRight: 24, - marginBottom: 1, - backgroundColor: colors.blue40, - flexDirection: 'row', - justifyContent: 'flex-start', - }), - advanced_settings__cell_footer: Styles.createViewStyle({ - paddingTop: 8, - paddingRight: 24, - paddingBottom: 24, - paddingLeft: 24, - }), - advanced_settings__cell_label_container: Styles.createViewStyle({ - paddingTop: 14, - paddingRight: 12, - paddingBottom: 14, - paddingLeft: 24, - flexGrow: 1, - }), - - advanced_settings__section_title: Styles.createTextStyle({ - backgroundColor: colors.blue, - paddingTop: 14, - paddingBottom: 14, - paddingLeft: 24, - paddingRight: 24, - marginBottom: 1, - fontFamily: 'DINPro', - fontSize: 20, - fontWeight: '900', - lineHeight: 26, - color: colors.white, - }), - advanced_settings__cell_label: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 20, - fontWeight: '900', - lineHeight: 26, - letterSpacing: -0.2, - color: colors.white, - flex: 0, - }), - advanced_settings__cell_footer_label: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - lineHeight: 20, - letterSpacing: -0.2, - color: colors.white80, - }), -}; diff --git a/app/components/AppButton.js b/app/components/AppButton.js deleted file mode 100644 index dd69ab1dc3..0000000000 --- a/app/components/AppButton.js +++ /dev/null @@ -1,80 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Button, Text, Component } from 'reactxp'; -import styles from './AppButtonStyles'; -import blurStyles from './BlurAppButtonStyles'; -import Img from './Img'; - -export class Label extends Text {} - -type Props = { - children?: React.Node, - style?: Object, - disabled: boolean, -}; - -type State = { - hovered: boolean, -}; - -class BaseButton extends Component<Props, State> { - state = { hovered: false }; - - textStyle = () => styles.white; - iconStyle = () => styles.white; - backgroundStyle = () => (this.state.hovered ? styles.white80 : styles.white); - - onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null); - onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null); - render() { - const { children, style, ...otherProps } = this.props; - return ( - <Button - {...otherProps} - style={[styles.common, this.backgroundStyle(), style]} - onHoverStart={this.onHoverStart} - onHoverEnd={this.onHoverEnd}> - {React.Children.map(children, (node) => { - if (React.isValidElement(node)) { - let updatedProps = {}; - - if (node.type === Label) { - updatedProps = { style: [styles.label, this.textStyle()] }; - } - - if (node.type === Img) { - updatedProps = { tintColor: 'currentColor', style: [styles.icon, this.iconStyle()] }; - } - - return React.cloneElement(node, updatedProps); - } else { - return <Label style={[styles.label, this.textStyle()]}>{children}</Label>; - } - })} - </Button> - ); - } -} - -export class RedButton extends BaseButton { - backgroundStyle = () => (this.state.hovered ? styles.redHover : styles.red); -} - -export class GreenButton extends BaseButton { - backgroundStyle = () => (this.state.hovered ? styles.greenHover : styles.green); -} - -export class BlueButton extends BaseButton { - backgroundStyle = () => (this.state.hovered ? styles.blueHover : styles.blue); -} - -export class TransparentButton extends BaseButton { - backgroundStyle = () => - this.state.hovered ? blurStyles.transparentHover : blurStyles.transparent; -} - -export class RedTransparentButton extends BaseButton { - backgroundStyle = () => - this.state.hovered ? blurStyles.redTransparentHover : blurStyles.redTransparent; -} diff --git a/app/components/AppButtonStyles.js b/app/components/AppButtonStyles.js deleted file mode 100644 index a007d4cdf3..0000000000 --- a/app/components/AppButtonStyles.js +++ /dev/null @@ -1,63 +0,0 @@ -// @flow - -import { Styles } from 'reactxp'; -import { colors } from '../config'; - -export default { - red: Styles.createViewStyle({ - backgroundColor: colors.red, - }), - redHover: Styles.createViewStyle({ - backgroundColor: colors.red95, - }), - green: Styles.createViewStyle({ - backgroundColor: colors.green, - }), - greenHover: Styles.createViewStyle({ - backgroundColor: colors.green90, - }), - blue: Styles.createViewStyle({ - backgroundColor: colors.blue80, - }), - blueHover: Styles.createViewStyle({ - backgroundColor: colors.blue60, - }), - white80: Styles.createViewStyle({ - color: colors.white80, - }), - white: Styles.createViewStyle({ - color: colors.white, - }), - icon: Styles.createViewStyle({ - position: 'absolute', - alignSelf: 'flex-end', - right: 8, - marginLeft: 8, - }), - iconTransparent: Styles.createViewStyle({ - position: 'absolute', - alignSelf: 'flex-end', - right: 42, - }), - common: Styles.createViewStyle({ - cursor: 'default', - paddingTop: 9, - paddingLeft: 9, - paddingRight: 9, - paddingBottom: 9, - borderRadius: 4, - flex: 1, - flexDirection: 'column', - alignContent: 'center', - justifyContent: 'center', - }), - - label: Styles.createTextStyle({ - alignSelf: 'center', - fontFamily: 'DINPro', - fontSize: 20, - fontWeight: '900', - lineHeight: 26, - flex: 1, - }), -}; diff --git a/app/components/BlurAppButtonStyles.android.js b/app/components/BlurAppButtonStyles.android.js deleted file mode 100644 index 5f185176c5..0000000000 --- a/app/components/BlurAppButtonStyles.android.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow - -import { Styles } from 'reactxp'; -import { colors } from '../../config'; - -export default { - transparent: Styles.createViewStyle({ - backgroundColor: colors.white20, - }), - transparentHover: Styles.createViewStyle({ - backgroundColor: colors.white40, - }), - redTransparent: Styles.createViewStyle({ - backgroundColor: colors.red40, - }), - redTransparentHover: Styles.createViewStyle({ - backgroundColor: colors.red45, - }), -}; diff --git a/app/components/BlurAppButtonStyles.js b/app/components/BlurAppButtonStyles.js deleted file mode 100644 index 45146a2e78..0000000000 --- a/app/components/BlurAppButtonStyles.js +++ /dev/null @@ -1,23 +0,0 @@ -// @flow - -import { Styles } from 'reactxp'; -import { colors } from '../config'; - -export default { - transparent: Styles.createViewStyle({ - backgroundColor: colors.white20, - backdropFilter: 'blur(4px)', - }), - transparentHover: Styles.createViewStyle({ - backgroundColor: colors.white40, - backdropFilter: 'blur(4px)', - }), - redTransparent: Styles.createViewStyle({ - backgroundColor: colors.red40, - backdropFilter: 'blur(4px)', - }), - redTransparentHover: Styles.createViewStyle({ - backgroundColor: colors.red45, - backdropFilter: 'blur(4px)', - }), -}; diff --git a/app/components/Cell.js b/app/components/Cell.js deleted file mode 100644 index 0fd4de40e6..0000000000 --- a/app/components/Cell.js +++ /dev/null @@ -1,117 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Button, Text, Component, Styles, Types } from 'reactxp'; -import Img from './Img'; -import { colors } from '../config'; - -const styles = { - cell: Styles.createViewStyle({ - backgroundColor: colors.blue, - paddingTop: 14, - paddingBottom: 14, - paddingLeft: 16, - paddingRight: 16, - marginBottom: 1, - flex: 1, - flexDirection: 'row', - alignItems: 'center', - alignContent: 'center', - cursor: 'default', - }), - cellHover: Styles.createViewStyle({ - backgroundColor: colors.blue80, - }), - icon: Styles.createViewStyle({ - color: colors.white60, - marginLeft: 8, - }), - - label: Styles.createTextStyle({ - color: colors.white, - alignSelf: 'center', - fontFamily: 'DINPro', - fontSize: 20, - fontWeight: '900', - lineHeight: 26, - flex: 1, - marginLeft: 8, - }), - subtext: Styles.createTextStyle({ - color: colors.white60, - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '800', - flex: 0, - textAlign: 'right', - }), -}; - -export class SubText extends Text {} -export class Label extends Text {} - -type CellButtonProps = { - children?: React.Node, - disabled?: boolean, - cellHoverStyle?: Types.ViewStyle, - style?: Types.ViewStyle, -}; - -type State = { hovered: boolean }; - -export class CellButton extends Component<CellButtonProps, State> { - state = { hovered: false }; - - textStyle = (cellHoverStyle?: Types.ViewStyle) => (this.state.hovered ? cellHoverStyle : null); - iconStyle = (cellHoverStyle?: Types.ViewStyle) => (this.state.hovered ? cellHoverStyle : null); - subtextStyle = (cellHoverStyle?: Types.ViewStyle) => (this.state.hovered ? cellHoverStyle : null); - backgroundStyle = (cellHoverStyle?: Types.ViewStyle) => - this.state.hovered ? cellHoverStyle || styles.cellHover : null; - - onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null); - onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null); - - render() { - const { children, style, cellHoverStyle, ...otherProps } = this.props; - return ( - <Button - style={[styles.cell, style, this.backgroundStyle(cellHoverStyle)]} - onHoverStart={this.onHoverStart} - onHoverEnd={this.onHoverEnd} - {...otherProps}> - {React.Children.map(children, (node) => { - if (React.isValidElement(node)) { - let updatedProps = {}; - - if (node.type === Label) { - updatedProps = { - style: [styles.label, node.props.style, this.textStyle(node.props.cellHoverStyle)], - }; - } - - if (node.type === Img) { - updatedProps = { - tintColor: 'currentColor', - style: [styles.icon, node.props.style, this.iconStyle(node.props.cellHoverStyle)], - }; - } - - if (node.type === SubText) { - updatedProps = { - style: [ - styles.subtext, - node.props.style, - this.subtextStyle(node.props.cellHoverStyle), - ], - }; - } - - return React.cloneElement(node, updatedProps); - } else if (node) { - return <Label style={[styles.label, this.textStyle()]}>{children}</Label>; - } - })} - </Button> - ); - } -} diff --git a/app/components/Connect.js b/app/components/Connect.js deleted file mode 100644 index 9d7f52f7b1..0000000000 --- a/app/components/Connect.js +++ /dev/null @@ -1,399 +0,0 @@ -// @flow - -import moment from 'moment'; -import * as React from 'react'; -import { Layout, Container, Header } from './Layout'; -import { SettingsBarButton, Brand } from './HeaderBar'; -import { Component, Text, View, Types } from 'reactxp'; -import * as AppButton from './AppButton'; -import Img from './Img'; -import Accordion from './Accordion'; -import styles from './ConnectStyles'; -import { NoCreditError, NoInternetError } from '../errors'; -import Map from './Map'; -import WindowStateObserver from '../lib/window-state-observer'; - -import type { HeaderBarStyle } from './HeaderBar'; -import type { ConnectionReduxState } from '../redux/connection/reducers'; - -type Props = { - connection: ConnectionReduxState, - accountExpiry: string, - selectedRelayName: string, - onSettings: () => void, - onSelectLocation: () => void, - onConnect: () => void, - onCopyIP: () => void, - onDisconnect: () => void, - onExternalLink: (type: string) => void, - updateAccountExpiry: () => Promise<void>, -}; - -type State = { - showCopyIPMessage: boolean, - mapOffset: [number, number], -}; - -export default class Connect extends Component<Props, State> { - state = { - showCopyIPMessage: false, - mapOffset: [0, 0], - }; - - _copyTimer: ?TimeoutID; - _windowStateObserver = new WindowStateObserver(); - - shouldComponentUpdate(nextProps: Props, nextState: State) { - const { connection: prevConnection, ...otherPrevProps } = this.props; - const { connection: nextConnection, ...otherNextProps } = nextProps; - - const prevState = this.state; - - return ( - // shallow compare the connection - !shallowCompare(prevConnection, nextConnection) || - !shallowCompare(otherPrevProps, otherNextProps) || - prevState.mapOffset[0] !== nextState.mapOffset[0] || - prevState.mapOffset[1] !== nextState.mapOffset[1] || - prevState.showCopyIPMessage !== nextState.showCopyIPMessage - ); - } - - componentDidMount() { - this.props.updateAccountExpiry(); - - this._windowStateObserver.onShow = () => { - this.props.updateAccountExpiry(); - }; - } - - componentWillUnmount() { - if (this._copyTimer) { - clearTimeout(this._copyTimer); - } - - this._windowStateObserver.dispose(); - } - - render() { - const error = this.checkForErrors(); - const child = error ? this.renderError(error) : this.renderMap(); - - return ( - <Layout> - <Header barStyle={this.headerBarStyle()} testName="header"> - <Brand /> - <SettingsBarButton onPress={this.props.onSettings} /> - </Header> - <Container>{child}</Container> - </Layout> - ); - } - - renderError(error: Error) { - let title = ''; - let message = ''; - - if (error instanceof NoCreditError) { - title = 'Out of time'; - message = 'Buy more time, so you can continue using the internet securely'; - } - - if (error instanceof NoInternetError) { - title = 'Offline'; - message = 'Your internet connection will be secured when you get back online'; - } - - return ( - <View style={styles.connect}> - <View style={styles.status_icon}> - <Img source="icon-fail" height={60} width={60} alt="" /> - </View> - <View style={styles.status}> - <View style={styles.error_title}>{title}</View> - <View style={styles.error_message}>{message}</View> - {error instanceof NoCreditError ? ( - <View> - <AppButton.GreenButton onPress={this.onExternalLink.bind(this, 'purchase')}> - <AppButton.Label>Buy more time</AppButton.Label> - <Img source="icon-extLink" height={16} width={16} /> - </AppButton.GreenButton> - </View> - ) : null} - </View> - </View> - ); - } - - _getMapProps() { - const { longitude, latitude, status } = this.props.connection; - - // when the user location is known - if (typeof longitude === 'number' && typeof latitude === 'number') { - return { - center: [longitude, latitude], - // do not show the marker when connecting - showMarker: status !== 'connecting', - markerStyle: status === 'connected' ? 'secure' : 'unsecure', - // zoom in when connected - zoomLevel: status === 'connected' ? 'low' : 'medium', - // a magic offset to align marker with spinner - offset: [0, 123], - }; - } else { - return { - center: [0, 0], - showMarker: false, - markerStyle: 'unsecure', - // show the world when user location is not known - zoomLevel: 'high', - // remove the offset since the marker is hidden - offset: [0, 0], - }; - } - } - - _updateMapOffset = (spinnerNode: ?HTMLElement) => { - if (spinnerNode) { - // calculate the vertical offset from the center of the map - // to shift the center of the map upwards to align the centers - // of spinner and marker on the map - const y = spinnerNode.offsetTop + spinnerNode.clientHeight * 0.5; - this.setState({ - mapOffset: [0, y], - }); - } - }; - - renderMap() { - let [isConnecting, isConnected, isDisconnected] = [false, false, false]; - switch (this.props.connection.status) { - case 'connecting': - isConnecting = true; - break; - case 'connected': - isConnected = true; - break; - case 'disconnected': - isDisconnected = true; - break; - } - - return ( - <View style={styles.connect}> - <View style={styles.map}> - <Map style={{ width: '100%', height: '100%' }} {...this._getMapProps()} /> - </View> - <View style={styles.container}> - {this._renderIsBlockingInternetMessage()} - - {/* show spinner when connecting */} - {isConnecting ? ( - <View style={styles.status_icon}> - <Img - source="icon-spinner" - height={60} - width={60} - alt="" - ref={this._updateMapOffset} - /> - </View> - ) : null} - - <View style={styles.status}> - <View style={this.networkSecurityStyle()} testName="networkSecurityMessage"> - {this.networkSecurityMessage()} - </View> - - {/* - ********************************** - Begin: Location block - ********************************** - */} - - {/* location when connecting or disconnected */} - {isConnecting || isDisconnected ? ( - <Text style={styles.status_location} testName="location"> - {this.props.connection.country} - </Text> - ) : null} - - {/* location when connected */} - {isConnected ? ( - <Text style={styles.status_location} testName="location"> - {this.props.connection.city} - {this.props.connection.city && <br />} - {this.props.connection.country} - </Text> - ) : null} - - {/* - ********************************** - End: Location block - ********************************** - */} - - <Text style={this.ipAddressStyle()} onPress={this.onIPAddressClick.bind(this)}> - {isConnected || isDisconnected ? ( - <Text testName="ipAddress"> - {this.state.showCopyIPMessage - ? 'IP copied to clipboard!' - : this.props.connection.ip} - </Text> - ) : null} - </Text> - </View> - - {/* - ********************************** - Begin: Footer block - ********************************** - */} - - {/* footer when disconnected */} - {isDisconnected ? ( - <View style={styles.footer}> - <AppButton.TransparentButton - style={styles.switch_location_button} - onPress={this.props.onSelectLocation}> - <AppButton.Label>{this.props.selectedRelayName}</AppButton.Label> - <Img height={12} width={7} source="icon-chevron" /> - </AppButton.TransparentButton> - <AppButton.GreenButton onPress={this.props.onConnect} testName="secureConnection"> - {'Secure my connection'} - </AppButton.GreenButton> - </View> - ) : null} - - {/* footer when connecting */} - {isConnecting ? ( - <View style={styles.footer}> - <AppButton.TransparentButton - style={styles.switch_location_button} - onPress={this.props.onSelectLocation}> - {'Switch location'} - </AppButton.TransparentButton> - <AppButton.RedTransparentButton onPress={this.props.onDisconnect}> - {'Cancel'} - </AppButton.RedTransparentButton> - </View> - ) : null} - - {/* footer when connected */} - {isConnected ? ( - <View style={styles.footer}> - <AppButton.TransparentButton - style={styles.switch_location_button} - onPress={this.props.onSelectLocation}> - {'Switch location'} - </AppButton.TransparentButton> - <AppButton.RedTransparentButton - onPress={this.props.onDisconnect} - testName="disconnect"> - {'Disconnect'} - </AppButton.RedTransparentButton> - </View> - ) : null} - - {/* - ********************************** - End: Footer block - ********************************** - */} - </View> - </View> - ); - } - - _renderIsBlockingInternetMessage() { - return ( - <Accordion - style={styles.blocking_container} - height={this.props.connection.status === 'connecting' ? 'auto' : 0}> - <Text style={styles.blocking_message}> - <Text style={styles.blocking_icon}> </Text> - <Text>BLOCKING INTERNET</Text> - </Text> - </Accordion> - ); - } - - // Handlers - - onExternalLink(type: string) { - this.props.onExternalLink(type); - } - - onIPAddressClick() { - if (this._copyTimer) { - clearTimeout(this._copyTimer); - } - this._copyTimer = setTimeout(() => this.setState({ showCopyIPMessage: false }), 3000); - this.setState({ showCopyIPMessage: true }); - this.props.onCopyIP(); - } - - // Private - - headerBarStyle(): HeaderBarStyle { - const { status } = this.props.connection; - switch (status) { - case 'disconnected': - return 'error'; - case 'connecting': - case 'connected': - return 'success'; - default: - throw new Error(`Invalid ConnectionState: ${(status: empty)}`); - } - } - - networkSecurityStyle(): Types.Style { - const classes = [styles.status_security]; - if (this.props.connection.status === 'connected') { - classes.push(styles.status_security__secure); - } else if (this.props.connection.status === 'disconnected') { - classes.push(styles.status_security__unsecured); - } - return classes; - } - - networkSecurityMessage(): string { - switch (this.props.connection.status) { - case 'connected': - return 'SECURE CONNECTION'; - case 'connecting': - return 'CREATING SECURE CONNECTION'; - default: - return 'UNSECURED CONNECTION'; - } - } - - ipAddressStyle(): Types.Style { - var classes = [styles.status_ipaddress]; - if (this.props.connection.status === 'connecting') { - classes.push(styles.status_ipaddress__invisible); - } - return classes; - } - - checkForErrors(): ?Error { - // Offline? - if (!this.props.connection.isOnline) { - return new NoInternetError(); - } - - // No credit? - const expiry = this.props.accountExpiry; - if (expiry && moment(expiry).isSameOrBefore(moment())) { - return new NoCreditError(); - } - - return null; - } -} - -function shallowCompare(lhs: Object, rhs: Object) { - const keys = Object.keys(lhs); - return keys.length === Object.keys(rhs).length && keys.every((key) => lhs[key] === rhs[key]); -} diff --git a/app/components/ConnectStyles.js b/app/components/ConnectStyles.js deleted file mode 100644 index c6f89a49b6..0000000000 --- a/app/components/ConnectStyles.js +++ /dev/null @@ -1,137 +0,0 @@ -// @flow - -import { Styles } from 'reactxp'; -import { colors } from '../config'; - -export default { - connect: Styles.createViewStyle({ - flex: 1, - }), - map: Styles.createViewStyle({ - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - zIndex: 0, - height: '100%', - width: '100%', - }), - container: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - position: 'relative' /* need this for z-index to work to cover map */, - zIndex: 1, - }), - footer: Styles.createViewStyle({ - flex: 0, - paddingBottom: 16, - paddingLeft: 24, - paddingRight: 24, - }), - blocking_container: Styles.createViewStyle({ - width: '100%', - position: 'absolute', - }), - blocking_icon: Styles.createViewStyle({ - width: 10, - height: 10, - flex: 0, - display: 'flex', - borderRadius: 5, - marginTop: 4, - marginRight: 8, - backgroundColor: colors.red, - }), - status: Styles.createViewStyle({ - paddingTop: 0, - paddingLeft: 24, - paddingRight: 24, - paddingBottom: 0, - marginTop: 186, - flex: 1, - }), - status_icon: Styles.createViewStyle({ - position: 'absolute', - alignSelf: 'center', - width: 60, - height: 60, - marginTop: 94, - }), - switch_location_button: Styles.createViewStyle({ - marginBottom: 16, - }), - - blocking_message: Styles.createTextStyle({ - display: 'flex', - flexDirection: 'row', - fontFamily: 'Open Sans', - fontSize: 12, - fontWeight: '800', - lineHeight: 17, - paddingTop: 8, - paddingLeft: 20, - paddingRight: 20, - paddingBottom: 8, - color: colors.white60, - backgroundColor: colors.blue, - }), - server_label: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 32, - fontWeight: '900', - lineHeight: 44, - letterSpacing: -0.7, - color: colors.white, - marginBottom: 7, - flex: 0, - }), - error_title: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 32, - fontWeight: '900', - lineHeight: 40, - color: colors.white, - marginBottom: 8, - }), - error_message: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - color: colors.white, - marginBottom: 24, - }), - status_security: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 16, - fontWeight: '800', - lineHeight: 22, - marginBottom: 4, - color: colors.white, - }), - status_security__secure: Styles.createTextStyle({ - color: colors.green, - }), - status_security__unsecured: Styles.createTextStyle({ - color: colors.red, - }), - status_ipaddress: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 16, - fontWeight: '800', - color: colors.white, - }), - status_ipaddress__invisible: Styles.createTextStyle({ - opacity: 0, - }), - status_location: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 38, - fontWeight: '900', - lineHeight: 40, - overflow: 'hidden', - letterSpacing: -0.9, - color: colors.white, - marginBottom: 4, - }), -}; diff --git a/app/components/CustomScrollbars.android.js b/app/components/CustomScrollbars.android.js deleted file mode 100644 index dadc462234..0000000000 --- a/app/components/CustomScrollbars.android.js +++ /dev/null @@ -1,33 +0,0 @@ -// @flow - -import * as React from 'react'; -import { View, Component } from 'reactxp'; - -type Props = { - autoHide: boolean, - thumbInset: { x: number, y: number }, - children?: React.Node, -}; - -type State = { - canScroll: boolean, - showScrollIndicators: boolean, -}; - -export default class CustomScrollbars extends Component<Props, State> { - static defaultProps = { - autoHide: true, - thumbInset: { x: 2, y: 2 }, - }; - - state = { - canScroll: false, - showScrollIndicators: true, - }; - - render() { - const { autoHide: _autoHide, thumbInset: _thumbInset, children, ...otherProps } = this.props; - - return <View {...otherProps}>{children}</View>; - } -} diff --git a/app/components/CustomScrollbars.css b/app/components/CustomScrollbars.css deleted file mode 100644 index 4604b1e413..0000000000 --- a/app/components/CustomScrollbars.css +++ /dev/null @@ -1,30 +0,0 @@ -.custom-scrollbars { - display: flex; - flex-direction: column; - position: relative; -} - -.custom-scrollbars__scrollable { - width: 100%; - height: 100%; -} - -.custom-scrollbars__scrollable::-webkit-scrollbar { - display: none; -} - -.custom-scrollbars__thumb { - background-color: rgba(255, 255, 255, 0.2); - border-radius: 4px; - width: 8px; - transition: height 0.25s ease-in-out, opacity 0.25s ease-in-out; - pointer-events: none; - opacity: 0; - z-index: 99; -} - -.custom-scrollbars__thumb--visible { - /* thumb appears without animation */ - transition: height 0.25s ease-in-out; - opacity: 1; -} diff --git a/app/components/CustomScrollbars.js b/app/components/CustomScrollbars.js deleted file mode 100644 index a149faff75..0000000000 --- a/app/components/CustomScrollbars.js +++ /dev/null @@ -1,259 +0,0 @@ -// @flow - -import * as React from 'react'; - -type ScrollbarUpdateContext = { - size: boolean, - position: boolean, -}; - -const AUTOHIDE_TIMEOUT = 1000; - -type Props = { - autoHide: boolean, - thumbInset: { x: number, y: number }, - children?: React.Node, -}; - -type State = { - canScroll: boolean, - showScrollIndicators: boolean, -}; - -type ScrollPosition = 'top' | 'bottom' | 'middle'; - -export default class CustomScrollbars extends React.Component<Props, State> { - static defaultProps = { - autoHide: true, - thumbInset: { x: 2, y: 2 }, - }; - - state = { - canScroll: false, - showScrollIndicators: true, - }; - - _scrollableElement: ?HTMLElement; - _thumbElement: ?HTMLElement; - _autoHideTimer: ?TimeoutID; - - scrollTo(x: number, y: number) { - const scrollable = this._scrollableElement; - if (scrollable) { - scrollable.scrollLeft = x; - scrollable.scrollTop = y; - } - } - - scrollToElement(child: HTMLElement, scrollPosition: ScrollPosition) { - const scrollable = this._scrollableElement; - if (scrollable) { - // throw if child is not a descendant of scroll view - if (!scrollable.contains(child)) { - throw new Error( - 'Cannot scroll to an element which is not a descendant of CustomScrollbars.', - ); - } - - const scrollTop = this._computeScrollTop(scrollable, child, scrollPosition); - this.scrollTo(0, scrollTop); - } - } - - componentDidMount() { - this._updateScrollbarsHelper({ - position: true, - size: true, - }); - - // show scroll indicators briefly when mounted - if (this.props.autoHide) { - this._startAutoHide(); - } - } - - componentWillUnmount() { - this._stopAutoHide(); - } - - componentDidUpdate() { - this._updateScrollbarsHelper({ - position: true, - size: true, - }); - } - - render() { - const { autoHide: _autoHide, thumbInset: _thumbInset, children, ...otherProps } = this.props; - const showScrollbars = this.state.canScroll && this.state.showScrollIndicators; - const thumbAnimationClass = showScrollbars ? ' custom-scrollbars__thumb--visible' : ''; - return ( - <div {...otherProps} className="custom-scrollbars"> - <div - className={`custom-scrollbars__thumb ${thumbAnimationClass}`} - style={{ position: 'absolute', top: 0, right: 0 }} - ref={this._onThumbRef} - /> - <div - className="custom-scrollbars__scrollable" - style={{ overflow: 'auto' }} - onScroll={this._onScroll} - ref={this._onScrollableRef}> - {children} - </div> - </div> - ); - } - - _onScrollableRef = (ref) => { - this._scrollableElement = ref; - }; - - _onThumbRef = (ref) => { - this._thumbElement = ref; - }; - - _onScroll = () => { - this._updateScrollbarsHelper({ position: true }); - - if (this.props.autoHide) { - this._startAutoHide(); - } - }; - - _startAutoHide() { - if (this._autoHideTimer) { - clearTimeout(this._autoHideTimer); - } - - this._autoHideTimer = setTimeout(() => { - this.setState({ - showScrollIndicators: false, - }); - }, AUTOHIDE_TIMEOUT); - - if (!this.state.showScrollIndicators) { - this.setState({ - showScrollIndicators: true, - }); - } - } - - _stopAutoHide() { - if (this._autoHideTimer) { - clearTimeout(this._autoHideTimer); - this._autoHideTimer = null; - } - } - - // Computes the position of child element within scrollable container - _computeOffsetTop(scrollable: HTMLElement, child: HTMLElement) { - let offsetTop = 0; - let node = child; - - while (node && scrollable.contains(node)) { - offsetTop += node.offsetTop; - - // Flow bug in offsetParent definition: - // https://github.com/facebook/flow/issues/4407 - node = ((node.offsetParent: any): HTMLElement); - } - - return offsetTop; - } - - _computeScrollTop(scrollable: HTMLElement, child: HTMLElement, scrollPosition: ScrollPosition) { - const offsetTop = this._computeOffsetTop(scrollable, child); - - switch (scrollPosition) { - case 'top': - return offsetTop; - - case 'bottom': - return offsetTop - (scrollable.offsetHeight - child.clientHeight); - - case 'middle': - return offsetTop - (scrollable.offsetHeight - child.clientHeight) * 0.5; - - default: - throw new Error(`Unknown enum type for ScrollPosition: ${(scrollPosition: empty)}`); - } - } - - _computeThumbPosition(scrollable: HTMLElement, thumb: HTMLElement) { - // the content height of the scroll view - const scrollHeight = scrollable.scrollHeight; - - // the visible height of the scroll view - const visibleHeight = scrollable.offsetHeight; - - // scroll offset - const scrollTop = scrollable.scrollTop; - - // lowest point of scrollTop - const maxScrollTop = scrollHeight - visibleHeight; - - // calculate scroll position within 0..1 range - const scrollPosition = scrollHeight > 0 ? scrollTop / maxScrollTop : 0; - - const thumbHeight = thumb.clientHeight; - - // calculate the thumb boundary to make sure that the visual appearance of - // a thumb at lowest point matches the bottom of scrollable view - const thumbBoundary = visibleHeight - thumbHeight - this.props.thumbInset.y * 2; - - // calculate thumb position based on scroll progress and thumb boundary - // adding vertical inset to adjust the thumb's appearance - const thumbPosition = thumbBoundary * scrollPosition + this.props.thumbInset.y; - - return { - x: -this.props.thumbInset.x, - y: thumbPosition, - }; - } - - _computeThumbHeight(scrollable: HTMLElement) { - const scrollHeight = scrollable.scrollHeight; - const visibleHeight = scrollable.offsetHeight; - - const thumbHeight = (visibleHeight / scrollHeight) * visibleHeight; - - // ensure that the scroll thumb doesn't shrink to nano size - return Math.max(thumbHeight, 8); - } - - _updateScrollbarsHelper(updateFlags: $Shape<ScrollbarUpdateContext>) { - const scrollable = this._scrollableElement; - const thumb = this._thumbElement; - if (scrollable && thumb) { - this._updateScrollbars(scrollable, thumb, updateFlags); - } - } - - _updateScrollbars( - scrollable: HTMLElement, - thumb: HTMLElement, - context: $Shape<ScrollbarUpdateContext>, - ) { - if (context.size) { - const thumbHeight = this._computeThumbHeight(scrollable); - thumb.style.setProperty('height', thumbHeight + 'px'); - - // hide thumb when there is nothing to scroll - const canScroll = thumbHeight < scrollable.offsetHeight; - if (this.state.canScroll !== canScroll) { - this.setState({ canScroll }); - - // flash the scroll indicators when the view becomes scrollable - if (this.props.autoHide && canScroll) { - this._startAutoHide(); - } - } - } - - if (context.position) { - const { x, y } = this._computeThumbPosition(scrollable, thumb); - thumb.style.setProperty('transform', `translate(${x}px, ${y}px)`); - } - } -} diff --git a/app/components/HeaderBar.js b/app/components/HeaderBar.js deleted file mode 100644 index 8a82ba7843..0000000000 --- a/app/components/HeaderBar.js +++ /dev/null @@ -1,149 +0,0 @@ -// @flow - -import React from 'react'; -import { Component, Text, Button, View, Styles } from 'reactxp'; -import Img from './Img'; -import { colors } from '../config'; - -export type HeaderBarStyle = 'default' | 'defaultDark' | 'error' | 'success'; -type HeaderBarProps = { - barStyle: HeaderBarStyle, -}; - -const headerBarStyles = { - container: { - base: Styles.createViewStyle({ - paddingTop: 12, - paddingBottom: 12, - paddingLeft: 12, - paddingRight: 12, - }), - platformOverride: { - darwin: Styles.createViewStyle({ - paddingTop: 24, - }), - linux: Styles.createViewStyle({ - WebkitAppRegion: 'drag', - }), - }, - }, - content: Styles.createViewStyle({ - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'flex-end', - // the size of "brand" logo - minHeight: 51, - }), - barStyle: { - default: Styles.createViewStyle({ - backgroundColor: colors.blue, - }), - defaultDark: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - }), - error: Styles.createViewStyle({ - backgroundColor: colors.red, - }), - success: Styles.createViewStyle({ - backgroundColor: colors.green, - }), - }, -}; - -export default class HeaderBar extends Component<HeaderBarProps> { - static defaultProps: HeaderBarProps = { - barStyle: 'default', - }; - - render() { - const style = [ - headerBarStyles.container.base, - headerBarStyles.container.platformOverride[process.platform], - headerBarStyles.barStyle[this.props.barStyle], - this.props.style, - ]; - - return ( - <View style={style}> - <View style={headerBarStyles.content}>{this.props.children}</View> - </View> - ); - } -} - -const brandStyles = { - container: Styles.createViewStyle({ - flex: 1, - flexDirection: 'row', - alignItems: 'center', - }), - title: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 24, - fontWeight: '900', - lineHeight: 30, - letterSpacing: -0.5, - color: colors.white60, - marginLeft: 8, - }), -}; - -export class Brand extends Component { - render() { - return ( - <View style={brandStyles.container} testName="headerbar__container"> - <Img width={50} height={50} source="logo-icon" /> - <Text style={brandStyles.title}>{'MULLVAD VPN'}</Text> - </View> - ); - } -} - -type SettingsButtonProps = { - onPress: ?() => void, -}; - -const settingsBarButtonStyles = { - container: { - base: Styles.createViewStyle({ - cursor: 'default', - padding: 0, - marginLeft: 8, - }), - platformOverride: { - linux: Styles.createViewStyle({ - WebkitAppRegion: 'no-drag', - }), - }, - }, - icon: { - normal: Styles.createViewStyle({ - color: colors.white60, - }), - hover: Styles.createViewStyle({ - color: colors.white, - }), - }, -}; - -export class SettingsBarButton extends Component<SettingsButtonProps> { - render() { - return ( - <Button - style={[ - settingsBarButtonStyles.container.base, - settingsBarButtonStyles.container.platformOverride[process.platform], - ]} - onPress={this.props.onPress} - testName="headerbar__settings"> - <Img - height={24} - width={24} - source="icon-settings" - style={settingsBarButtonStyles.icon.normal} - hoverStyle={settingsBarButtonStyles.icon.hover} - /> - </Button> - ); - } -} diff --git a/app/components/Img.android.js b/app/components/Img.android.js deleted file mode 100644 index 778562fe3d..0000000000 --- a/app/components/Img.android.js +++ /dev/null @@ -1,45 +0,0 @@ -// @flow - -import React, { Component } from 'react'; -import { StyleSheet } from 'react-native'; -import { Image, Styles } from 'reactxp'; - -export default class Img extends Component { - props: { - source: string, - style: Object, - tintColor?: string, - height?: number, - width?: number, - }; - - render() { - const width = this.props.width || 7; - const height = this.props.height || 12; - const source = this.props.source || 'icon-chevron'; - const tintColor = this.props.tintColor || 'currentColor'; - - if (tintColor === 'currentColor' && this.props.style) { - const { color: tint, ...otherStyles } = StyleSheet.flatten(this.props.style); - return ( - <Image - style={Styles.createViewStyle( - { ...otherStyles, tintColor: tint, height: height, width: width }, - false, - )} - source={source} - /> - ); - } else { - return ( - <Image - style={Styles.createViewStyle( - { ...this.props.style, height: height, width: width }, - false, - )} - source={source} - /> - ); - } - } -} diff --git a/app/components/Img.js b/app/components/Img.js deleted file mode 100644 index 6919eb0740..0000000000 --- a/app/components/Img.js +++ /dev/null @@ -1,64 +0,0 @@ -// @flow - -import * as React from 'react'; -import { View, Component, Types } from 'reactxp'; - -type Props = { - source: string, - width?: number, - heigth?: number, - tintColor?: string, - hoverStyle?: Types.ViewStyle, - disabled?: boolean, -}; - -type State = { hovered: boolean }; - -export default class Img extends Component<Props, State> { - state = { hovered: false }; - - onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null); - onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null); - - getHoverStyle = () => (this.state.hovered ? this.props.hoverStyle || null : null); - - render() { - const { source, width, heigth, style, onMouseEnter, onMouseLeave, ...otherProps } = this.props; - const tintColor = this.props.tintColor; - const url = './assets/images/' + source + '.svg'; - let image; - - if (tintColor) { - image = ( - <div - style={{ - WebkitMaskImage: `url('${url}')`, - WebkitMaskRepeat: 'no-repeat', - backgroundColor: tintColor, - lineHeight: 0, - }}> - <img - src={url} - width={width} - height={heigth} - style={{ - visibility: 'hidden', - }} - /> - </div> - ); - } else { - image = <img src={url} width={width} height={heigth} />; - } - - return ( - <View - {...otherProps} - onMouseEnter={onMouseEnter || this.onHoverStart} - onMouseLeave={onMouseLeave || this.onHoverEnd} - style={[style, this.getHoverStyle()]}> - {image} - </View> - ); - } -} diff --git a/app/components/Launch.js b/app/components/Launch.js deleted file mode 100644 index 37dd38d73f..0000000000 --- a/app/components/Launch.js +++ /dev/null @@ -1,59 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Component, Styles, View, Text } from 'reactxp'; -import { Layout, Container, Header } from './Layout'; -import { SettingsBarButton } from './HeaderBar'; -import Img from './Img'; -import { colors } from '../config'; - -const styles = { - container: Styles.createViewStyle({ - flex: 1, - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - marginTop: -150, - }), - logo: Styles.createViewStyle({ - marginBottom: 4, - }), - title: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 24, - fontWeight: '900', - lineHeight: 30, - letterSpacing: -0.5, - color: colors.white60, - marginBottom: 4, - }), - subtitle: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 14, - lineHeight: 20, - color: colors.white40, - }), -}; - -type Props = { - openSettings: () => void, -}; - -export default class Launch extends Component<Props> { - render() { - return ( - <Layout> - <Header> - <SettingsBarButton onPress={this.props.openSettings} /> - </Header> - <Container> - <View style={styles.container} testName="headerbar__container"> - <Img height={120} width={120} source="logo-icon" style={styles.logo} /> - <Text style={styles.title}>{'MULLVAD VPN'}</Text> - <Text style={styles.subtitle}>{'Connecting to daemon...'}</Text> - </View> - </Container> - </Layout> - ); - } -} diff --git a/app/components/Layout.js b/app/components/Layout.js deleted file mode 100644 index 42e96640c0..0000000000 --- a/app/components/Layout.js +++ /dev/null @@ -1,36 +0,0 @@ -// @flow - -import * as React from 'react'; -import { View, Component } from 'reactxp'; -import HeaderBar from './HeaderBar'; -import styles from './LayoutStyles'; - -export class Header extends Component<React.ElementProps<typeof HeaderBar>> { - static defaultProps = HeaderBar.defaultProps; - - render() { - return ( - <View style={[styles.header, this.props.style]}> - <HeaderBar barStyle={this.props.barStyle}>{this.props.children}</HeaderBar> - </View> - ); - } -} - -type ContainerProps = { - children: React.Node, -}; -export class Container extends Component<ContainerProps> { - render() { - return <View style={styles.container}>{this.props.children}</View>; - } -} - -type LayoutProps = { - children: Array<React.Node> | React.Node, -}; -export class Layout extends Component<LayoutProps> { - render() { - return <View style={styles.layout}>{this.props.children}</View>; - } -} diff --git a/app/components/LayoutStyles.js b/app/components/LayoutStyles.js deleted file mode 100644 index 7db2a0225b..0000000000 --- a/app/components/LayoutStyles.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow - -import { Styles } from 'reactxp'; -import { colors } from '../config'; - -export default { - layout: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - }), - header: Styles.createViewStyle({ - flex: 0, - }), - container: Styles.createViewStyle({ - flex: 1, - backgroundColor: colors.blue, - overflow: 'hidden', - }), -}; diff --git a/app/components/Login.js b/app/components/Login.js deleted file mode 100644 index 52fe8791b6..0000000000 --- a/app/components/Login.js +++ /dev/null @@ -1,467 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Component, Text, View, Animated, Styles, UserInterface } from 'reactxp'; -import { Layout, Container, Header } from './Layout'; -import { SettingsBarButton, Brand } from './HeaderBar'; -import AccountInput from './AccountInput'; -import Accordion from './Accordion'; -import { formatAccount } from '../lib/formatters'; -import Img from './Img'; -import * as Cell from './Cell'; -import * as AppButton from './AppButton'; -import styles from './LoginStyles'; -import { colors } from '../config'; - -import type { LoginState } from '../redux/account/reducers'; -import type { AccountToken } from '../lib/daemon-rpc'; - -export type Props = { - accountToken: ?AccountToken, - accountHistory: Array<AccountToken>, - loginError: ?Error, - loginState: LoginState, - openSettings: ?() => void, - openExternalLink: (type: string) => void, - login: (accountToken: AccountToken) => void, - resetLoginError: () => void, - updateAccountToken: (accountToken: AccountToken) => void, - removeAccountTokenFromHistory: (accountToken: AccountToken) => Promise<void>, -}; - -type State = { - isActive: boolean, -}; - -const MIN_ACCOUNT_TOKEN_LENGTH = 10; - -export default class Login extends Component<Props, State> { - state = { - isActive: true, - }; - - _accountInput: ?AccountInput; - _shouldResetLoginError = false; - - _showsFooter = true; - _footerAnimatedValue = Animated.createValue(0); - _footerAnimation: ?Animated.Animation; - _footerAnimationStyle: Animated.Style; - _footerRef: ?React.Node; - - _isLoginButtonActive = false; - _loginButtonAnimatedValue = Animated.createValue(0); - _loginButtonAnimation: ?Animated.Animation; - _loginButtonAnimationStyle: Animated.Style; - - constructor(props: Props) { - super(props); - - if (props.loginState === 'failed') { - this._shouldResetLoginError = true; - } - - this._footerAnimationStyle = Styles.createAnimatedViewStyle({ - transform: [{ translateY: this._footerAnimatedValue }], - }); - - this._loginButtonAnimationStyle = Styles.createAnimatedViewStyle({ - backgroundColor: Animated.interpolate( - this._loginButtonAnimatedValue, - [0.0, 1.0], - [colors.white, colors.green], - ), - }); - } - - componentDidUpdate(prevProps: Props, _prevState: State) { - if ( - this.props.loginState !== prevProps.loginState && - this.props.loginState === 'failed' && - !this._shouldResetLoginError - ) { - this._shouldResetLoginError = true; - - // focus on login field when failed to log in - const accountInput = this._accountInput; - if (accountInput) { - accountInput.focus(); - } - } - - this._setLoginButtonActive(this._shouldActivateLoginButton()); - this._setFooterVisibility(this._shouldShowFooter()); - } - - render() { - return ( - <Layout> - <Header> - <Brand /> - <SettingsBarButton onPress={this.props.openSettings} /> - </Header> - <Container> - <View style={styles.login_form}> - {this._getStatusIcon()} - <Text style={styles.title}>{this._formTitle()}</Text> - - {this._shouldShowLoginForm() && <View>{this._createLoginForm()}</View>} - </View> - - <Animated.View - ref={(ref) => { - this._footerRef = ref; - }} - style={[styles.login_footer, this._footerAnimationStyle]} - testName={'footerVisibility ' + this._shouldShowFooter().toString()}> - {this._createFooter()} - </Animated.View> - </Container> - </Layout> - ); - } - - _onCreateAccount = () => this.props.openExternalLink('createAccount'); - - _onFocus = () => { - this.setState({ isActive: true }); - }; - - _onBlur = (e) => { - const relatedTarget = e.relatedTarget; - - // restore focus if click happened within dropdown - if (relatedTarget) { - e.target.focus(); - return; - } - - this.setState({ isActive: false }); - }; - - async _setLoginButtonActive(isActive: boolean) { - if (this._isLoginButtonActive === isActive) { - return; - } - - const animation = Animated.timing(this._loginButtonAnimatedValue, { - toValue: isActive ? 1 : 0, - easing: Animated.Easing.Linear(), - duration: 250, - }); - - const oldAnimation = this._loginButtonAnimation; - if (oldAnimation) { - oldAnimation.stop(); - } - - animation.start(); - - this._loginButtonAnimation = animation; - this._isLoginButtonActive = isActive; - } - - async _setFooterVisibility(show: boolean) { - if (this._showsFooter === show) { - return; - } - - this._showsFooter = show; - - const layout = await UserInterface.measureLayoutRelativeToWindow(this._footerRef); - const value = show ? 0 : layout.height; - - const animation = Animated.timing(this._footerAnimatedValue, { - toValue: value, - easing: Animated.Easing.InOut(), - duration: 250, - }); - - const oldAnimation = this._footerAnimation; - if (oldAnimation) { - oldAnimation.stop(); - } - - animation.start(); - - this._footerAnimation = animation; - } - - _onLogin = () => { - const accountToken = this.props.accountToken; - if (accountToken && accountToken.length >= MIN_ACCOUNT_TOKEN_LENGTH) { - this.props.login(accountToken); - } - }; - - _onInputChange = (value: string) => { - // reset error when user types in the new account number - if (this._shouldResetLoginError) { - this._shouldResetLoginError = false; - this.props.resetLoginError(); - } - - this.props.updateAccountToken(value); - }; - - _formTitle() { - switch (this.props.loginState) { - case 'logging in': - return 'Logging in...'; - case 'failed': - return 'Login failed'; - case 'ok': - return 'Login successful'; - default: - return 'Login'; - } - } - - _formSubtitle() { - const { loginState, loginError } = this.props; - switch (loginState) { - case 'failed': - return (loginError && loginError.message) || 'Unknown error'; - case 'logging in': - return 'Checking account number'; - default: - return 'Enter your account number'; - } - } - - _getStatusIcon() { - const statusIconPath = this._getStatusIconPath(); - return ( - <View style={styles.status_icon}> - {statusIconPath ? <Img source={statusIconPath} height={48} width={48} alt="" /> : null} - </View> - ); - } - - _getStatusIconPath(): ?string { - switch (this.props.loginState) { - case 'logging in': - return 'icon-spinner'; - case 'failed': - return 'icon-fail'; - case 'ok': - return 'icon-success'; - default: - return undefined; - } - } - - _accountInputGroupStyles(): Array<Object> { - const classes = [styles.account_input_group]; - if (this.state.isActive) { - classes.push(styles.account_input_group__active); - } - - switch (this.props.loginState) { - case 'logging in': - classes.push(styles.account_input_group__inactive); - break; - case 'failed': - classes.push(styles.account_input_group__error); - break; - } - - return classes; - } - - _accountInputButtonStyles(): Array<Object> { - const classes = [styles.input_button]; - - if (this.props.loginState === 'logging in') { - classes.push(styles.input_button__invisible); - } - - classes.push(this._loginButtonAnimationStyle); - - return classes; - } - - _accountInputArrowStyles(): Array<Object> { - const { accountToken, loginState } = this.props; - const classes = [styles.input_arrow]; - - if (accountToken && accountToken.length >= MIN_ACCOUNT_TOKEN_LENGTH) { - classes.push(styles.input_arrow__active); - } - - if (loginState === 'logging in') { - classes.push(styles.input_arrow__invisible); - } - - return classes; - } - - _shouldActivateLoginButton() { - const { accountToken } = this.props; - return accountToken && accountToken.length >= MIN_ACCOUNT_TOKEN_LENGTH; - } - - _shouldEnableAccountInput() { - // enable account input always except when "logging in" - return this.props.loginState !== 'logging in'; - } - - _shouldShowAccountHistory() { - return ( - this._shouldEnableAccountInput() && - this.state.isActive && - this.props.accountHistory.length > 0 - ); - } - - _shouldShowLoginForm() { - return this.props.loginState !== 'ok'; - } - - _shouldShowFooter() { - return ( - (this.props.loginState === 'none' || this.props.loginState === 'failed') && - !this._shouldShowAccountHistory() - ); - } - - _onSelectAccountFromHistory = (accountToken) => { - this.props.updateAccountToken(accountToken); - this.props.login(accountToken); - }; - - _onRemoveAccountFromHistory = (accountToken) => { - this._removeAccountFromHistory(accountToken); - }; - - async _removeAccountFromHistory(accountToken: AccountToken) { - try { - await this.props.removeAccountTokenFromHistory(accountToken); - - // TODO: Remove account from memory - } catch (error) { - // TODO: Show error - } - } - - _createLoginForm() { - return ( - <View> - <Text style={styles.subtitle}>{this._formSubtitle()}</Text> - <View style={this._accountInputGroupStyles()}> - <View style={styles.account_input_backdrop}> - <AccountInput - style={styles.account_input_textfield} - type="text" - placeholder="0000 0000 0000 0000" - placeholderTextColor={colors.blue40} - onFocus={this._onFocus} - onBlur={this._onBlur} - onChange={this._onInputChange} - onEnter={this._onLogin} - value={this.props.accountToken || ''} - editable={this._shouldEnableAccountInput()} - autoFocus={true} - ref={(ref) => (this._accountInput = ref)} - testName="AccountInput" - /> - <Animated.View - style={this._accountInputButtonStyles()} - onPress={this._onLogin} - testName="account-input-button"> - <Img - style={this._accountInputArrowStyles()} - source="icon-arrow" - height={16} - width={24} - tintColor="currentColor" - /> - </Animated.View> - </View> - <Accordion height={this._shouldShowAccountHistory() ? 'auto' : 0}> - { - <AccountDropdown - items={this.props.accountHistory.slice().reverse()} - onSelect={this._onSelectAccountFromHistory} - onRemove={this._onRemoveAccountFromHistory} - /> - } - </Accordion> - </View> - </View> - ); - } - - _createFooter() { - return ( - <View> - <Text style={styles.login_footer__prompt}>{"Don't have an account number?"}</Text> - <AppButton.BlueButton onPress={this._onCreateAccount}> - <AppButton.Label>Create account</AppButton.Label> - <Img source="icon-extLink" height={16} width={16} /> - </AppButton.BlueButton> - </View> - ); - } -} - -type AccountDropdownProps = { - items: Array<AccountToken>, - onSelect: (value: AccountToken) => void, - onRemove: (value: AccountToken) => void, -}; - -class AccountDropdown extends React.Component<AccountDropdownProps> { - render() { - const uniqueItems = [...new Set(this.props.items)]; - return ( - <View> - {uniqueItems.map((token) => ( - <AccountDropdownItem - key={token} - value={token} - label={formatAccount(token)} - onSelect={this.props.onSelect} - onRemove={this.props.onRemove} - /> - ))} - </View> - ); - } -} - -type AccountDropdownItemProps = { - label: string, - value: AccountToken, - onRemove: (value: AccountToken) => void, - onSelect: (value: AccountToken) => void, -}; - -class AccountDropdownItem extends React.Component<AccountDropdownItemProps> { - render() { - return ( - <View> - <View style={styles.account_dropdown__spacer} /> - <Cell.CellButton - style={styles.account_dropdown__item} - cellHoverStyle={styles.account_dropdown__item_hover}> - <Cell.Label - style={styles.account_dropdown__label} - cellHoverStyle={styles.account_dropdown__label_hover} - onPress={() => this.props.onSelect(this.props.value)}> - {this.props.label} - </Cell.Label> - <Img - style={styles.account_dropdown__remove} - cellHoverStyle={styles.account_dropdown__remove_cell_hover} - hoverStyle={styles.account_dropdown__remove_hover} - source="icon-close-sml" - height={16} - width={16} - onPress={() => this.props.onRemove(this.props.value)} - /> - </Cell.CellButton> - </View> - ); - } -} diff --git a/app/components/LoginStyles.js b/app/components/LoginStyles.js deleted file mode 100644 index e385f5115c..0000000000 --- a/app/components/LoginStyles.js +++ /dev/null @@ -1,174 +0,0 @@ -// @flow - -import { Styles } from 'reactxp'; -import { colors } from '../config'; - -export default { - login_footer: Styles.createViewStyle({ - flex: 0, - paddingTop: 16, - paddingBottom: 24, - paddingLeft: 24, - paddingRight: 24, - backgroundColor: colors.darkBlue, - }), - status_icon: Styles.createViewStyle({ - flex: 0, - marginBottom: 30, - alignItems: 'center', - }), - login_form: Styles.createViewStyle({ - flex: 1, - flexDirection: 'column', - overflow: 'visible', - paddingTop: 0, - paddingBottom: 0, - paddingLeft: 24, - paddingRight: 24, - marginTop: 83, - marginBottom: 0, - marginRight: 0, - marginLeft: 0, - }), - account_input_group: Styles.createViewStyle({ - borderWidth: 2, - borderRadius: 8, - borderColor: 'transparent', - }), - account_input_group__active: Styles.createViewStyle({ - borderColor: colors.darkBlue, - }), - account_input_group__inactive: Styles.createViewStyle({ - opacity: 0.6, - }), - account_input_group__error: Styles.createViewStyle({ - borderColor: colors.red40, - color: colors.red, - }), - account_input_backdrop: Styles.createViewStyle({ - backgroundColor: colors.white, - borderColor: colors.darkBlue, - flexDirection: 'row', - }), - input_button: Styles.createViewStyle({ - flex: 0, - borderWidth: 0, - width: 48, - alignItems: 'center', - justifyContent: 'center', - }), - input_button__invisible: Styles.createViewStyle({ - backgroundColor: colors.white, - opacity: 0, - }), - input_arrow: Styles.createViewStyle({ - flex: 0, - borderWidth: 0, - width: 48, - alignItems: 'center', - justifyContent: 'center', - color: colors.blue20, - }), - input_arrow__active: Styles.createViewStyle({ - color: colors.white, - }), - input_arrow__invisible: Styles.createViewStyle({ - color: colors.white, - opacity: 0, - }), - account_dropdown__spacer: Styles.createViewStyle({ - height: 1, - backgroundColor: colors.darkBlue, - }), - account_dropdown__item: Styles.createViewStyle({ - paddingTop: 0, - paddingRight: 0, - paddingLeft: 0, - paddingBottom: 0, - marginBottom: 0, - flexDirection: 'row', - alignItems: 'stretch', - backgroundColor: colors.white60, - cursor: 'default', - }), - account_dropdown__item_hover: Styles.createViewStyle({ - backgroundColor: colors.white40, - }), - account_dropdown__remove: Styles.createViewStyle({ - justifyContent: 'center', - color: colors.blue40, - paddingTop: 10, - paddingRight: 12, - paddingBottom: 12, - paddingLeft: 12, - marginLeft: 0, - }), - account_dropdown__remove_cell_hover: Styles.createViewStyle({ - color: colors.blue60, - }), - account_dropdown__remove_hover: Styles.createViewStyle({ - color: colors.blue, - }), - account_dropdown__label_hover: Styles.createViewStyle({ - color: colors.blue, - }), - - login_footer__prompt: Styles.createTextStyle({ - color: colors.white80, - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - lineHeight: 18, - letterSpacing: -0.2, - marginBottom: 8, - }), - title: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 32, - fontWeight: '900', - lineHeight: 44, - letterSpacing: -0.7, - color: colors.white, - marginBottom: 7, - flex: 0, - }), - subtitle: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - lineHeight: 15, - fontWeight: '600', - letterSpacing: -0.2, - color: colors.white80, - marginBottom: 8, - }), - account_input_textfield: Styles.createTextInputStyle({ - borderWidth: 0, - paddingTop: 10, - paddingRight: 12, - paddingLeft: 12, - paddingBottom: 12, - fontFamily: 'DINPro', - fontSize: 20, - fontWeight: '900', - lineHeight: 26, - color: colors.blue, - backgroundColor: 'transparent', - flex: 1, - }), - account_dropdown__label: Styles.createTextStyle({ - flex: 1, - fontFamily: 'DINPro', - fontSize: 20, - fontWeight: '900', - lineHeight: 26, - color: colors.blue80, - borderWidth: 0, - textAlign: 'left', - marginLeft: 0, - paddingTop: 10, - paddingRight: 0, - paddingLeft: 12, - paddingBottom: 12, - cursor: 'default', - }), -}; diff --git a/app/components/Map.js b/app/components/Map.js deleted file mode 100644 index 72298fd2ee..0000000000 --- a/app/components/Map.js +++ /dev/null @@ -1,101 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Component, View } from 'reactxp'; - -import SvgMap from './SvgMap'; - -export type MapProps = { - center: [number, number], // longitude, latitude - offset: [number, number], // offset [x, y] from the center of the map - zoomLevel: 'high' | 'medium' | 'low', - showMarker: boolean, - markerStyle: 'secure' | 'unsecure', - style: Object, -}; - -type MapState = { - bounds: { - width: number, - height: number, - }, -}; - -export default class Map extends Component<MapProps, MapState> { - state = { - bounds: { - width: 0, - height: 0, - }, - }; - - render() { - const { width, height } = this.state.bounds; - const readyToRenderTheMap = width > 0 && height > 0; - return ( - <View style={this.props.style} onLayout={this._onLayout}> - {readyToRenderTheMap && ( - <SvgMap - width={width} - height={height} - center={this.props.center} - offset={this.props.offset} - zoomLevel={this._zoomLevel(this.props.zoomLevel)} - showMarker={this.props.showMarker} - markerImagePath={this._markerImage(this.props.markerStyle)} - /> - )} - </View> - ); - } - - shouldComponentUpdate(nextProps: MapProps, nextState: MapState) { - const oldProps = this.props; - const oldState = this.state; - return ( - oldProps.center[0] !== nextProps.center[0] || - oldProps.center[1] !== nextProps.center[1] || - oldProps.offset[0] !== nextProps.offset[0] || - oldProps.offset[1] !== nextProps.offset[1] || - oldProps.zoomLevel !== nextProps.zoomLevel || - oldProps.showMarker !== nextProps.showMarker || - oldProps.markerStyle !== nextProps.markerStyle || - oldState.bounds.width !== nextState.bounds.width || - oldState.bounds.height !== nextState.bounds.height - ); - } - - _onLayout = (layoutInfo) => { - this.setState({ - bounds: { - width: layoutInfo.width, - height: layoutInfo.height, - }, - }); - }; - - // TODO: Remove zoom level in favor of center + coordinate span - _zoomLevel(variant: $PropertyType<MapProps, 'zoomLevel'>) { - switch (variant) { - case 'high': - return 1; - case 'medium': - return 20; - case 'low': - return 40; - default: - throw new Error(`Invalid enumeration type: ${variant}`); - } - } - - _markerImage(style: $PropertyType<MapProps, 'markerStyle'>) { - switch (style) { - case 'secure': - return './assets/images/location-marker-secure.svg'; - case 'unsecure': - return './assets/images/location-marker-unsecure.svg'; - default: - throw new Error(`Invalid enumeration type: ${style}`); - } - } -} diff --git a/app/components/NavigationBar.js b/app/components/NavigationBar.js deleted file mode 100644 index e8e4de16f9..0000000000 --- a/app/components/NavigationBar.js +++ /dev/null @@ -1,99 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Button, Component, Text, View, Styles } from 'reactxp'; -import Img from './Img'; -import { colors } from '../config'; - -const styles = { - navigationBar: { - default: Styles.createViewStyle({ - flex: 0, - alignItems: 'flex-start', - paddingLeft: 12, - }), - darwin: Styles.createViewStyle({ - paddingTop: 24, - }), - win32: Styles.createViewStyle({ - paddingTop: 12, - }), - linux: Styles.createViewStyle({ - paddingTop: 12, - WebkitAppRegion: 'drag', - }), - }, - closeBarItem: { - default: Styles.createViewStyle({ - cursor: 'default', - WebkitAppRegion: 'no-drag', - }), - icon: Styles.createViewStyle({ - flex: 0, - opacity: 0.6, - }), - }, - backBarButton: { - default: Styles.createViewStyle({ - borderWidth: 0, - padding: 0, - margin: 0, - cursor: 'default', - WebkitAppRegion: 'no-drag', - }), - content: Styles.createViewStyle({ - flexDirection: 'row', - alignItems: 'center', - }), - label: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - color: colors.white60, - }), - icon: Styles.createViewStyle({ - opacity: 0.6, - marginRight: 8, - }), - }, -}; - -export default class NavigationBar extends Component { - render() { - return ( - <View style={[styles.navigationBar.default, styles.navigationBar[process.platform]]}> - {this.props.children} - </View> - ); - } -} - -export class CloseBarItem extends Component { - props: { - action: () => void, - }; - render() { - return ( - <Button style={[styles.closeBarItem.default]} onPress={this.props.action}> - <Img height={24} width={24} style={[styles.closeBarItem.icon]} source="icon-close" /> - </Button> - ); - } -} - -export class BackBarItem extends Component { - props: { - title: string, - action: () => void, - }; - render() { - return ( - <Button style={styles.backBarButton.default} onPress={this.props.action}> - <View style={styles.backBarButton.content}> - <Img style={styles.backBarButton.icon} source="icon-back" /> - <Text style={styles.backBarButton.label}>{this.props.title}</Text> - </View> - </Button> - ); - } -} diff --git a/app/components/PlatformWindow.android.js b/app/components/PlatformWindow.android.js deleted file mode 100644 index 32f9a4f3bf..0000000000 --- a/app/components/PlatformWindow.android.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow - -import * as React from 'react'; -import { KeyboardAvoidingView } from 'react-native'; - -export default class PlatformWindow extends React.Component { - props: { - children: Array<React.Node> | React.Node, - }; - - render() { - return <KeyboardAvoidingView behavior={'position'}>{this.props.children}</KeyboardAvoidingView>; - } -} diff --git a/app/components/PlatformWindow.js b/app/components/PlatformWindow.js deleted file mode 100644 index c1133af0c2..0000000000 --- a/app/components/PlatformWindow.js +++ /dev/null @@ -1,18 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Component, View, Styles } from 'reactxp'; - -const styles = { - darwin: Styles.createViewStyle({ - WebkitMask: ` - url(./assets/images/app-triangle.svg) 50% 0% no-repeat, - url(./assets/images/app-header-backdrop.svg) no-repeat`, - }), -}; - -export default class PlatformWindow extends Component { - render() { - return <View style={styles[process.platform]}>{this.props.children}</View>; - } -} diff --git a/app/components/Preferences.js b/app/components/Preferences.js deleted file mode 100644 index 7e57df3909..0000000000 --- a/app/components/Preferences.js +++ /dev/null @@ -1,106 +0,0 @@ -// @flow - -import React from 'react'; -import { Component, Text, View } from 'reactxp'; -import { Layout, Container } from './Layout'; -import NavigationBar, { BackBarItem } from './NavigationBar'; -import SettingsHeader, { HeaderTitle } from './SettingsHeader'; -import Switch from './Switch'; -import styles from './PreferencesStyles'; - -export type PreferencesProps = { - autoConnect: boolean, - allowLan: boolean, - getAutoStart: () => boolean, - setAutoStart: (boolean) => void, - setAutoConnect: (boolean) => void, - setAllowLan: (boolean) => void, - onClose: () => void, -}; - -type State = { - autoStart: boolean, -}; - -export default class Preferences extends Component<PreferencesProps, State> { - state = { - autoStart: false, - }; - - constructor(props: PreferencesProps) { - super(); - this.state.autoStart = props.getAutoStart(); - } - - render() { - return ( - <Layout> - <Container> - <View style={styles.preferences}> - <NavigationBar> - <BackBarItem action={this.props.onClose} title={'Settings'} /> - </NavigationBar> - - <View style={styles.preferences__container}> - <SettingsHeader> - <HeaderTitle>Preferences</HeaderTitle> - </SettingsHeader> - - <View style={styles.preferences__content}> - <View style={styles.preferences__cell}> - <View style={styles.preferences__cell_label_container}> - <Text style={styles.preferences__cell_label}>Auto-connect</Text> - </View> - <View style={styles.preferences__cell_accessory}> - <Switch isOn={this.props.autoConnect} onChange={this.props.setAutoConnect} /> - </View> - </View> - <View style={styles.preferences__cell_footer}> - <Text style={styles.preferences__cell_footer_label}> - {'Automatically connect the VPN when the computer starts.'} - </Text> - </View> - - <View style={styles.preferences__cell}> - <View style={styles.preferences__cell_label_container}> - <Text style={styles.preferences__cell_label}>Auto-start</Text> - </View> - <View style={styles.preferences__cell_accessory}> - <Switch isOn={this.state.autoStart} onChange={this._onChangeAutoStart} /> - </View> - </View> - <View style={styles.preferences__cell_footer}> - <Text style={styles.preferences__cell_footer_label}> - {'Automatically open Mullvad VPN at login to the system.'} - </Text> - </View> - - <View style={styles.preferences__cell}> - <View style={styles.preferences__cell_label_container}> - <Text style={styles.preferences__cell_label}>Local network sharing</Text> - </View> - <View style={styles.preferences__cell_accessory}> - <Switch isOn={this.props.allowLan} onChange={this.props.setAllowLan} /> - </View> - </View> - <View style={styles.preferences__cell_footer}> - <Text style={styles.preferences__cell_footer_label}> - { - 'Allows access to other devices on the same network for sharing, printing etc.' - } - </Text> - </View> - </View> - </View> - </View> - </Container> - </Layout> - ); - } - - _onChangeAutoStart = (autoStart: boolean) => { - this.props.setAutoStart(autoStart); - // TODO: Handle failure to set auto-start - this.setState({ autoStart }); - }; -} diff --git a/app/components/PreferencesStyles.js b/app/components/PreferencesStyles.js deleted file mode 100644 index 3a39ce3c5f..0000000000 --- a/app/components/PreferencesStyles.js +++ /dev/null @@ -1,60 +0,0 @@ -// @flow - -import { Styles } from 'reactxp'; -import { colors } from '../config'; - -export default { - preferences: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - flex: 1, - }), - preferences__container: Styles.createViewStyle({ - display: 'flex', - flexDirection: 'column', - flex: 1, - }), - preferences__content: Styles.createViewStyle({ - flexDirection: 'column', - flexGrow: 1, - flexShrink: 1, - flexBasis: 'auto', - }), - preferences__cell: Styles.createViewStyle({ - backgroundColor: colors.blue, - flexDirection: 'row', - alignItems: 'center', - }), - preferences__cell_accessory: Styles.createViewStyle({ - marginRight: 12, - }), - preferences__cell_footer: Styles.createViewStyle({ - paddingTop: 8, - paddingRight: 24, - paddingBottom: 24, - paddingLeft: 24, - }), - preferences__cell_label_container: Styles.createViewStyle({ - paddingTop: 14, - paddingRight: 12, - paddingBottom: 14, - paddingLeft: 24, - flexGrow: 1, - }), - - preferences__cell_label: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 20, - fontWeight: '900', - lineHeight: 26, - letterSpacing: -0.2, - color: colors.white, - }), - preferences__cell_footer_label: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - lineHeight: 20, - letterSpacing: -0.2, - color: colors.white80, - }), -}; diff --git a/app/components/SelectLocation.js b/app/components/SelectLocation.js deleted file mode 100644 index 0d737c3224..0000000000 --- a/app/components/SelectLocation.js +++ /dev/null @@ -1,247 +0,0 @@ -// @flow - -import * as React from 'react'; -import ReactDOM from 'react-dom'; -import { View } from 'reactxp'; -import { Layout, Container } from './Layout'; -import CustomScrollbars from './CustomScrollbars'; -import NavigationBar, { CloseBarItem } from './NavigationBar'; -import SettingsHeader, { HeaderTitle, HeaderSubTitle } from './SettingsHeader'; -import * as Cell from './Cell'; -import styles from './SelectLocationStyles'; -import Img from './Img'; - -import Accordion from './Accordion'; - -import type { - SettingsReduxState, - RelayLocationRedux, - RelayLocationCityRedux, -} from '../redux/settings/reducers'; -import type { RelayLocation } from '../lib/daemon-rpc'; - -export type SelectLocationProps = { - settings: SettingsReduxState, - onClose: () => void, - onSelect: (location: RelayLocation) => void, -}; - -type State = { - expanded: Array<string>, -}; - -export default class SelectLocation extends React.Component<SelectLocationProps, State> { - _selectedCell: ?Cell.CellButton; - _scrollView: ?CustomScrollbars; - - state = { - expanded: [], - }; - - constructor(props: SelectLocationProps, context?: any) { - super(props, context); - - // set initially expanded country based on relaySettings - const relaySettings = this.props.settings.relaySettings; - if (relaySettings.normal) { - const { location } = relaySettings.normal; - if (location === 'any') { - // no-op - } else if (location.country) { - this.state.expanded.push(location.country); - } else if (location.city) { - this.state.expanded.push(location.city[0]); - } - } - } - - componentDidMount() { - // restore scroll to selected cell - const cell = this._selectedCell; - const scrollView = this._scrollView; - - if (scrollView && cell) { - // eslint-disable-next-line react/no-find-dom-node - const cellDOMNode = ReactDOM.findDOMNode(cell); - - if (cellDOMNode instanceof HTMLElement) { - scrollView.scrollToElement(cellDOMNode, 'middle'); - } - } - } - - render() { - return ( - <Layout> - <Container> - <View style={styles.select_location}> - <NavigationBar> - <CloseBarItem action={this.props.onClose} /> - </NavigationBar> - <View style={styles.container}> - <SettingsHeader style={styles.title_header}> - <HeaderTitle>Select location</HeaderTitle> - </SettingsHeader> - - <CustomScrollbars autoHide={true} ref={(ref) => (this._scrollView = ref)}> - <View style={styles.content}> - <SettingsHeader style={styles.subtitle_header}> - <HeaderSubTitle> - While connected, your real location is masked with a private and secure - location in the selected region - </HeaderSubTitle> - </SettingsHeader> - - {this.props.settings.relayLocations.map((relayCountry) => { - return this._renderCountry(relayCountry); - })} - </View> - </CustomScrollbars> - </View> - </View> - </Container> - </Layout> - ); - } - - _isSelected(selectedLocation: RelayLocation) { - const { relaySettings } = this.props.settings; - if (relaySettings.normal) { - const otherLocation = relaySettings.normal.location; - - if ( - selectedLocation.country && - otherLocation.country && - selectedLocation.country === otherLocation.country - ) { - return true; - } - - if (Array.isArray(selectedLocation.city) && Array.isArray(otherLocation.city)) { - const selectedCity = selectedLocation.city; - const otherCity = otherLocation.city; - - return ( - selectedCity.length === otherCity.length && - selectedCity.every((v, i) => v === otherCity[i]) - ); - } - } - return false; - } - - _toggleCollapse = (countryCode: string) => { - this.setState((state) => { - const expanded = state.expanded.slice(); - const index = expanded.indexOf(countryCode); - if (index === -1) { - expanded.push(countryCode); - } else { - expanded.splice(index, 1); - } - return { expanded }; - }); - }; - - _relayStatusIndicator(active: boolean, isSelected: boolean) { - const statusClass = active ? styles.relay_status__active : styles.relay_status__inactive; - - return isSelected ? ( - <Img style={styles.tick_icon} source="icon-tick" height={24} width={24} /> - ) : ( - <View style={[styles.relay_status, statusClass]} /> - ); - } - - _renderCountry(relayCountry: RelayLocationRedux) { - const isSelected = this._isSelected({ country: relayCountry.code }); - - const onRef = isSelected - ? (element) => { - this._selectedCell = element; - } - : undefined; - - // either expanded by user or when the city selected within the country - const isExpanded = this.state.expanded.includes(relayCountry.code); - - const handleSelect = - relayCountry.hasActiveRelays && !isSelected - ? () => { - this.props.onSelect({ country: relayCountry.code }); - } - : undefined; - - const handleCollapse = (e) => { - this._toggleCollapse(relayCountry.code); - e.stopPropagation(); - }; - - return ( - <View key={relayCountry.code} style={styles.country}> - <Cell.CellButton - cellHoverStyle={isSelected ? styles.cell_selected : null} - style={isSelected ? styles.cell_selected : styles.cell} - onPress={handleSelect} - disabled={!relayCountry.hasActiveRelays} - testName="country" - ref={onRef}> - {this._relayStatusIndicator(relayCountry.hasActiveRelays, isSelected)} - - <Cell.Label>{relayCountry.name}</Cell.Label> - - {relayCountry.cities.length > 1 ? ( - <Img - style={styles.collapse_button} - hoverStyle={styles.expand_chevron_hover} - onPress={handleCollapse} - source={isExpanded ? 'icon-chevron-up' : 'icon-chevron-down'} - height={24} - width={24} - /> - ) : null} - </Cell.CellButton> - - {relayCountry.cities.length > 1 && ( - <Accordion height={isExpanded ? 'auto' : 0}> - {relayCountry.cities.map((relayCity) => this._renderCity(relayCountry.code, relayCity))} - </Accordion> - )} - </View> - ); - } - - _renderCity(countryCode: string, relayCity: RelayLocationCityRedux) { - const relayLocation: RelayLocation = { city: [countryCode, relayCity.code] }; - - const isSelected = this._isSelected(relayLocation); - - const onRef = isSelected - ? (element) => { - this._selectedCell = element; - } - : undefined; - - const handleSelect = - relayCity.hasActiveRelays && !isSelected - ? () => { - this.props.onSelect(relayLocation); - } - : undefined; - - return ( - <Cell.CellButton - key={`${countryCode}_${relayCity.code}`} - onPress={handleSelect} - disabled={!relayCity.hasActiveRelays} - cellHoverStyle={isSelected ? styles.sub_cell__selected : null} - style={isSelected ? styles.sub_cell__selected : styles.sub_cell} - testName="city" - ref={onRef}> - {this._relayStatusIndicator(relayCity.hasActiveRelays, isSelected)} - - <Cell.Label>{relayCity.name}</Cell.Label> - </Cell.CellButton> - ); - } -} diff --git a/app/components/SelectLocationStyles.js b/app/components/SelectLocationStyles.js deleted file mode 100644 index 57b0653a45..0000000000 --- a/app/components/SelectLocationStyles.js +++ /dev/null @@ -1,87 +0,0 @@ -// @flow - -import { Styles } from 'reactxp'; -import { colors } from '../config'; - -export default { - select_location: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - flex: 1, - }), - container: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - }), - title_header: Styles.createViewStyle({ - paddingBottom: 0, - }), - subtitle_header: Styles.createViewStyle({ - paddingTop: 0, - }), - content: Styles.createViewStyle({ - overflow: 'visible', - }), - relay_status: Styles.createViewStyle({ - width: 16, - height: 16, - borderRadius: 8, - marginLeft: 4, - marginRight: 4, - marginTop: 20, - marginBottom: 20, - }), - relay_status__inactive: Styles.createViewStyle({ - backgroundColor: colors.red95, - }), - relay_status__active: Styles.createViewStyle({ - backgroundColor: colors.green90, - }), - tick_icon: Styles.createViewStyle({ - color: colors.white, - marginLeft: 0, - marginRight: 0, - marginTop: 15.5, - marginBottom: 15.5, - }), - country: Styles.createViewStyle({ - flexDirection: 'column', - flex: 0, - }), - collapse_button: Styles.createViewStyle({ - flex: 0, - alignSelf: 'stretch', - justifyContent: 'center', - paddingRight: 16, - paddingLeft: 16, - }), - cell: Styles.createViewStyle({ - paddingTop: 0, - paddingBottom: 0, - paddingLeft: 20, - paddingRight: 0, - }), - sub_cell: Styles.createViewStyle({ - paddingTop: 0, - paddingBottom: 0, - paddingRight: 0, - paddingLeft: 40, - backgroundColor: colors.blue40, - }), - sub_cell__selected: Styles.createViewStyle({ - paddingTop: 0, - paddingBottom: 0, - paddingRight: 0, - paddingLeft: 40, - backgroundColor: colors.green, - }), - cell_selected: Styles.createViewStyle({ - paddingTop: 0, - paddingBottom: 0, - paddingLeft: 20, - paddingRight: 0, - backgroundColor: colors.green, - }), - expand_chevron_hover: Styles.createViewStyle({ - color: colors.white, - }), -}; diff --git a/app/components/Settings.js b/app/components/Settings.js deleted file mode 100644 index 7c7099a3ed..0000000000 --- a/app/components/Settings.js +++ /dev/null @@ -1,194 +0,0 @@ -// @flow - -import moment from 'moment'; -import * as React from 'react'; -import { Component, View } from 'reactxp'; -import * as AppButton from './AppButton'; -import * as Cell from './Cell'; -import { Layout, Container } from './Layout'; -import NavigationBar, { CloseBarItem } from './NavigationBar'; -import SettingsHeader, { HeaderTitle } from './SettingsHeader'; -import CustomScrollbars from './CustomScrollbars'; -import styles from './SettingsStyles'; -import Img from './Img'; -import WindowStateObserver from '../lib/window-state-observer'; - -import type { LoginState } from '../redux/account/reducers'; - -type Props = { - loginState: LoginState, - accountExpiry: ?string, - appVersion: string, - onQuit: () => void, - onClose: () => void, - onViewAccount: () => void, - onViewSupport: () => void, - onViewPreferences: () => void, - onViewAdvancedSettings: () => void, - onExternalLink: (type: string) => void, - updateAccountExpiry: () => Promise<void>, -}; - -export default class Settings extends Component<Props> { - _windowStateObserver = new WindowStateObserver(); - - componentDidMount() { - this.props.updateAccountExpiry(); - - this._windowStateObserver.onShow = () => { - this.props.updateAccountExpiry(); - }; - } - - componentWillUnmount() { - this._windowStateObserver.dispose(); - } - - render() { - return ( - <Layout> - <Container> - <View style={styles.settings}> - <NavigationBar> - <CloseBarItem action={this.props.onClose} /> - </NavigationBar> - - <View style={styles.settings__container}> - <SettingsHeader> - <HeaderTitle>Settings</HeaderTitle> - </SettingsHeader> - - <CustomScrollbars style={styles.settings__scrollview} autoHide={true}> - <View style={styles.settings__content}> - <View> - {this._renderTopButtons()} - {this._renderMiddleButtons()} - {this._renderBottomButtons()} - </View> - {this._renderQuitButton()} - </View> - </CustomScrollbars> - </View> - </View> - </Container> - </Layout> - ); - } - - _renderTopButtons() { - const isLoggedIn = this.props.loginState === 'ok'; - if (!isLoggedIn) { - return null; - } - - let isOutOfTime = false; - let formattedExpiry = ''; - - const expiryIso = this.props.accountExpiry; - if (isLoggedIn && expiryIso) { - const expiry = moment(expiryIso); - isOutOfTime = expiry.isSameOrBefore(moment()); - formattedExpiry = (expiry.fromNow(true) + ' left').toUpperCase(); - } - - return ( - <View> - <View testName="settings__account"> - {isOutOfTime ? ( - <Cell.CellButton - onPress={this.props.onViewAccount} - testName="settings__account_paid_until_button"> - <Cell.Label>Account</Cell.Label> - <Cell.SubText - testName="settings__account_paid_until_subtext" - style={styles.settings__account_paid_until_label__error}> - {'OUT OF TIME'} - </Cell.SubText> - <Img height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> - ) : ( - <Cell.CellButton - onPress={this.props.onViewAccount} - testName="settings__account_paid_until_button"> - <Cell.Label>Account</Cell.Label> - <Cell.SubText testName="settings__account_paid_until_subtext"> - {formattedExpiry} - </Cell.SubText> - <Img height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> - )} - </View> - - <Cell.CellButton onPress={this.props.onViewPreferences} testName="settings__preferences"> - <Cell.Label>Preferences</Cell.Label> - <Img height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> - - <Cell.CellButton onPress={this.props.onViewAdvancedSettings} testName="settings__advanced"> - <Cell.Label>Advanced</Cell.Label> - <Img height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> - <View style={styles.settings__cell_spacer} /> - </View> - ); - } - - _renderMiddleButtons() { - return ( - <View> - <Cell.CellButton - onPress={this.props.onExternalLink.bind(this, 'download')} - testName="settings__version"> - <Cell.Label>App version</Cell.Label> - <Cell.SubText>{this._formattedVersion()}</Cell.SubText> - <Img height={16} width={16} source="icon-extLink" /> - </Cell.CellButton> - <View style={styles.settings__cell_spacer} /> - </View> - ); - } - - _formattedVersion() { - // the version in package.json has to be semver, but we use a YEAR.release-channel - // version scheme. in package.json we thus have to write YEAR.release.X-channel and - // this function is responsible for removing .X part. - return this.props.appVersion - .replace('.0-', '-') // remove the .0 in 2018.1.0-beta9 - .replace(/\.0$/, ''); // remove the .0 in 2018.1.0 - } - - _renderBottomButtons() { - return ( - <View> - <Cell.CellButton - onPress={this.props.onExternalLink.bind(this, 'faq')} - testName="settings__external_link"> - <Cell.Label>FAQs</Cell.Label> - <Img height={16} width={16} source="icon-extLink" /> - </Cell.CellButton> - - <Cell.CellButton - onPress={this.props.onExternalLink.bind(this, 'guides')} - testName="settings__external_link"> - <Cell.Label>Guides</Cell.Label> - <Img height={16} width={16} source="icon-extLink" /> - </Cell.CellButton> - - <Cell.CellButton onPress={this.props.onViewSupport} testName="settings__view_support"> - <Cell.Label>Report a problem</Cell.Label> - <Img height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> - </View> - ); - } - - _renderQuitButton() { - return ( - <View style={styles.settings__footer}> - <AppButton.RedButton onPress={this.props.onQuit} testName="settings__quit"> - <AppButton.Label>Quit app</AppButton.Label> - </AppButton.RedButton> - </View> - ); - } -} diff --git a/app/components/SettingsHeader.js b/app/components/SettingsHeader.js deleted file mode 100644 index c1791d6fc0..0000000000 --- a/app/components/SettingsHeader.js +++ /dev/null @@ -1,61 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Component, Text, View, Styles } from 'reactxp'; -import { colors } from '../config'; - -const styles = { - header: { - default: Styles.createViewStyle({ - flexGrow: 0, - flexShrink: 0, - flexBasis: 'auto', - paddingTop: 16, - paddingRight: 24, - paddingLeft: 24, - paddingBottom: 24, - }), - linux: Styles.createViewStyle({ - WebkitAppRegion: 'drag', - }), - }, - title: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 32, - fontWeight: '900', - lineHeight: 40, - color: colors.white, - }), - subtitle: Styles.createTextStyle({ - marginTop: 4, - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - overflow: 'visible', - color: colors.white80, - lineHeight: 20, - letterSpacing: -0.2, - }), -}; - -export default class SettingsHeader extends Component { - render() { - return ( - <View style={[styles.header.default, styles.header[process.platform], this.props.style]}> - {this.props.children} - </View> - ); - } -} - -export class HeaderTitle extends Component { - render() { - return <Text style={[styles.title]}>{this.props.children}</Text>; - } -} - -export class HeaderSubTitle extends Component { - render() { - return <Text style={[styles.subtitle]}>{this.props.children}</Text>; - } -} diff --git a/app/components/SettingsStyles.js b/app/components/SettingsStyles.js deleted file mode 100644 index be2491bec4..0000000000 --- a/app/components/SettingsStyles.js +++ /dev/null @@ -1,40 +0,0 @@ -// @flow - -import { Styles } from 'reactxp'; -import { colors } from '../config'; - -export default { - settings: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - flex: 1, - }), - settings__container: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - }), - settings__content: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - justifyContent: 'space-between', - overflow: 'visible', - }), - settings__scrollview: Styles.createViewStyle({ - flexGrow: 1, - flexShrink: 1, - flexBasis: '100%', - }), - settings__cell_spacer: Styles.createViewStyle({ - height: 24, - flex: 0, - }), - settings__footer: Styles.createViewStyle({ - paddingTop: 24, - paddingBottom: 24, - paddingLeft: 24, - paddingRight: 24, - }), - - settings__account_paid_until_label__error: Styles.createTextStyle({ - color: colors.red, - }), -}; diff --git a/app/components/Support.js b/app/components/Support.js deleted file mode 100644 index 2552a0e432..0000000000 --- a/app/components/Support.js +++ /dev/null @@ -1,328 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Component, Text, View, TextInput } from 'reactxp'; -import * as AppButton from './AppButton'; -import { Layout, Container } from './Layout'; -import NavigationBar, { BackBarItem } from './NavigationBar'; -import SettingsHeader, { HeaderTitle, HeaderSubTitle } from './SettingsHeader'; -import styles from './SupportStyles'; -import Img from './Img'; - -import type { AccountToken } from '../lib/daemon-rpc'; -import type { SupportReportForm } from '../redux/support/actions'; -type SupportState = { - email: string, - message: string, - savedReport: ?string, - sendState: 'INITIAL' | 'CONFIRM_NO_EMAIL' | 'LOADING' | 'SUCCESS' | 'FAILED', -}; - -export type SupportProps = { - defaultEmail: string, - defaultMessage: string, - accountHistory: Array<AccountToken>, - onClose: () => void, - viewLog: (path: string) => void, - saveReportForm: (form: SupportReportForm) => void, - clearReportForm: () => void, - collectProblemReport: (accountsToRedact: Array<string>) => Promise<string>, - sendProblemReport: (email: string, message: string, savedReport: string) => Promise<void>, -}; - -export default class Support extends Component<SupportProps, SupportState> { - state = { - email: '', - message: '', - savedReport: null, - sendState: 'INITIAL', - }; - - _collectLogPromise: ?Promise<string>; - - constructor(props: SupportProps) { - super(props); - - // seed initial data from props - this.state.email = props.defaultEmail; - this.state.message = props.defaultMessage; - } - - validate() { - return this.state.message.trim().length > 0; - } - - onChangeEmail = (email: string) => { - this.setState({ email: email }, () => { - this._saveFormData(); - }); - }; - - onChangeDescription = (description: string) => { - this.setState({ message: description }, () => { - this._saveFormData(); - }); - }; - - onViewLog = async (): Promise<void> => { - try { - const reportPath = await this._collectLog(); - this.props.viewLog(reportPath); - } catch (error) { - // TODO: handle error - } - }; - - _saveFormData() { - this.props.saveReportForm({ - email: this.state.email, - message: this.state.message, - }); - } - - async _collectLog(): Promise<string> { - if (this._collectLogPromise) { - return this._collectLogPromise; - } else { - const collectPromise = this.props.collectProblemReport(this.props.accountHistory); - - // save promise to prevent subsequent requests - this._collectLogPromise = collectPromise; - - try { - const reportPath = await collectPromise; - return new Promise((resolve) => { - this.setState({ savedReport: reportPath }, () => resolve(reportPath)); - }); - } catch (error) { - this._collectLogPromise = null; - - throw error; - } - } - } - - onSend = async (): Promise<void> => { - if (this.state.sendState === 'INITIAL' && this.state.email.length === 0) { - return new Promise((resolve) => { - this.setState({ sendState: 'CONFIRM_NO_EMAIL' }, () => resolve()); - }); - } else { - try { - await this._sendReport(); - } catch (error) { - // No-op - } - } - }; - - _sendReport(): Promise<void> { - return new Promise((resolve, reject) => { - this.setState({ sendState: 'LOADING' }, async () => { - try { - const { email, message } = this.state; - const reportPath = await this._collectLog(); - await this.props.sendProblemReport(email, message, reportPath); - this.props.clearReportForm(); - this.setState({ sendState: 'SUCCESS' }, () => { - resolve(); - }); - } catch (error) { - this.setState({ sendState: 'FAILED' }, () => { - reject(error); - }); - } - }); - }); - } - - render() { - const { sendState } = this.state; - const header = ( - <SettingsHeader> - <HeaderTitle>Report a problem</HeaderTitle> - {(sendState === 'INITIAL' || sendState === 'CONFIRM_NO_EMAIL') && ( - <HeaderSubTitle> - { - "To help you more effectively, your app's log file will be attached to this message. Your data will remain secure and private, as it is anonymised before being sent over an encrypted channel." - } - </HeaderSubTitle> - )} - </SettingsHeader> - ); - - const content = this._renderContent(); - - return ( - <Layout> - <Container> - <View style={styles.support}> - <NavigationBar> - <BackBarItem action={this.props.onClose} title={'Settings'} /> - </NavigationBar> - - <View style={styles.support__container}> - {header} - - {content} - </View> - </View> - </Container> - </Layout> - ); - } - - _renderContent() { - switch (this.state.sendState) { - case 'INITIAL': - case 'CONFIRM_NO_EMAIL': - return this._renderForm(); - case 'LOADING': - return this._renderLoading(); - case 'SUCCESS': - return this._renderSent(); - case 'FAILED': - return this._renderFailed(); - default: - return null; - } - } - - _renderForm() { - return ( - <View style={styles.support__content}> - <View style={styles.support__form}> - <View style={styles.support__form_row_email}> - <TextInput - style={styles.support__form_email} - placeholder="Your email (optional)" - defaultValue={this.state.email} - onChangeText={this.onChangeEmail} - keyboardType="email-address" - /> - </View> - <View style={styles.support__form_row_message}> - <View style={styles.support__form_message_scroll_wrap}> - <TextInput - style={styles.support__form_message} - placeholder="Describe your problem" - defaultValue={this.state.message} - multiline={true} - onChangeText={this.onChangeDescription} - testName="support__form_message" - /> - </View> - </View> - <View style={styles.support__footer}> - {this.state.sendState === 'CONFIRM_NO_EMAIL' - ? this._renderNoEmailWarning() - : this._renderActionButtons()} - </View> - </View> - </View> - ); - } - - _renderNoEmailWarning() { - return ( - <View> - <Text style={styles.support__no_email_warning}> - You are about to send the problem report without a way for us to get back to you. If you - want an answer to your report you will have to enter an email address. - </Text> - <AppButton.GreenButton - disabled={!this.validate()} - onPress={this.onSend} - testName="support__send_logs"> - {'Send anyway'} - </AppButton.GreenButton> - </View> - ); - } - - _renderActionButtons() { - return ( - <View> - <AppButton.BlueButton - style={styles.view_logs_button} - onPress={this.onViewLog} - testName="support__view_logs"> - <AppButton.Label>View app logs</AppButton.Label> - <Img source="icon-extLink" height={16} width={16} /> - </AppButton.BlueButton> - <AppButton.GreenButton - disabled={!this.validate()} - onPress={this.onSend} - testName="support__send_logs"> - Send - </AppButton.GreenButton> - </View> - ); - } - - _renderLoading() { - return ( - <View style={styles.support__content}> - <View style={styles.support__form}> - <View style={styles.support__form_row}> - <View style={styles.support__status_icon}> - <Img source="icon-spinner" height={60} width={60} alt="" /> - </View> - <View style={styles.support__status_security__secure}>{'SECURE CONNECTION'}</View> - <Text style={styles.support__send_status}>{'Sending...'}</Text> - </View> - </View> - </View> - ); - } - - _renderSent() { - return ( - <View style={styles.support__content}> - <View style={styles.support__form}> - <View style={styles.support__form_row}> - <View style={styles.support__status_icon}> - <Img source="icon-success" height={60} width={60} alt="" /> - </View> - <Text style={styles.support__status_security__secure}>{'SECURE CONNECTION'}</Text> - <Text style={styles.support__send_status}>{'Sent'}</Text> - - <Text style={styles.support__sent_message}>Thanks! We will look into this.</Text> - {this.state.email.trim().length > 0 ? ( - <Text style={styles.support__sent_message}> - If needed we will contact you on {'\u00A0'} - <Text style={styles.support__sent_email}>{this.state.email}</Text> - </Text> - ) : null} - </View> - </View> - </View> - ); - } - - _renderFailed() { - return ( - <View style={styles.support__content}> - <View style={styles.support__form}> - <View style={styles.support__form_row}> - <View style={styles.support__status_icon}> - <Img source="icon-fail" height={60} width={60} alt="" /> - </View> - <Text style={styles.support__status_security__secure}>{'SECURE CONNECTION'}</Text> - <Text style={styles.support__send_status}>{'Failed to send'}</Text> - </View> - </View> - <View style={styles.support__footer}> - <AppButton.BlueButton - style={styles.edit_message_button} - onPress={() => this.setState({ sendState: 'INITIAL' })}> - {'Edit message'} - </AppButton.BlueButton> - <AppButton.GreenButton onPress={this.onSend} testName="support__send_logs"> - Try again - </AppButton.GreenButton> - </View> - </View> - ); - } -} diff --git a/app/components/SupportStyles.js b/app/components/SupportStyles.js deleted file mode 100644 index 718659157a..0000000000 --- a/app/components/SupportStyles.js +++ /dev/null @@ -1,131 +0,0 @@ -// @flow - -import { Styles } from 'reactxp'; -import { colors } from '../config'; - -export default { - support: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - flex: 1, - }), - support__container: Styles.createViewStyle({ - display: 'flex', - flexDirection: 'column', - flex: 1, - }), - support__content: Styles.createViewStyle({ - flex: 1, - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - }), - support__form: Styles.createViewStyle({ - display: 'flex', - flex: 1, - flexDirection: 'column', - }), - support__form_row: Styles.createViewStyle({ - paddingLeft: 22, - paddingRight: 22, - marginBottom: 12, - }), - support__form_row_email: Styles.createViewStyle({ - paddingLeft: 22, - paddingRight: 22, - marginBottom: 12, - }), - support__form_row_message: Styles.createViewStyle({ - flex: 1, - paddingLeft: 22, - paddingRight: 22, - }), - support__form_message_scroll_wrap: Styles.createViewStyle({ - flex: 1, - display: 'flex', - borderRadius: 4, - overflow: 'hidden', - }), - support__footer: Styles.createViewStyle({ - paddingTop: 16, - paddingBottom: 16, - paddingLeft: 24, - paddingRight: 24, - flexDirection: 'column', - flex: 0, - }), - support__status_icon: Styles.createViewStyle({ - alignItems: 'center', - marginBottom: 32, - }), - view_logs_button: Styles.createViewStyle({ - marginBottom: 16, - }), - edit_message_button: Styles.createViewStyle({ - marginBottom: 16, - }), - support__form_email: Styles.createTextStyle({ - flex: 1, - borderRadius: 4, - overflow: 'hidden', - paddingTop: 14, - paddingLeft: 14, - paddingRight: 14, - paddingBottom: 14, - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - lineHeight: 26, - color: colors.blue, - backgroundColor: colors.white, - }), - support__form_message: Styles.createTextStyle({ - paddingTop: 14, - paddingLeft: 14, - paddingRight: 14, - paddingBottom: 14, - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - color: colors.blue, - backgroundColor: colors.white, - flex: 1, - }), - support__sent_message: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - overflow: 'visible', - color: colors.white60, - lineHeight: 20, - letterSpacing: -0.2, - }), - support__sent_email: Styles.createTextStyle({ - fontWeight: '900', - color: colors.white, - }), - support__status_security__secure: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 16, - fontWeight: '800', - lineHeight: 22, - marginBottom: 4, - color: colors.green, - }), - support__send_status: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 38, - fontWeight: '900', - maxHeight: 'calc(1.16em * 2)', - overflow: 'visible', - letterSpacing: -0.9, - color: colors.white, - marginBottom: 4, - }), - support__no_email_warning: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - lineHeight: 16, - color: colors.white80, - marginBottom: 12, - }), -}; diff --git a/app/components/SvgMap.js b/app/components/SvgMap.js deleted file mode 100644 index 43f1e2ef71..0000000000 --- a/app/components/SvgMap.js +++ /dev/null @@ -1,336 +0,0 @@ -// @flow - -import * as React from 'react'; -import { - ComposableMap, - ZoomableGroup, - Geographies, - Geography, - Markers, - Marker, -} from 'react-simple-maps'; - -import { geoTimes } from 'd3-geo-projection'; -import rbush from 'rbush'; - -import geographyData from '../assets/geo/geometry.json'; -import statesProvincesLinesData from '../assets/geo/states-provinces-lines.json'; - -import countryTreeData from '../assets/geo/countries.rbush.json'; -import cityTreeData from '../assets/geo/cities.rbush.json'; -import geometryTreeData from '../assets/geo/geometry.rbush.json'; -import statesProvincesLinesTreeData from '../assets/geo/states-provinces-lines.rbush.json'; - -const countryTree = rbush().fromJSON(countryTreeData); -const cityTree = rbush().fromJSON(cityTreeData); -const geometryTree = rbush().fromJSON(geometryTreeData); -const provincesStatesLinesTree = rbush().fromJSON(statesProvincesLinesTreeData); - -type BBox = [number, number, number, number]; - -export type SvgMapProps = { - width: number, - height: number, - center: [number, number], // longitude, latitude - offset: [number, number], // [x, y] in points - zoomLevel: number, - showMarker: boolean, - markerImagePath: string, -}; - -type SvgMapState = { - zoomCenter: [number, number], - zoomLevel: number, - visibleCities: Array<Object>, - visibleCountries: Array<Object>, - visibleGeometry: Array<Object>, - visibleStatesProvincesLines: Array<Object>, - viewportBbox: BBox, -}; - -const MOVE_SPEED = 2000; - -// @TODO: Calculate zoom level based on (center + span) (aka MKCoordinateSpan) -export default class SvgMap extends React.Component<SvgMapProps, SvgMapState> { - state = { - zoomCenter: [0, 0], - zoomLevel: 1, - visibleCities: [], - visibleCountries: [], - visibleGeometry: [], - visibleStatesProvincesLines: [], - viewportBbox: [0, 0, 0, 0], - }; - - _projectionConfig = { - scale: 160, - }; - - constructor(props: SvgMapProps) { - super(props); - - this.state = this._getNextState(null, props); - } - - componentWillReceiveProps(nextProps: SvgMapProps) { - if (this._shouldInvalidateState(nextProps)) { - this.setState((prevState) => this._getNextState(prevState, nextProps)); - } - } - - shouldComponentUpdate(nextProps: SvgMapProps, nextState: SvgMapState) { - return ( - this.props.width !== nextProps.width || - this.props.height !== nextProps.height || - this.props.center[0] !== nextProps.center[0] || - this.props.center[1] !== nextProps.center[1] || - this.props.offset[0] !== nextProps.offset[0] || - this.props.offset[1] !== nextProps.offset[1] || - this.props.zoomLevel !== nextProps.zoomLevel || - this.props.showMarker !== nextProps.showMarker || - this.props.markerImagePath !== nextProps.markerImagePath || - this.state.zoomCenter !== nextState.zoomCenter || - this.state.zoomLevel !== nextState.zoomLevel - ); - } - - render() { - const mapStyle = { - width: '100%', - height: '100%', - backgroundColor: '#192e45', - }; - - const zoomableGroupStyle = { - transition: `transform ${MOVE_SPEED}ms ease-in-out`, - }; - - const geographyStyle = this._mergeRsmStyle({ - default: { - fill: '#294d73', - stroke: '#192e45', - strokeWidth: `${1 / this.state.zoomLevel}`, - }, - }); - - const stateProvinceLineStyle = this._mergeRsmStyle({ - default: { - fill: 'transparent', - stroke: '#192e45', - strokeWidth: `${1 / this.state.zoomLevel}`, - }, - }); - - const markerStyle = this._mergeRsmStyle({ - default: { - transition: `transform ${MOVE_SPEED}ms ease-in-out`, - }, - }); - - // disable CSS transition when moving between locations - // by using the different "key" - const userMarker = this.props.showMarker && ( - <Marker - key={`user-location-${this.props.center.join('-')}`} - marker={{ coordinates: this.props.center }} - style={markerStyle}> - <image x="-30" y="-30" href={this.props.markerImagePath} /> - </Marker> - ); - - const countryMarkers = this.state.visibleCountries.map((item) => ( - <Marker - key={`country-${item.id}`} - marker={{ coordinates: item.geometry.coordinates }} - style={markerStyle}> - <text fill="rgba(255,255,255,.6)" fontSize="22" textAnchor="middle"> - {item.properties.name} - </text> - </Marker> - )); - - const cityMarkers = this.state.visibleCities.map((item) => ( - <Marker - key={`city-${item.id}`} - marker={{ coordinates: item.geometry.coordinates }} - style={markerStyle}> - <circle r="2" fill="rgba(255,255,255,.6)" /> - <text x="0" y="-10" fill="rgba(255,255,255,.6)" fontSize="16" textAnchor="middle"> - {item.properties.name} - </text> - </Marker> - )); - - return ( - <ComposableMap - width={this.props.width} - height={this.props.height} - style={mapStyle} - projection={this._getProjection} - projectionConfig={this._projectionConfig}> - <ZoomableGroup - center={this.state.zoomCenter} - zoom={this.state.zoomLevel} - disablePanning={false} - style={zoomableGroupStyle}> - <Geographies geography={geographyData} disableOptimization={true}> - {(geographies, projection) => { - return this.state.visibleGeometry.map(({ id }) => ( - <Geography - key={id} - geography={geographies[id]} - projection={projection} - style={geographyStyle} - /> - )); - }} - </Geographies> - <Geographies geography={statesProvincesLinesData} disableOptimization={true}> - {(geographies, projection) => { - return this.state.visibleStatesProvincesLines.map(({ id }) => ( - <Geography - key={id} - geography={geographies[id]} - projection={projection} - style={stateProvinceLineStyle} - /> - )); - }} - </Geographies> - <Markers>{[...countryMarkers, ...cityMarkers, userMarker]}</Markers> - </ZoomableGroup> - </ComposableMap> - ); - } - - _mergeRsmStyle(style: Object) { - const defaultStyle = style.default || {}; - return { - default: defaultStyle, - hover: style.hover || defaultStyle, - pressed: style.pressed || defaultStyle, - }; - } - - _getProjection( - width: number, - height: number, - config: { - scale?: number, - xOffset?: number, - yOffset?: number, - rotation?: [number, number, number], - precision?: number, - }, - ) { - const scale = config.scale || 160; - const xOffset = config.xOffset || 0; - const yOffset = config.yOffset || 0; - const rotation = config.rotation || [0, 0, 0]; - const precision = config.precision || 0.1; - - return geoTimes() - .scale(scale) - .translate([xOffset + width / 2, yOffset + height / 2]) - .rotate(rotation) - .precision(precision); - } - - _getZoomCenter( - center: [number, number], - offset: [number, number], - projection: Function, - zoom: number, - ) { - const pos = projection(center); - return projection.invert([pos[0] + offset[0] / zoom, pos[1] + offset[1] / zoom]); - } - - _getViewportGeoBoundingBox( - centerCoordinate: [number, number], - width: number, - height: number, - projection: Function, - zoom: number, - ) { - const center = projection(centerCoordinate); - const halfWidth = (width * 0.5) / zoom; - const halfHeight = (height * 0.5) / zoom; - - const northWest = projection.invert([center[0] - halfWidth, center[1] - halfHeight]); - const southEast = projection.invert([center[0] + halfWidth, center[1] + halfHeight]); - - // normalize to [minX, minY, maxX, maxY] - return [ - Math.min(northWest[0], southEast[0]), - Math.min(northWest[1], southEast[1]), - Math.max(northWest[0], southEast[0]), - Math.max(northWest[1], southEast[1]), - ]; - } - - _shouldInvalidateState(nextProps: SvgMapProps) { - const oldProps = this.props; - return ( - oldProps.width !== nextProps.width || - oldProps.height !== nextProps.height || - oldProps.center[0] !== nextProps.center[0] || - oldProps.center[1] !== nextProps.center[1] || - oldProps.offset[0] !== nextProps.offset[0] || - oldProps.offset[1] !== nextProps.offset[1] || - oldProps.zoomLevel !== nextProps.zoomLevel - ); - } - - _getNextState(prevState: ?SvgMapState, nextProps: SvgMapProps): SvgMapState { - const { width, height, center, offset, zoomLevel } = nextProps; - - const projection = this._getProjection(width, height, this._projectionConfig); - const zoomCenter = this._getZoomCenter(center, offset, projection, zoomLevel); - const viewportBbox = this._getViewportGeoBoundingBox( - zoomCenter, - width, - height, - projection, - zoomLevel, - ); - - const viewportBboxMatch = { - minX: viewportBbox[0], - minY: viewportBbox[1], - maxX: viewportBbox[2], - maxY: viewportBbox[3], - }; - - // combine previous and current viewports to get the rough area of transition - const combinedViewportBboxMatch = prevState - ? { - minX: Math.min(viewportBbox[0], prevState.viewportBbox[0]), - minY: Math.min(viewportBbox[1], prevState.viewportBbox[1]), - maxX: Math.max(viewportBbox[2], prevState.viewportBbox[2]), - maxY: Math.max(viewportBbox[3], prevState.viewportBbox[3]), - } - : { - minX: viewportBbox[0], - minY: viewportBbox[1], - maxX: viewportBbox[2], - maxY: viewportBbox[3], - }; - - const visibleCountries = - zoomLevel < 5 || zoomLevel > 20 ? [] : countryTree.search(viewportBboxMatch); - const visibleCities = zoomLevel >= 40 ? cityTree.search(viewportBboxMatch) : []; - const visibleGeometry = geometryTree.search(combinedViewportBboxMatch); - const visibleStatesProvincesLines = provincesStatesLinesTree.search(combinedViewportBboxMatch); - - return { - zoomCenter, - zoomLevel, - visibleCities, - visibleCountries, - visibleGeometry, - visibleStatesProvincesLines, - viewportBbox, - }; - } -} diff --git a/app/components/Switch.android.js b/app/components/Switch.android.js deleted file mode 100644 index cbf29c3ad9..0000000000 --- a/app/components/Switch.android.js +++ /dev/null @@ -1,25 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Switch as _Switch } from 'react-native'; - -export type SwitchProps = { - isOn: boolean, - onChange?: (isOn: boolean) => void, -}; - -type State = {}; - -export default class Switch extends React.Component<SwitchProps, State> { - static defaultProps: SwitchProps = { - isOn: false, - onChange: () => {}, - }; - - state = {}; - - render() { - const { isOn, ...otherProps } = this.props; - return <_Switch {...otherProps} value={isOn} onValueChange={this.props.onChange(isOn)} />; - } -} diff --git a/app/components/Switch.css b/app/components/Switch.css deleted file mode 100644 index aaa3821c34..0000000000 --- a/app/components/Switch.css +++ /dev/null @@ -1,44 +0,0 @@ -.switch { - display: block; - position: relative; - -webkit-appearance: none; - border-radius: 16px; - width: 52px; - height: 32px; - border: 2px solid white; - background-color: transparent; - transition: 300ms ease-in-out all; -} - -.switch:checked { - text-align: right; -} - -.switch::after { - position: absolute; - left: 2px; - top: 2px; - display: block; - content: ''; - width: 24px; - height: 24px; - border-radius: 24px; - background-color: #D0021B; - transition: 200ms ease-in-out all; - transform: translate3d(0, 0, 0); -} - -.switch:active::after { - width: 28px; -} - -.switch:active:checked::after { - transform: translate3d(0, 0, 0); - left: 18px; -} - -.switch:checked::after { - background-color: #44AD4D; - transform: translate3d(0, 0, 0); - left: 22px; -}
\ No newline at end of file diff --git a/app/components/Switch.js b/app/components/Switch.js deleted file mode 100644 index 4abb4a247f..0000000000 --- a/app/components/Switch.js +++ /dev/null @@ -1,150 +0,0 @@ -// @flow - -import * as React from 'react'; - -const CLICK_TIMEOUT = 1000; -const MOVE_THRESHOLD = 10; - -export type SwitchProps = { - className?: string, - isOn: boolean, - onChange: ?(isOn: boolean) => void, -}; - -type State = { - ignoreChange: boolean, - initialPos: { x: number, y: number }, - startTime: ?number, -}; - -export default class Switch extends React.Component<SwitchProps, State> { - static defaultProps: SwitchProps = { - isOn: false, - onChange: null, - }; - - state = { - ignoreChange: false, - initialPos: { x: 0, y: 0 }, - startTime: (null: ?number), - }; - - isCapturingMouseEvents = false; - ref: ?HTMLInputElement; - onRef = (e: ?HTMLInputElement) => (this.ref = e); - - handleMouseDown = (e: MouseEvent) => { - const { clientX: x, clientY: y } = e; - this.startCapturingMouseEvents(); - this.setState({ - initialPos: { x, y }, - startTime: e.timeStamp, - }); - }; - - handleMouseMove = (e: MouseEvent) => { - const inputElement = this.ref; - const { x: x0 } = this.state.initialPos; - const { clientX: x, clientY: y } = e; - const dx = Math.abs(x0 - x); - - if (dx < MOVE_THRESHOLD) { - return; - } - - const isOn = !!this.props.isOn; - let nextOn = isOn; - - if (x < x0 && isOn) { - nextOn = false; - } else if (x > x0 && !isOn) { - nextOn = true; - } - - if (isOn !== nextOn) { - this.setState({ - initialPos: { x, y }, - ignoreChange: true, - }); - - if (inputElement) { - inputElement.checked = nextOn; - } - - this.notify(nextOn); - } - }; - - handleMouseUp = () => { - this.stopCapturingMouseEvents(); - }; - - handleChange = (e: Event) => { - const startTime = this.state.startTime; - const eventTarget: Object = e.target; - - if (typeof startTime !== 'number') { - throw new Error('startTime must be a number.'); - } - - const dt = e.timeStamp - startTime; - - if (this.state.ignoreChange) { - this.setState({ ignoreChange: false }); - e.preventDefault(); - } else if (dt > CLICK_TIMEOUT) { - e.preventDefault(); - } else { - this.notify(eventTarget.checked); - } - }; - - notify(isOn: boolean) { - const onChange = this.props.onChange; - if (onChange) { - onChange(isOn); - } - } - - startCapturingMouseEvents() { - if (this.isCapturingMouseEvents) { - throw new Error('startCapturingMouseEvents() is called out of order.'); - } - document.addEventListener('mousemove', this.handleMouseMove); - document.addEventListener('mouseup', this.handleMouseUp); - this.isCapturingMouseEvents = true; - } - - stopCapturingMouseEvents() { - if (!this.isCapturingMouseEvents) { - throw new Error('stopCapturingMouseEvents() is called out of order.'); - } - document.removeEventListener('mousemove', this.handleMouseMove); - document.removeEventListener('mouseup', this.handleMouseUp); - this.isCapturingMouseEvents = false; - } - - componentWillUnmount() { - // guard from abrupt programmatic unmount - if (this.isCapturingMouseEvents) { - this.stopCapturingMouseEvents(); - } - } - - render() { - // eslint-disable-next-line no-unused-vars - const { isOn, onChange, ...otherProps } = this.props; - const className = ('switch ' + (otherProps.className || '')).trim(); - return ( - <input - {...otherProps} - type="checkbox" - ref={this.onRef} - className={className} - checked={isOn} - onMouseDown={this.handleMouseDown} - onChange={this.handleChange} - /> - ); - } -} diff --git a/app/components/TransitionContainer.js b/app/components/TransitionContainer.js deleted file mode 100644 index 2c1fb09995..0000000000 --- a/app/components/TransitionContainer.js +++ /dev/null @@ -1,201 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Styles, Component, Animated, View, Types, UserInterface } from 'reactxp'; -import type { TransitionGroupProps } from '../transitions'; -import getStyles from './TransitionContainerStyles'; - -type TransitionContainerProps = { - children: React.Node, - ...TransitionGroupProps, -}; - -type State = { - previousChildren: ?React.Node, - childrenAnimation: Types.AnimatedViewStyleRuleSet, - previousChildrenAnimation: Types.AnimatedViewStyleRuleSet, - animationStyles: Types.AnimatedViewStyleRuleSet, - dimensions: Types.Dimensions, -}; - -export default class TransitionContainer extends Component<TransitionContainerProps, State> { - constructor(props: TransitionContainerProps) { - super(props); - - const dimensions = UserInterface.measureWindow(); - - this.state = { - dimensions, - }; - } - - componentWillReceiveProps(nextProps: TransitionContainerProps) { - switch (nextProps.name) { - case 'slide-up': - this.slideUpTransition(nextProps); - break; - case 'slide-down': - this.slideDownTransition(nextProps); - break; - case 'push': - this.pushTransition(nextProps); - break; - case 'pop': - this.popTransition(nextProps); - break; - default: - break; - } - } - - onFinishedAnimation() { - this.setState({ - childrenAnimation: getStyles().allowPointerEventsStyle, - previousChildren: null, - }); - } - - slideUpTransition(nextProps: TransitionContainerProps) { - const currentTranslationValue = Animated.createValue(this.state.dimensions.height); - this.setState( - { - previousChildren: this.props.children, - childrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 1, - transform: [{ translateY: currentTranslationValue }], - }), - previousChildrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 0, - transform: [{ translateY: 0 }], - }), - }, - () => { - Animated.timing(currentTranslationValue, { - toValue: 0, - easing: Animated.Easing.InOut(), - duration: nextProps.duration, - }).start(() => this.onFinishedAnimation()); - }, - ); - } - - slideDownTransition(nextProps: TransitionContainerProps) { - const previousTranslationValue = Animated.createValue(0); - this.setState( - { - previousChildren: this.props.children, - childrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 0, - transform: [{ translateY: 0 }], - }), - previousChildrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 1, - transform: [{ translateY: previousTranslationValue }], - }), - }, - () => { - Animated.timing(previousTranslationValue, { - toValue: this.state.dimensions.height, - easing: Animated.Easing.InOut(), - duration: nextProps.duration, - }).start(() => this.onFinishedAnimation()); - }, - ); - } - - pushTransition(nextProps: TransitionContainerProps) { - const currentTranslationValue = Animated.createValue(this.state.dimensions.width); - const previousTranslationValue = Animated.createValue(0); - this.setState( - { - previousChildren: this.props.children, - childrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 1, - transform: [{ translateX: currentTranslationValue }], - }), - previousChildrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 0, - transform: [{ translateX: previousTranslationValue }], - }), - }, - () => { - const compositeAnimation = Animated.parallel([ - Animated.timing(currentTranslationValue, { - toValue: 0, - easing: Animated.Easing.InOut(), - duration: nextProps.duration, - }), - Animated.timing(previousTranslationValue, { - toValue: -this.state.dimensions.width / 2, - easing: Animated.Easing.InOut(), - duration: nextProps.duration, - }), - ]); - compositeAnimation.start(() => this.onFinishedAnimation()); - }, - ); - } - - popTransition(nextProps: TransitionContainerProps) { - const currentTranslationValue = Animated.createValue(-this.state.dimensions.width / 2); - const previousTranslationValue = Animated.createValue(0); - this.setState( - { - previousChildren: this.props.children, - childrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 0, - transform: [{ translateX: currentTranslationValue }], - }), - previousChildrenAnimation: Styles.createAnimatedViewStyle({ - pointerEvents: 'none', - zIndex: 1, - transform: [{ translateX: previousTranslationValue }], - }), - }, - () => { - const compositeAnimation = Animated.parallel([ - Animated.timing(currentTranslationValue, { - toValue: 0, - easing: Animated.Easing.InOut(), - duration: nextProps.duration, - }), - Animated.timing(previousTranslationValue, { - toValue: this.state.dimensions.width, - easing: Animated.Easing.InOut(), - duration: nextProps.duration, - }), - ]); - compositeAnimation.start(() => this.onFinishedAnimation()); - }, - ); - } - - render() { - const { children } = this.props; - const { previousChildren, childrenAnimation, previousChildrenAnimation } = this.state; - return ( - <View style={getStyles().transitionContainerStyle}> - {previousChildren && ( - <Animated.View - key={previousChildren && previousChildren.key} - style={[getStyles().animationDefaultStyle, previousChildrenAnimation]}> - {previousChildren} - </Animated.View> - )} - - <Animated.View - key={children.key} - style={[getStyles().animationDefaultStyle, childrenAnimation]}> - {children} - </Animated.View> - </View> - ); - } -} diff --git a/app/components/TransitionContainerStyles.android.js b/app/components/TransitionContainerStyles.android.js deleted file mode 100644 index d69980ab43..0000000000 --- a/app/components/TransitionContainerStyles.android.js +++ /dev/null @@ -1,38 +0,0 @@ -// @flow - -import { Styles, UserInterface } from 'reactxp'; -import { MobileAppBridge } from 'NativeModules'; -import { log } from '../lib/platform'; - -const dimensions = UserInterface.measureWindow(); -let menuBarHeight; - -MobileAppBridge.getMenuBarHeight() - .then((_response) => { - menuBarHeight = _response; - return; - }) - .catch((e) => { - log.error('Failed getting menuBarHeight:', e); - }); - -export default () => { - return { - animationDefaultStyle: Styles.createAnimatedViewStyle( - { - position: 'absolute', - width: dimensions.width, - height: dimensions.height - menuBarHeight + 24, - }, - false, - ), - allowPointerEventsStyle: null, - transitionContainerStyle: Styles.createViewStyle( - { - width: dimensions.width, - height: dimensions.height - menuBarHeight + 24, //TODO: Remove ugly hack since it seems that at least my LG is seems hard to find the real display area ... Probably needs to be fixed for some versions or models - }, - false, - ), - }; -}; diff --git a/app/components/TransitionContainerStyles.js b/app/components/TransitionContainerStyles.js deleted file mode 100644 index ca95cabfee..0000000000 --- a/app/components/TransitionContainerStyles.js +++ /dev/null @@ -1,23 +0,0 @@ -// @flow - -import { Styles, UserInterface } from 'reactxp'; - -const dimensions = UserInterface.measureWindow(); -const styles = { - animationDefaultStyle: Styles.createAnimatedViewStyle({ - position: 'absolute', - width: dimensions.width, - height: dimensions.height, - }), - allowPointerEventsStyle: Styles.createAnimatedViewStyle({ - pointerEvents: 'auto', - }), - transitionContainerStyle: Styles.createViewStyle({ - width: dimensions.width, - height: dimensions.height, - }), -}; - -export default () => { - return styles; -}; |
