diff --git a/assets/images/hashtag.svg b/assets/images/hashtag.svg
new file mode 100644
index 000000000000..a9d4286599b1
--- /dev/null
+++ b/assets/images/hashtag.svg
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/CONST.js b/src/CONST.js
index 83922e707316..ee0fd890e486 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -219,6 +219,7 @@ const CONST = {
POLICY_ANNOUNCE: 'policyAnnounce',
POLICY_ADMINS: 'policyAdmins',
DOMAIN_ALL: 'domainAll',
+ POLICY_ROOM: 'policyRoom',
},
STATE_NUM: {
OPEN: 0,
@@ -230,7 +231,12 @@ const CONST = {
DAILY: 'daily',
ALWAYS: 'always',
},
+ VISIBILITY: {
+ RESTRICTED: 'restricted',
+ PRIVATE: 'private',
+ },
MAX_PREVIEW_AVATARS: 4,
+ MAX_ROOM_NAME_LENGTH: 80,
},
MODAL: {
MODAL_TYPE: {
diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index 928d3b3f5eb2..c97e2e24b1ec 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -156,4 +156,7 @@ export default {
// Is report data loading?
IS_LOADING_REPORT_DATA: 'isLoadingReportData',
+
+ // Are we loading the create policy room command
+ IS_LOADING_CREATE_POLICY_ROOM: 'isLoadingCratePolicyRoom',
};
diff --git a/src/ROUTES.js b/src/ROUTES.js
index f3b856a469de..adf0b319b77b 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -85,6 +85,7 @@ export default {
WORKSPACE_TRAVEL: 'workspace/:policyID/travel',
WORKSPACE_MEMBERS: 'workspace/:policyID/members',
WORKSPACE_BANK_ACCOUNT: 'workspace/:policyID/bank-account',
+ WORKSPACE_NEW_ROOM: 'workspace/new-room',
getWorkspaceInitialRoute: policyID => `workspace/${policyID}`,
getWorkspaceInviteRoute: policyID => `workspace/${policyID}/invite`,
getWorkspaceSettingsRoute: policyID => `workspace/${policyID}/settings`,
diff --git a/src/components/ExpensiPicker.js b/src/components/ExpensiPicker.js
index 4a6f4fc69649..bd4401f352b0 100644
--- a/src/components/ExpensiPicker.js
+++ b/src/components/ExpensiPicker.js
@@ -19,6 +19,9 @@ const propTypes = {
/** Error text to display */
errorText: PropTypes.string,
+
+ /** Customize the ExpensiPicker container */
+ containerStyles: PropTypes.arrayOf(PropTypes.object),
};
const defaultProps = {
@@ -26,6 +29,7 @@ const defaultProps = {
isDisabled: false,
hasError: false,
errorText: '',
+ containerStyles: [],
};
class ExpensiPicker extends PureComponent {
diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js
index d3f1f9be19bc..b0e7d9d2726b 100644
--- a/src/components/Icon/Expensicons.js
+++ b/src/components/Icon/Expensicons.js
@@ -25,6 +25,7 @@ import EyeDisabled from '../../../assets/images/eye-disabled.svg';
import ExpensifyCard from '../../../assets/images/expensifycard.svg';
import Gallery from '../../../assets/images/gallery.svg';
import Gear from '../../../assets/images/gear.svg';
+import Hashtag from '../../../assets/images/hashtag.svg';
import Info from '../../../assets/images/info.svg';
import Invoice from '../../../assets/images/invoice.svg';
import Link from '../../../assets/images/link.svg';
@@ -92,6 +93,7 @@ export {
ExpensifyCard,
Gallery,
Gear,
+ Hashtag,
Info,
Invoice,
Link,
diff --git a/src/components/TextInputWithLabel.js b/src/components/TextInputWithLabel.js
index 43030b397341..9eea98fd4c7c 100644
--- a/src/components/TextInputWithLabel.js
+++ b/src/components/TextInputWithLabel.js
@@ -1,11 +1,11 @@
import _ from 'underscore';
import React from 'react';
-// eslint-disable-next-line no-restricted-imports
-import {View, TextInput} from 'react-native';
+import {View} from 'react-native';
import PropTypes from 'prop-types';
import styles from '../styles/styles';
import ExpensifyText from './ExpensifyText';
import TextLink from './TextLink';
+import TextInputWithPrefix from './TextInputWithPrefix';
const propTypes = {
/** Label text */
@@ -14,6 +14,12 @@ const propTypes = {
/** Text to show if there is an error */
errorText: PropTypes.string,
+ /** Prefix character prepended to the text input */
+ prefixCharacter: PropTypes.string,
+
+ /** Placeholder to display when the text input is empty */
+ placeholder: PropTypes.string,
+
/** Styles for the outermost container for this component. */
containerStyles: PropTypes.arrayOf(PropTypes.object),
@@ -25,15 +31,21 @@ const propTypes = {
/** Whether to disable the field and style */
disabled: PropTypes.bool,
+
+ /** Callback to execute when the text input is modified */
+ onChangeText: PropTypes.func,
};
const defaultProps = {
label: '',
errorText: '',
+ prefixCharacter: '',
containerStyles: [],
helpLinkText: '',
helpLinkURL: '',
disabled: false,
+ placeholder: '',
+ onChangeText: () => {},
};
const TextInputWithLabel = props => (
@@ -54,16 +66,8 @@ const TextInputWithLabel = props => (
)}
-
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */}
+
{props.errorText !== '' && (
{props.errorText}
)}
diff --git a/src/components/TextInputWithPrefix/index.android.js b/src/components/TextInputWithPrefix/index.android.js
new file mode 100644
index 000000000000..87e7f84e9ec8
--- /dev/null
+++ b/src/components/TextInputWithPrefix/index.android.js
@@ -0,0 +1,58 @@
+import PropTypes from 'prop-types';
+// eslint-disable-next-line no-restricted-imports
+import {TextInput, View} from 'react-native';
+import _ from 'underscore';
+import React from 'react';
+import ExpensifyText from '../ExpensifyText';
+import styles from '../../styles/styles';
+
+const propTypes = {
+ /** Prefix character */
+ prefixCharacter: PropTypes.string,
+
+ /** Text to show if there is an error */
+ errorText: PropTypes.string,
+
+ /** Whether to disable the field and style */
+ disabled: PropTypes.bool,
+
+ /** Callback to execute the text input is modified */
+ onChangeText: PropTypes.func,
+};
+
+const defaultProps = {
+ errorText: '',
+ prefixCharacter: '',
+ disabled: false,
+ onChangeText: () => {},
+};
+
+const TextInputWithPrefix = props => (_.isEmpty(props.prefixCharacter)
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ ?
+ : (
+
+ {props.prefixCharacter}
+ props.onChangeText(`${props.prefixCharacter}${text}`)}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {..._.omit(props, ['prefixCharacter', 'errorText', 'onChangeText'])}
+ />
+
+ ));
+
+TextInputWithPrefix.propTypes = propTypes;
+TextInputWithPrefix.defaultProps = defaultProps;
+export default TextInputWithPrefix;
diff --git a/src/components/TextInputWithPrefix/index.js b/src/components/TextInputWithPrefix/index.js
new file mode 100644
index 000000000000..50d3793f41af
--- /dev/null
+++ b/src/components/TextInputWithPrefix/index.js
@@ -0,0 +1,56 @@
+import PropTypes from 'prop-types';
+// eslint-disable-next-line no-restricted-imports
+import {TextInput, View} from 'react-native';
+import _ from 'underscore';
+import React from 'react';
+import ExpensifyText from '../ExpensifyText';
+import styles from '../../styles/styles';
+
+const propTypes = {
+ /** Prefix character */
+ prefixCharacter: PropTypes.string,
+
+ /** Text to show if there is an error */
+ errorText: PropTypes.string,
+
+ /** Whether to disable the field and style */
+ disabled: PropTypes.bool,
+
+ /** Callback to execute the text input is modified */
+ onChangeText: PropTypes.func,
+};
+
+const defaultProps = {
+ errorText: '',
+ prefixCharacter: '',
+ disabled: false,
+ onChangeText: () => {},
+};
+
+const TextInputWithPrefix = props => (_.isEmpty(props.prefixCharacter)
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ ?
+ : (
+
+ {props.prefixCharacter}
+ props.onChangeText(`${props.prefixCharacter}${text}`)}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {..._.omit(props, ['prefixCharacter', 'errorText', 'onChangeText'])}
+ />
+
+ ));
+
+TextInputWithPrefix.propTypes = propTypes;
+TextInputWithPrefix.defaultProps = defaultProps;
+export default TextInputWithPrefix;
diff --git a/src/languages/en.js b/src/languages/en.js
index 23c5029b2dab..add267154958 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -176,6 +176,7 @@ export default {
fabAction: 'New chat',
newChat: 'New chat',
newGroup: 'New group',
+ newRoom: 'New Room',
headerChat: 'Chats',
buttonSearch: 'Search',
buttonMySettings: 'My settings',
@@ -793,6 +794,18 @@ export default {
emojiPicker: {
skinTonePickerLabel: 'Change default skin tone',
},
+ newRoomPage: {
+ newRoom: 'New Room',
+ roomName: 'Room Name',
+ visibility: 'Visibility',
+ restrictedDescription: 'People in your workspace are able to find this room using Search',
+ privateDescription: 'Only people invited to this room are able to find it',
+ createRoom: 'Create Room',
+ roomAlreadyExists: 'A room with this name already exists',
+ social: 'social',
+ selectAWorkspace: 'Select a workspace',
+ growlMessageOnError: 'Unable to create policy room, please check your connection and try again.',
+ },
keyboardShortcutModal: {
title: 'Keyboard Shortcuts',
subtitle: 'Save time with these handy keyboard shortcuts:',
diff --git a/src/languages/es.js b/src/languages/es.js
index 3ce7f2a9bd35..6a6b8e5d3a4c 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -176,6 +176,7 @@ export default {
fabAction: 'Nuevo chat',
newChat: 'Nuevo chat',
newGroup: 'Nuevo grupo',
+ newRoom: 'Nueva sala de chat',
headerChat: 'Chats',
buttonSearch: 'Buscar',
buttonMySettings: 'Mi configuración',
@@ -795,6 +796,18 @@ export default {
emojiPicker: {
skinTonePickerLabel: 'Elige el tono de piel por defecto',
},
+ newRoomPage: {
+ newRoom: 'Nueva sala de chat',
+ roomName: 'Nombre de la sala',
+ visibility: 'Visibilidad',
+ restrictedDescription: 'Sólo las personas en tu espacio de trabajo pueden encontrar esta sala a través de "Buscar"',
+ privateDescription: 'Sólo las personas que están invitadas a esta sala pueden encontrarla',
+ createRoom: 'Crea una sala de chat',
+ roomAlreadyExists: 'Ya existe una sala con este nombre',
+ social: 'social',
+ selectAWorkspace: 'Seleccionar un espacio de trabajo',
+ growlMessageOnError: 'No ha sido posible crear el espacio de trabajo, por favor comprueba tu conexión e inténtalo de nuevo.',
+ },
keyboardShortcutModal: {
title: 'Atajos de teclado',
subtitle: 'Ahorra tiempo con estos atajos de teclado:',
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index 4b998a632ee4..1f2ec68439d0 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -37,6 +37,7 @@ import WorkspaceBillsPage from '../../../pages/workspace/bills/WorkspaceBillsPag
import WorkspaceTravelPage from '../../../pages/workspace/travel/WorkspaceTravelPage';
import WorkspaceMembersPage from '../../../pages/workspace/WorkspaceMembersPage';
import WorkspaceBankAccountPage from '../../../pages/workspace/WorkspaceBankAccountPage';
+import WorkspaceNewRoomPage from '../../../pages/workspace/WorkspaceNewRoomPage';
import CONST from '../../../CONST';
const defaultSubRouteOptions = {
@@ -222,6 +223,10 @@ const SettingsModalStackNavigator = createModalStackNavigator([
Component: WorkspaceInvitePage,
name: 'Workspace_Invite',
},
+ {
+ Component: WorkspaceNewRoomPage,
+ name: 'Workspace_NewRoom',
+ },
{
Component: ReimbursementAccountPage,
name: 'ReimbursementAccount',
diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js
index 4e89c80ec813..d195bd886da2 100644
--- a/src/libs/Navigation/linkingConfig.js
+++ b/src/libs/Navigation/linkingConfig.js
@@ -102,6 +102,9 @@ export default {
Workspace_Invite: {
path: ROUTES.WORKSPACE_INVITE,
},
+ Workspace_NewRoom: {
+ path: ROUTES.WORKSPACE_NEW_ROOM,
+ },
ReimbursementAccount: {
path: ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN,
exact: true,
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index f8b51506a25b..9c5546ba3a74 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -1451,28 +1451,33 @@ function handleInaccessibleReport() {
}
/**
- * Creates a policy room and fetches it
+ * Creates a policy room, fetches it, and navigates to it.
* @param {String} policyID
* @param {String} reportName
* @param {String} visibility
* @return {Promise}
*/
function createPolicyRoom(policyID, reportName, visibility) {
+ Onyx.set(ONYXKEYS.IS_LOADING_CREATE_POLICY_ROOM, true);
return API.CreatePolicyRoom({policyID, reportName, visibility})
.then((response) => {
if (response.jsonCode !== 200) {
- Log.hmmm(response.message);
+ Growl.error(response.message);
return;
}
return fetchChatReportsByIDs([response.reportID]);
})
.then(([{reportID}]) => {
if (!reportID) {
- Log.hmmm('Unable to grab policy room after creation');
+ Log.error('Unable to grab policy room after creation', reportID);
return;
}
Navigation.navigate(ROUTES.getReportRoute(reportID));
- });
+ })
+ .catch(() => {
+ Growl.error(Localize.translateLocal('newRoomPage.growlMessageOnError'));
+ })
+ .finally(() => Onyx.set(ONYXKEYS.IS_LOADING_CREATE_POLICY_ROOM, false));
}
export {
diff --git a/src/libs/reportUtils.js b/src/libs/reportUtils.js
index d6a4f1d56717..1431fdac402b 100644
--- a/src/libs/reportUtils.js
+++ b/src/libs/reportUtils.js
@@ -92,6 +92,16 @@ function isDefaultRoom(report) {
], lodashGet(report, ['chatType'], ''));
}
+/**
+ * Whether the provided report is a policy room
+ * @param {Object} report
+ * @param {String} report.chatType
+ * @returns {Boolean}
+ */
+function isPolicyRoom(report) {
+ return lodashGet(report, ['chatType'], '') === CONST.REPORT.CHAT_TYPE.POLICY_ROOM;
+}
+
/**
* Given a collection of reports returns the most recently accessed one
*
@@ -198,6 +208,7 @@ export {
canDeleteReportAction,
sortReportsByLastVisited,
isDefaultRoom,
+ isPolicyRoom,
getDefaultRoomSubtitle,
isArchivedRoom,
isConciergeChatReport,
diff --git a/src/pages/home/sidebar/SidebarScreen.js b/src/pages/home/sidebar/SidebarScreen.js
index d678eeb81fba..5f3f1b12471e 100755
--- a/src/pages/home/sidebar/SidebarScreen.js
+++ b/src/pages/home/sidebar/SidebarScreen.js
@@ -159,6 +159,13 @@ class SidebarScreen extends Component {
text: this.props.translate('sidebarScreen.newGroup'),
onSelected: () => Navigation.navigate(ROUTES.NEW_GROUP),
},
+ ...(Permissions.canUseDefaultRooms(this.props.betas) ? [
+ {
+ icon: Expensicons.Hashtag,
+ text: this.props.translate('sidebarScreen.newRoom'),
+ onSelected: () => Navigation.navigate(ROUTES.WORKSPACE_NEW_ROOM),
+ },
+ ] : []),
...(Permissions.canUseIOUSend(this.props.betas) ? [
{
icon: Expensicons.Send,
diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js
new file mode 100644
index 000000000000..d4f2e715cc8c
--- /dev/null
+++ b/src/pages/workspace/WorkspaceNewRoomPage.js
@@ -0,0 +1,202 @@
+import React from 'react';
+import {View} from 'react-native';
+import _ from 'underscore';
+import {withOnyx} from 'react-native-onyx';
+import PropTypes from 'prop-types';
+import withFullPolicy, {fullPolicyDefaultProps, fullPolicyPropTypes} from './withFullPolicy';
+import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
+import compose from '../../libs/compose';
+import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
+import Navigation from '../../libs/Navigation/Navigation';
+import ScreenWrapper from '../../components/ScreenWrapper';
+import styles from '../../styles/styles';
+import TextInputWithLabel from '../../components/TextInputWithLabel';
+import ExpensiPicker from '../../components/ExpensiPicker';
+import ONYXKEYS from '../../ONYXKEYS';
+import CONST from '../../CONST';
+import ExpensifyButton from '../../components/ExpensifyButton';
+import FixedFooter from '../../components/FixedFooter';
+import * as Report from '../../libs/actions/Report';
+import Permissions from '../../libs/Permissions';
+import Log from '../../libs/Log';
+
+const propTypes = {
+ /** All reports shared with the user */
+ reports: PropTypes.shape({
+ reportName: PropTypes.string,
+ type: PropTypes.string,
+ policyID: PropTypes.string,
+ }).isRequired,
+
+ /** Are we loading the createPolicyRoom command */
+ isLoadingCreatePolicyRoom: PropTypes.bool,
+
+ ...fullPolicyPropTypes,
+
+ ...withLocalizePropTypes,
+};
+const defaultProps = {
+ betas: [],
+ isLoadingCreatePolicyRoom: false,
+ ...fullPolicyDefaultProps,
+};
+
+class WorkspaceNewRoomPage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ roomName: '',
+ policyID: '',
+ visibility: CONST.REPORT.VISIBILITY.RESTRICTED,
+ error: '',
+ workspaceOptions: [],
+ };
+ this.onWorkspaceSelect = this.onWorkspaceSelect.bind(this);
+ this.onSubmit = this.onSubmit.bind(this);
+ this.checkAndModifyRoomName = this.checkAndModifyRoomName.bind(this);
+ }
+
+ componentDidMount() {
+ // Workspaces are policies with type === 'free'
+ const workspaces = _.filter(this.props.policies, policy => policy && policy.type === CONST.POLICY.TYPE.FREE);
+ this.setState({workspaceOptions: _.map(workspaces, policy => ({label: policy.name, key: policy.id, value: policy.id}))});
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.policies.length === prevProps.policies.length) {
+ return;
+ }
+
+ // Workspaces are policies with type === 'free'
+ const workspaces = _.filter(this.props.policies, policy => policy && policy.type === CONST.POLICY.TYPE.FREE);
+
+ // eslint-disable-next-line react/no-did-update-set-state
+ this.setState({workspaceOptions: _.map(workspaces, policy => ({label: policy.name, key: policy.id, value: policy.id}))});
+ }
+
+ /**
+ * Called when a workspace is selected. Also calls checkAndModifyRoomName,
+ * which displays an error if the given roomName exists on the newly selected workspace.
+ * @param {String} policyID
+ */
+ onWorkspaceSelect(policyID) {
+ this.setState({policyID});
+ this.checkAndModifyRoomName(this.state.roomName);
+ }
+
+ /**
+ * Called when the "Create Room" button is pressed.
+ */
+ onSubmit() {
+ Report.createPolicyRoom(this.state.policyID, this.state.roomName, this.state.visibility);
+ }
+
+ /**
+ * Modifies the room name to follow our conventions:
+ * - Max length 80 characters
+ * - Cannot not include space or special characters, and we automatically apply an underscore for spaces
+ * - Must be lowercase
+ * Also checks to see if this room name already exists, and displays an error message if so.
+ * @param {String} roomName
+ *
+ * @returns {String}
+ */
+ checkAndModifyRoomName(roomName) {
+ const modifiedRoomNameWithoutHash = roomName.substr(1)
+ .replace(/ /g, '_')
+ .replace(/[^a-zA-Z\d_]/g, '')
+ .substr(0, CONST.REPORT.MAX_ROOM_NAME_LENGTH)
+ .toLowerCase();
+ const finalRoomName = `#${modifiedRoomNameWithoutHash}`;
+
+ const isExistingRoomName = _.some(
+ _.values(this.props.reports),
+ report => report && report.policyID === this.state.policyID && report.reportName === finalRoomName,
+ );
+ if (isExistingRoomName) {
+ this.setState({error: this.props.translate('newRoomPage.roomAlreadyExists')});
+ } else {
+ this.setState({error: ''});
+ }
+ return finalRoomName;
+ }
+
+ render() {
+ if (!Permissions.canUseDefaultRooms(this.props.betas)) {
+ Log.info('Not showing create Policy Room page since user is not on default rooms beta');
+ Navigation.dismissModal();
+ return null;
+ }
+ const shouldDisableSubmit = Boolean(!this.state.roomName || !this.state.policyID || this.state.error);
+ return (
+
+ Navigation.dismissModal()}
+ />
+
+ this.setState({roomName: this.checkAndModifyRoomName(roomName)})}
+ value={this.state.roomName.substr(1)}
+ errorText={this.state.error}
+ />
+
+
+
+ this.setState({visibility})}
+ />
+
+
+
+
+
+ );
+ }
+}
+
+WorkspaceNewRoomPage.propTypes = propTypes;
+WorkspaceNewRoomPage.defaultProps = defaultProps;
+
+export default compose(
+ withFullPolicy,
+ withOnyx({
+ betas: {
+ key: ONYXKEYS.BETAS,
+ },
+ policies: {
+ key: ONYXKEYS.COLLECTION.POLICY,
+ },
+ reports: {
+ key: ONYXKEYS.COLLECTION.REPORT,
+ },
+ isLoadingCreatePolicyRoom: {
+ key: ONYXKEYS.IS_LOADING_CREATE_POLICY_ROOM,
+ },
+ }),
+ withLocalize,
+)(WorkspaceNewRoomPage);
diff --git a/src/styles/styles.js b/src/styles/styles.js
index c0ec2df5f495..abdb11847b5e 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -676,6 +676,42 @@ const styles = {
textAlignVertical: 'center',
},
+ textInputWithPrefix: {
+ container: {
+ backgroundColor: themeColors.componentBG,
+ borderColor: themeColors.border,
+ borderWidth: 1,
+ borderRadius: variables.componentBorderRadiusNormal,
+ color: themeColors.text,
+ display: 'flex',
+ flexDirection: 'row',
+ fontFamily: fontFamily.GTA,
+ fontSize: variables.fontSizeNormal,
+ height: variables.inputComponentSizeNormal,
+ marginBottom: 4,
+ paddingBottom: 10,
+ paddingLeft: 12,
+ paddingRight: 12,
+ paddingTop: 10,
+ textAlignVertical: 'center',
+ },
+ textInput: {
+ outlineStyle: 'none',
+ color: themeColors.text,
+ fontFamily: fontFamily.GTA,
+ fontSize: variables.fontSizeNormal,
+ textAlignVertical: 'center',
+ flex: 1,
+ },
+ prefix: {
+ paddingRight: 10,
+ color: themeColors.text,
+ fontFamily: fontFamily.GTA,
+ fontSize: variables.fontSizeNormal,
+ textAlignVertical: 'center',
+ },
+ },
+
expensiPickerContainer: {
borderWidth: 0,
borderRadius: variables.componentBorderRadiusNormal,