Skip to content

Commit

Permalink
Merge branch 'main' into ionatan_capitalization
Browse files Browse the repository at this point in the history
  • Loading branch information
iwiznia authored Aug 27, 2021
2 parents 1611e49 + 1f332a6 commit 6d2cdeb
Show file tree
Hide file tree
Showing 23 changed files with 286 additions and 91 deletions.
12 changes: 11 additions & 1 deletion src/CONST.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/components/ExpensiTextInput/index.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ const ExpensiTextInput = forwardRef((props, ref) => (
<BaseExpensiTextInput
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}

// Setting autoCompleteType to new-password throws an error on Android, so fall back to password in that case
// eslint-disable-next-line react/jsx-props-no-multi-spaces
autoCompleteType={props.autoCompleteType === 'new-password' ? 'password' : props.autoCompleteType}
innerRef={ref}
inputStyle={[
styles.expensiTextInput,
Expand Down
4 changes: 4 additions & 0 deletions src/components/ExpensiTextInput/index.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ const ExpensiTextInput = forwardRef((props, ref) => (
<BaseExpensiTextInput
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}

// Setting autoCompleteType to new-password throws an error on iOS, so fall back to password in that case
// eslint-disable-next-line react/jsx-props-no-multi-spaces
autoCompleteType={props.autoCompleteType === 'new-password' ? 'password' : props.autoCompleteType}
innerRef={ref}
inputStyle={[styles.expensiTextInput]}
/>
Expand Down
103 changes: 96 additions & 7 deletions src/components/ImageView/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {View, Image, Pressable} from 'react-native';
import {
View, Image, Pressable,
} from 'react-native';
import styles, {getZoomCursorStyle, getZoomSizingStyle} from '../../styles/styles';
import canUseTouchScreen from '../../libs/canUseTouchscreen';
import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions';
import compose from '../../libs/compose';

const propTypes = {
/** URL to full-sized image */
url: PropTypes.string.isRequired,
...windowDimensionsPropTypes,
};

class ImageView extends PureComponent {
Expand All @@ -22,14 +27,24 @@ class ImageView extends PureComponent {
initialScrollTop: 0,
initialX: 0,
initialY: 0,
imgWidth: 0,
imgHeight: 0,
zoomScale: 0,
imageLeft: 0,
imageTop: 0,
imageRight: 0,
imageBottom: 0,
};
}

componentDidMount() {
if (this.canUseTouchScreen) {
return;
}

Image.getSize(this.props.url, (width, height) => {
const scale = Math.max(this.props.windowWidth / width, this.props.windowHeight / height);
this.setImageRegion(width, height, scale);
});
document.addEventListener('mousemove', this.trackMovement.bind(this));
}

Expand All @@ -41,6 +56,75 @@ class ImageView extends PureComponent {
document.removeEventListener('mousemove', this.trackMovement.bind(this));
}

/**
* When open image, set image left/right/top/bottom point and width, height
* @param {Boolean} width image width
* @param {Boolean} height image height
* @param {Boolean} scale zoomscale when click zoom
*/
setImageRegion(width, height, scale) {
let imgLeft = (this.props.windowWidth - width) / 2;
let imgRight = ((this.props.windowWidth - width) / 2) + width;
let imgTop = (this.props.windowHeight - height) / 2;
let imgBottom = ((this.props.windowHeight - height) / 2) + height;
const isScreenWiderThanImage = (this.props.windowWidth / width) > 1;
const isScreenTallerThanImage = (this.props.windowHeight / height) > 1;
const aspect = width / height;
if (aspect > 1 && !isScreenWiderThanImage) {
// In case Width fit Screen width and Height not fit the Screen height
const fitRate = this.props.windowWidth / width;
imgLeft = 0;
imgRight = this.props.windowWidth;
imgTop = (this.props.windowHeight - (fitRate * height)) / 2;
imgBottom = imgTop + (fitRate * height);
} else if (aspect <= 1 && !isScreenTallerThanImage) {
// In case Height fit Screen height and Width not fit the Screen width
const fitRate = this.props.windowHeight / height;
imgTop = 0;
imgBottom = this.props.windowHeight;
imgLeft = (this.props.windowWidth - (fitRate * width)) / 2;
imgRight = imgLeft + (fitRate * width);
}

this.setState({
imgWidth: width, imgHeight: height, zoomScale: scale, imageLeft: imgLeft, imageTop: imgTop, imageRight: imgRight, imageBottom: imgBottom,
});
}

/**
* Convert touch point to zoomed point
* @param {Boolean} x x point when click zoom
* @param {Boolean} y y point when click zoom
* @returns {Object} converted touch point
*/
getScrollOffset(x, y) {
let fitRatio = 1;
if (this.state.imageTop === 0) {
// Fit Height
fitRatio = this.props.windowHeight / this.state.imgHeight;
} else if (this.state.imageLeft === 0) {
// Fit Width
fitRatio = this.props.windowWidth / this.state.imgWidth;
}
let sx = (x - this.state.imageLeft) / fitRatio;
let sy = (y - this.state.imageTop) / fitRatio;

// White blank touch
if (x < this.state.imageLeft) {
sx = 0;
}
if (x > this.state.imageRight) {
sx = this.state.imgWidth;
}
if (y < this.state.imageTop) {
sy = 0;
}
if (y > this.state.imageBottom) {
sy = this.state.imgHeight;
}
return {offsetX: sx, offsetY: sy};
}

trackMovement(e) {
if (!this.state.isZoomed) {
return;
Expand Down Expand Up @@ -105,8 +189,11 @@ class ImageView extends PureComponent {
onPress={(e) => {
if (this.state.isZoomed && !this.state.isDragging) {
const {offsetX, offsetY} = e.nativeEvent;
this.scrollableRef.scrollTop = offsetY * 1.5;
this.scrollableRef.scrollLeft = offsetX * 1.5;
const delta = this.getScrollOffset(offsetX, offsetY);
const sX = delta.offsetX;
const sY = delta.offsetY;
this.scrollableRef.scrollTop = sY * this.state.zoomScale;
this.scrollableRef.scrollLeft = sX * this.state.zoomScale;
}

if (this.state.isZoomed && this.state.isDragging && this.state.isMouseDown) {
Expand All @@ -126,8 +213,8 @@ class ImageView extends PureComponent {
>
<Image
source={{uri: this.props.url}}
style={getZoomSizingStyle(this.state.isZoomed)}
resizeMode="center"
style={getZoomSizingStyle(this.state.isZoomed, this.state.imgWidth, this.state.imgHeight, this.state.zoomScale)}
resizeMode="contain"
/>
</Pressable>
</View>
Expand All @@ -136,4 +223,6 @@ class ImageView extends PureComponent {
}

ImageView.propTypes = propTypes;
export default ImageView;
export default compose(
withWindowDimensions,
)(ImageView);
4 changes: 4 additions & 0 deletions src/components/Picker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const Picker = ({
fixAndroidTouchableBug
onOpen={onOpen}
onClose={onClose}
pickerProps={{
onFocus: onOpen,
onBlur: onClose,
}}
/>
);

Expand Down
19 changes: 18 additions & 1 deletion src/components/Picker/pickerStyles/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
import CONST from '../../../CONST';
import getBrowser from '../../../libs/getBrowser';
import styles from '../../../styles/styles';

const pickerStyles = disabled => styles.expensiPicker(disabled);
const pickerStylesWeb = () => {
if (CONST.BROWSER.FIREFOX === getBrowser()) {
return {
textIndent: -2,
};
}
return {};
};

const pickerStyles = disabled => ({
...styles.expensiPicker(disabled),
inputWeb: {
...styles.expensiPicker(disabled).inputWeb,
...pickerStylesWeb(),
},
});

export default pickerStyles;
10 changes: 6 additions & 4 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -601,13 +601,15 @@ export default {
},
invite: {
invitePeople: 'Invite people',
invitePeoplePrompt: 'Invite a colleague to your workspace.',
invitePeoplePrompt: 'Invite colleagues to your workspace.',
personalMessagePrompt: 'Add a personal message (optional)',
enterEmailOrPhone: 'Email or phone',
enterEmailOrPhone: 'Emails or phone numbers',
EmailOrPhonePlaceholder: 'Enter comma-separated list of emails or phone numbers',
pleaseEnterValidLogin: 'Please ensure the email or phone number is valid (e.g. +15005550006).',
pleaseEnterUniqueLogin: 'That user is already a member of this workspace.',
genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.',
welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} workspace! Download the Expensify mobile App to start tracking your expenses.`,
systemUserError: ({email}) => `Sorry, you cannot invite ${email} to a workspace.`,
welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} workspace! Download the Expensify mobile app to start tracking your expenses.`,
},
editor: {
title: 'Edit workspace',
Expand All @@ -618,7 +620,7 @@ export default {
avatarUploadFailureMessage: 'An error occurred uploading the avatar, please try again.',
},
error: {
growlMessageInvalidPolicy: 'Invalid workspace! A new workspace has been created.',
growlMessageInvalidPolicy: 'Invalid workspace!',
},
},
requestCallPage: {
Expand Down
8 changes: 5 additions & 3 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -603,12 +603,14 @@ export default {
},
invite: {
invitePeople: 'Invitar a la gente',
invitePeoplePrompt: 'Invita a un colega a tu espacio de trabajo.',
invitePeoplePrompt: 'Invita a tus compañeros a tu espacio de trabajo.',
personalMessagePrompt: 'Agregar un mensaje personal (Opcional)',
enterEmailOrPhone: 'Email o teléfono',
enterEmailOrPhone: 'Correos electrónicos o números de teléfono',
EmailOrPhonePlaceholder: 'Introduce una lista de correos electrónicos o números de teléfono separado por comas',
pleaseEnterValidLogin: 'Asegúrese de que el correo electrónico o el número de teléfono sean válidos (e.g. +15005550006).',
pleaseEnterUniqueLogin: 'Ese usuario ya es miembro de este espacio de trabajo.',
genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..',
systemUserError: ({email}) => `Lo sentimos, no puedes invitar a ${email} a un espacio de trabajo.`,
welcomeNote: ({workspaceName}) => `¡Has sido invitado a la ${workspaceName} Espacio de trabajo! Descargue la aplicación móvil Expensify para comenzar a rastrear sus gastos.`,
},
editor: {
Expand All @@ -620,7 +622,7 @@ export default {
avatarUploadFailureMessage: 'No se pudo subir el avatar. Por favor, inténtalo de nuevo.',
},
error: {
growlMessageInvalidPolicy: '¡Espacio de trabajo no válido! Un nuevo espacio de trabajo ha sido creado.',
growlMessageInvalidPolicy: '¡Espacio de trabajo no válido!',
},
},
requestCallPage: {
Expand Down
4 changes: 3 additions & 1 deletion src/libs/actions/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ function setCurrentURL(url) {
* @param {String} locale
*/
function setLocale(locale) {
API.PreferredLocale_Update({name: 'preferredLocale', value: locale});
if (currentUserAccountID) {
API.PreferredLocale_Update({name: 'preferredLocale', value: locale});
}
Onyx.merge(ONYXKEYS.NVP_PREFERRED_LOCALE, locale);
}

Expand Down
1 change: 1 addition & 0 deletions src/libs/getBrowser/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => '';
42 changes: 42 additions & 0 deletions src/libs/getBrowser/index.web.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import CONST from '../../CONST';

/**
* Fetch browser name from UA string
*
* @return {String} e.g. Chrome
*/
function getBrowser() {
const {userAgent} = window.navigator;
let match = userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))/i) || [];
let temp;

if (/trident/i.test(match[1])) {
return 'IE';
}

if (match[1] && (match[1].toLowerCase() === 'chrome')) {
temp = userAgent.match(/\b(OPR|Edge)\/(\d+)/);

if (temp !== null) {
return temp.slice(1).replace('OPR', 'Opera');
}

temp = userAgent.match(/\b(Edg)\/(\d+)/);

if (temp !== null) {
return temp.slice(1).replace('Edg', 'Edge');
}
}

match = match[1] ? match[1] : navigator.appName;
return match;
}

/**
* Get the Browser name
* @returns {String}
*/
export default () => {
const browser = getBrowser();
return browser ? browser.toLowerCase() : CONST.BROWSER.OTHER;
};
20 changes: 20 additions & 0 deletions src/libs/userUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import CONST from '../CONST';

/**
* Whether a login is system email
*
* @param {String} login - user email
* @return {Boolean}
*/
function isSystemUser(login) {
return [
CONST.EMAIL.CONCIERGE,
CONST.EMAIL.CHRONOS,
CONST.EMAIL.RECEIPTS,
].includes(login);
}

export {
// eslint-disable-next-line import/prefer-default-export
isSystemUser,
};
7 changes: 5 additions & 2 deletions src/pages/ReimbursementAccount/CompanyStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,6 @@ class CompanyStep extends React.Component {
: ''}
/>
<ExpensiTextInput
autoCompleteType="password"
label={`Expensify ${this.props.translate('common.password')}`}
containerStyles={[styles.mt4]}
secureTextEntry
Expand All @@ -315,10 +314,14 @@ class CompanyStep extends React.Component {
this.setState({password});
}}
value={this.state.password}
onSubmitEditing={this.submit}
onSubmitEditing={shouldDisableSubmitButton ? undefined : this.submit}
errorText={error === this.props.translate('common.passwordCannotBeBlank')
? this.props.translate('common.passwordCannotBeBlank')
: ''}

// Use new-password to prevent an autoComplete bug https://github.com/Expensify/Expensify/issues/173177
// eslint-disable-next-line react/jsx-props-no-multi-spaces
autoCompleteType="new-password"
/>
<CheckboxWithLabel
isChecked={this.state.hasNoConnectionToCannabis}
Expand Down
Loading

0 comments on commit 6d2cdeb

Please sign in to comment.