diff --git a/android/app/build.gradle b/android/app/build.gradle index 51f781835403..85420386f821 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -148,8 +148,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001005000 - versionName "1.0.50-0" + versionCode 1001005001 + versionName "1.0.50-1" } splits { abi { diff --git a/assets/images/exclamation.svg b/assets/images/exclamation.svg new file mode 100644 index 000000000000..0d2e32a463d9 --- /dev/null +++ b/assets/images/exclamation.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist index bc4585b43622..724ae52960b7 100644 --- a/ios/ExpensifyCash/Info.plist +++ b/ios/ExpensifyCash/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1.0.50.0 + 1.0.50.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes @@ -66,7 +66,7 @@ NSCameraUsageDescription Your camera is used to create chat attachments. NSLocationWhenInUseUsageDescription - + Your location is used to determine your default currency. NSPhotoLibraryAddUsageDescription Your camera roll is used to store chat attachments. NSPhotoLibraryUsageDescription diff --git a/ios/ExpensifyCashTests/Info.plist b/ios/ExpensifyCashTests/Info.plist index e46fd818362e..5a077ca69029 100644 --- a/ios/ExpensifyCashTests/Info.plist +++ b/ios/ExpensifyCashTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.0.50.0 + 1.0.50.1 diff --git a/package-lock.json b/package-lock.json index cfd67f8f002a..c4096c976c4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.50-0", + "version": "1.0.50-1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a2dacfa8a45a..b1d5cb73007a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.50-0", + "version": "1.0.50-1", "author": "Expensify, Inc.", "homepage": "https://expensify.cash", "description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js index 75c39f6bdac7..bbaf5a5d5947 100644 --- a/src/components/Icon/Expensicons.js +++ b/src/components/Icon/Expensicons.js @@ -1,65 +1,74 @@ +import Android from '../../../assets/images/android.svg'; +import Apple from '../../../assets/images/apple.svg'; +import ArrowRight from '../../../assets/images/arrow-right.svg'; import BackArrow from '../../../assets/images/back-left.svg'; +import Bug from '../../../assets/images/bug.svg'; +import Camera from '../../../assets/images/camera.svg'; import ChatBubble from '../../../assets/images/chatbubble.svg'; +import Checkmark from '../../../assets/images/checkmark.svg'; import Clipboard from '../../../assets/images/clipboard.svg'; import Close from '../../../assets/images/close.svg'; +import DownArrow from '../../../assets/images/down.svg'; +import Download from '../../../assets/images/download.svg'; +import Emoji from '../../../assets/images/emoji.svg'; +import Exclamation from '../../../assets/images/exclamation.svg'; +import Eye from '../../../assets/images/eye.svg'; +import Gallery from '../../../assets/images/gallery.svg'; +import Gear from '../../../assets/images/gear.svg'; +import Info from '../../../assets/images/info.svg'; +import Link from '../../../assets/images/link.svg'; import LinkCopy from '../../../assets/images/link-copy.svg'; +import Lock from '../../../assets/images/lock.svg'; import MagnifyingGlass from '../../../assets/images/magnifyingglass.svg'; import Mail from '../../../assets/images/mail.svg'; +import MoneyBag from '../../../assets/images/money-bag.svg'; +import MoneyCircle from '../../../assets/images/money-circle.svg'; +import Monitor from '../../../assets/images/monitor.svg'; +import NewWindow from '../../../assets/images/new-window.svg'; +import Offline from '../../../assets/images/offline.svg'; import Paperclip from '../../../assets/images/paperclip.svg'; import Pencil from '../../../assets/images/pencil.svg'; import Phone from '../../../assets/images/phone.svg'; import Pin from '../../../assets/images/pin.svg'; import PinCircle from '../../../assets/images/pin-circle.svg'; import Plus from '../../../assets/images/plus.svg'; +import Profile from '../../../assets/images/profile.svg'; +import Receipt from '../../../assets/images/receipt.svg'; import Send from '../../../assets/images/send.svg'; +import SignOut from '../../../assets/images/sign-out.svg'; import Trashcan from '../../../assets/images/trashcan.svg'; import Users from '../../../assets/images/users.svg'; -import Checkmark from '../../../assets/images/checkmark.svg'; -import Receipt from '../../../assets/images/receipt.svg'; -import MoneyCircle from '../../../assets/images/money-circle.svg'; -import Download from '../../../assets/images/download.svg'; -import DownArrow from '../../../assets/images/down.svg'; -import Profile from '../../../assets/images/profile.svg'; -import Gear from '../../../assets/images/gear.svg'; -import Wallet from '../../../assets/images/wallet.svg'; -import Lock from '../../../assets/images/lock.svg'; -import ArrowRight from '../../../assets/images/arrow-right.svg'; -import Emoji from '../../../assets/images/emoji.svg'; import Upload from '../../../assets/images/upload.svg'; -import Camera from '../../../assets/images/camera.svg'; -import Gallery from '../../../assets/images/gallery.svg'; -import Offline from '../../../assets/images/offline.svg'; -import SignOut from '../../../assets/images/sign-out.svg'; -import Info from '../../../assets/images/info.svg'; -import Link from '../../../assets/images/link.svg'; -import Eye from '../../../assets/images/eye.svg'; -import Android from '../../../assets/images/android.svg'; -import Apple from '../../../assets/images/apple.svg'; -import Bug from '../../../assets/images/bug.svg'; -import MoneyBag from '../../../assets/images/money-bag.svg'; -import Monitor from '../../../assets/images/monitor.svg'; -import NewWindow from '../../../assets/images/new-window.svg'; +import Wallet from '../../../assets/images/wallet.svg'; export { Android, Apple, ArrowRight, BackArrow, - DownArrow, + Bug, Camera, ChatBubble, Checkmark, Clipboard, Close, + DownArrow, Download, Emoji, + Exclamation, + Eye, Gallery, Gear, + Info, + Link, LinkCopy, Lock, MagnifyingGlass, Mail, + MoneyBag, MoneyCircle, + Monitor, + NewWindow, Offline, Paperclip, Pencil, @@ -70,16 +79,9 @@ export { Profile, Receipt, Send, + SignOut, Trashcan, Upload, Users, Wallet, - SignOut, - Info, - Link, - Eye, - Bug, - MoneyBag, - Monitor, - NewWindow, }; diff --git a/src/languages/en.js b/src/languages/en.js index 9fe9d373cedd..23206b2960cd 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -133,6 +133,7 @@ export default { emailAddress: 'Email Address', setMyTimezoneAutomatically: 'Set my timezone automatically', timezone: 'Timezone', + growlMessageOnSave: 'Your profile was successfully saved', }, addSecondaryLoginPage: { addPhoneNumber: 'Add Phone Number', diff --git a/src/libs/GrowlNotification/GrowlNotificationContainer/GrowlNotificationContainerPropTypes.js b/src/libs/GrowlNotification/GrowlNotificationContainer/GrowlNotificationContainerPropTypes.js new file mode 100644 index 000000000000..fb5d017e984a --- /dev/null +++ b/src/libs/GrowlNotification/GrowlNotificationContainer/GrowlNotificationContainerPropTypes.js @@ -0,0 +1,15 @@ +import {Animated} from 'react-native'; +import PropTypes from 'prop-types'; +import {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; + +const propTypes = { + /** GrowlNotification content */ + children: PropTypes.node.isRequired, + + /** GrowlNotification Y postion, required to show or hide with fling animation */ + translateY: PropTypes.instanceOf(Animated.Value).isRequired, + + ...windowDimensionsPropTypes, +}; + +export default propTypes; diff --git a/src/libs/GrowlNotification/GrowlNotificationContainer/index.js b/src/libs/GrowlNotification/GrowlNotificationContainer/index.js new file mode 100644 index 000000000000..82b0b68cbf6a --- /dev/null +++ b/src/libs/GrowlNotification/GrowlNotificationContainer/index.js @@ -0,0 +1,22 @@ +import React from 'react'; +import {Animated} from 'react-native'; +import styles from '../../../styles/styles'; +import withWindowDimensions from '../../../components/withWindowDimensions'; +import propTypes from './GrowlNotificationContainerPropTypes'; + +const GrowlNotificationContainer = ({children, translateY, isSmallScreenWidth}) => ( + + {children} + +); + +GrowlNotificationContainer.propTypes = propTypes; + +export default withWindowDimensions(GrowlNotificationContainer); diff --git a/src/libs/GrowlNotification/GrowlNotificationContainer/index.native.js b/src/libs/GrowlNotification/GrowlNotificationContainer/index.native.js new file mode 100644 index 000000000000..d6d5a0bc0e8b --- /dev/null +++ b/src/libs/GrowlNotification/GrowlNotificationContainer/index.native.js @@ -0,0 +1,19 @@ +import React from 'react'; +import {Animated} from 'react-native'; +import styles from '../../../styles/styles'; +import propTypes from './GrowlNotificationContainerPropTypes'; + +const GrowlNotificationContainer = ({children, translateY}) => ( + + {children} + +); + +GrowlNotificationContainer.propTypes = propTypes; + +export default GrowlNotificationContainer; diff --git a/src/libs/GrowlNotification/index.js b/src/libs/GrowlNotification/index.js new file mode 100644 index 000000000000..16f232b498aa --- /dev/null +++ b/src/libs/GrowlNotification/index.js @@ -0,0 +1,104 @@ +import React, {Component} from 'react'; +import { + Text, View, Animated, +} from 'react-native'; +import { + Directions, FlingGestureHandler, State, TouchableWithoutFeedback, +} from 'react-native-gesture-handler'; +import colors from '../../styles/colors'; +import Icon from '../../components/Icon'; +import {Checkmark, Exclamation} from '../../components/Icon/Expensicons'; +import styles from '../../styles/styles'; +import GrowlNotificationContainer from './GrowlNotificationContainer'; + +const types = { + success: { + icon: Checkmark, + iconColor: colors.green, + }, + error: { + icon: Exclamation, + iconColor: colors.red, + }, + warning: { + icon: Exclamation, + iconColor: colors.yellow, + }, +}; + +const INACTIVE_POSITION_Y = -255; + +class GrowlNotification extends Component { + constructor() { + super(); + + this.state = { + bodyText: '', + type: 'success', + translateY: new Animated.Value(INACTIVE_POSITION_Y), + }; + + this.show = this.show.bind(this); + this.fling = this.fling.bind(this); + } + + /** + * Show the growl notification + * + * @param {String} bodyText + * @param {String} type + * @param {Number} duration - 2000 + */ + show(bodyText, type, duration = 2000) { + this.setState({ + bodyText, + type, + }, () => { + this.fling(0); + setTimeout(() => { + this.fling(INACTIVE_POSITION_Y); + }, duration); + }); + } + + /** + * Animate growl notification + * + * @param {Number} val + */ + fling(val = INACTIVE_POSITION_Y) { + Animated.spring(this.state.translateY, { + toValue: val, + duration: 80, + useNativeDriver: true, + }).start(); + } + + render() { + return ( + { + if (nativeEvent.state === State.ACTIVE) { + this.fling(INACTIVE_POSITION_Y); + } + }} + > + + + + + + {this.state.bodyText} + + + + + + + + ); + } +} + +export default GrowlNotification; diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index ad94e99e0c27..00ea71e257dd 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -29,6 +29,7 @@ import CreateMenu from '../../../components/CreateMenu'; import Picker from '../../../components/Picker'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import compose from '../../../libs/compose'; +import GrowlNotification from '../../../libs/GrowlNotification'; import Button from '../../../components/Button'; const propTypes = { @@ -130,6 +131,7 @@ class ProfilePage extends Component { this.setAutomaticTimezone = this.setAutomaticTimezone.bind(this); this.getLogins = this.getLogins.bind(this); this.createMenuItems = this.createMenuItems.bind(this); + this.growlNotification = undefined; } componentDidUpdate(prevProps) { @@ -208,6 +210,8 @@ class ProfilePage extends Component { selected: selectedTimezone, }, }); + + this.growlNotification.show(this.props.translate('profilePage.growlMessageOnSave'), 'success', 3000); } /** @@ -257,6 +261,7 @@ class ProfilePage extends Component { return ( + this.growlNotification = el} /> ({ + transform: [{translateY: y}], + }), + + growlNotificationBox: { + backgroundColor: colors.dark, + borderRadius: variables.componentBorderRadiusNormal, + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + shadowColor: '#000', + ...spacing.p5, + }, + + growlNotificationText: { + fontSize: variables.fontSizeNormal, + fontFamily: fontFamily.GTA, + width: '90%', + lineHeight: variables.fontSizeNormalHeight, + color: themeColors.textReversed, + }, + blockquote: { borderLeftColor: themeColors.border, borderLeftWidth: 4, diff --git a/src/styles/utilities/sizing.js b/src/styles/utilities/sizing.js index a57aa02d8549..67bba032ecc3 100644 --- a/src/styles/utilities/sizing.js +++ b/src/styles/utilities/sizing.js @@ -15,4 +15,8 @@ export default { w50: { width: '50%', }, + + mwn: { + maxWidth: 'auto', + }, };