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',
+ },
};