diff --git a/app/components/option_item/index.tsx b/app/components/option_item/index.tsx
index 5d91feed5..33a4ebd4d 100644
--- a/app/components/option_item/index.tsx
+++ b/app/components/option_item/index.tsx
@@ -103,6 +103,7 @@ export type OptionItemProps = {
description?: string;
destructive?: boolean;
icon?: string;
+ iconColor?: string;
info?: string;
inline?: boolean;
label: string;
@@ -124,6 +125,7 @@ const OptionItem = ({
description,
destructive,
icon,
+ iconColor,
info,
inline = false,
label,
@@ -239,7 +241,7 @@ const OptionItem = ({
)}
diff --git a/app/components/post_draft/draft_handler/draft_handler.tsx b/app/components/post_draft/draft_handler/draft_handler.tsx
index 1ac994c29..5de98330c 100644
--- a/app/components/post_draft/draft_handler/draft_handler.tsx
+++ b/app/components/post_draft/draft_handler/draft_handler.tsx
@@ -16,6 +16,7 @@ type Props = {
channelId: string;
cursorPosition: number;
rootId?: string;
+ canShowPostPriority?: boolean;
files?: FileInfo[];
maxFileCount: number;
maxFileSize: number;
@@ -40,6 +41,7 @@ export default function DraftHandler(props: Props) {
channelId,
cursorPosition,
rootId = '',
+ canShowPostPriority,
files,
maxFileCount,
maxFileSize,
@@ -135,6 +137,7 @@ export default function DraftHandler(props: Props) {
testID={testID}
channelId={channelId}
rootId={rootId}
+ canShowPostPriority={canShowPostPriority}
// From draft handler
cursorPosition={cursorPosition}
diff --git a/app/components/post_draft/draft_input/index.tsx b/app/components/post_draft/draft_input/index.tsx
index a5f827de8..cfb4a276a 100644
--- a/app/components/post_draft/draft_input/index.tsx
+++ b/app/components/post_draft/draft_input/index.tsx
@@ -6,6 +6,7 @@ import React, {useCallback, useRef} from 'react';
import {LayoutChangeEvent, Platform, ScrollView, View} from 'react-native';
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
+import PostPriorityLabel from '@components/post_priority/post_priority_label';
import {useTheme} from '@context/theme';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
@@ -20,6 +21,11 @@ type Props = {
channelId: string;
rootId?: string;
currentUserId: string;
+ canShowPostPriority?: boolean;
+
+ // Post Props
+ postProps: Post['props'];
+ updatePostProps: (postProps: Post['props']) => void;
// Cursor Position Handler
updateCursorPosition: React.Dispatch>;
@@ -77,6 +83,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
borderTopLeftRadius: 12,
borderTopRightRadius: 12,
},
+ postPriorityLabel: {
+ marginLeft: 12,
+ marginTop: Platform.select({
+ ios: 3,
+ android: 10,
+ }),
+ },
};
});
@@ -84,6 +97,7 @@ export default function DraftInput({
testID,
channelId,
currentUserId,
+ canShowPostPriority,
files,
maxMessageLength,
rootId = '',
@@ -96,6 +110,8 @@ export default function DraftInput({
updateCursorPosition,
cursorPosition,
updatePostInputTop,
+ postProps,
+ updatePostProps,
setIsFocused,
}: Props) {
const theme = useTheme();
@@ -139,6 +155,11 @@ export default function DraftInput({
overScrollMode={'never'}
disableScrollViewPanResponder={true}
>
+ {Boolean(postProps.priority) && (
+
+
+
+ )}
;
containerHeight: number;
isChannelScreen: boolean;
+ canShowPostPriority?: boolean;
}
const {KEYBOARD_TRACKING_OFFSET} = ViewConstants;
@@ -54,6 +55,7 @@ function PostDraft({
keyboardTracker,
containerHeight,
isChannelScreen,
+ canShowPostPriority,
}: Props) {
const [value, setValue] = useState(message);
const [cursorPosition, setCursorPosition] = useState(message.length);
@@ -109,6 +111,7 @@ function PostDraft({
cursorPosition={cursorPosition}
files={files}
rootId={rootId}
+ canShowPostPriority={canShowPostPriority}
updateCursorPosition={setCursorPosition}
updatePostInputTop={setPostInputTop}
updateValue={setValue}
diff --git a/app/components/post_draft/quick_actions/index.ts b/app/components/post_draft/quick_actions/index.ts
index b5c7111f0..415d6359e 100644
--- a/app/components/post_draft/quick_actions/index.ts
+++ b/app/components/post_draft/quick_actions/index.ts
@@ -5,7 +5,7 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React from 'react';
-import {observeCanUploadFiles, observeMaxFileCount} from '@queries/servers/system';
+import {observeCanUploadFiles, observeIsPostPriorityEnabled, observeMaxFileCount} from '@queries/servers/system';
import QuickActions from './quick_actions';
@@ -17,6 +17,7 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
return {
canUploadFiles,
+ isPostPriorityEnabled: observeIsPostPriorityEnabled(database),
maxFileCount,
};
});
diff --git a/app/components/post_draft/quick_actions/post_priority_action/index.tsx b/app/components/post_draft/quick_actions/post_priority_action/index.tsx
new file mode 100644
index 000000000..cc6452f23
--- /dev/null
+++ b/app/components/post_draft/quick_actions/post_priority_action/index.tsx
@@ -0,0 +1,84 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, {useCallback} from 'react';
+import {useIntl} from 'react-intl';
+import {StyleSheet} from 'react-native';
+
+import CompassIcon from '@components/compass_icon';
+import PostPriorityPicker, {PostPriorityData} from '@components/post_priority/post_priority_picker';
+import TouchableWithFeedback from '@components/touchable_with_feedback';
+import {ICON_SIZE} from '@constants/post_draft';
+import {useTheme} from '@context/theme';
+import {bottomSheet, dismissBottomSheet} from '@screens/navigation';
+import {changeOpacity} from '@utils/theme';
+
+type Props = {
+ testID?: string;
+ postProps: Post['props'];
+ updatePostProps: (postProps: Post['props']) => void;
+}
+
+const style = StyleSheet.create({
+ icon: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 10,
+ },
+});
+
+export default function PostPriorityAction({
+ testID,
+ postProps,
+ updatePostProps,
+}: Props) {
+ const intl = useIntl();
+ const theme = useTheme();
+
+ const handlePostPriorityPicker = useCallback((postPriorityData: PostPriorityData) => {
+ updatePostProps((oldPostProps: Post['props']) => ({
+ ...oldPostProps,
+ ...postPriorityData,
+ }));
+ dismissBottomSheet();
+ }, [updatePostProps]);
+
+ const renderContent = useCallback(() => {
+ return (
+
+ );
+ }, [handlePostPriorityPicker, postProps]);
+
+ const onPress = useCallback(() => {
+ bottomSheet({
+ title: intl.formatMessage({id: 'post_priority.picker.title', defaultMessage: 'Message priority'}),
+ renderContent,
+ snapPoints: [275, 10],
+ theme,
+ closeButtonId: 'post-priority-close-id',
+ });
+ }, [intl, renderContent, theme]);
+
+ const iconName = 'alert-circle-outline';
+ const iconColor = changeOpacity(theme.centerChannelColor, 0.64);
+
+ return (
+
+
+
+ );
+}
diff --git a/app/components/post_draft/quick_actions/quick_actions.tsx b/app/components/post_draft/quick_actions/quick_actions.tsx
index 4d63402d3..f5d882425 100644
--- a/app/components/post_draft/quick_actions/quick_actions.tsx
+++ b/app/components/post_draft/quick_actions/quick_actions.tsx
@@ -8,17 +8,22 @@ import CameraAction from './camera_quick_action';
import FileAction from './file_quick_action';
import ImageAction from './image_quick_action';
import InputAction from './input_quick_action';
+import PostPriorityAction from './post_priority_action';
type Props = {
testID?: string;
canUploadFiles: boolean;
fileCount: number;
+ isPostPriorityEnabled: boolean;
+ canShowPostPriority?: boolean;
maxFileCount: number;
// Draft Handler
value: string;
updateValue: (value: string) => void;
addFiles: (file: FileInfo[]) => void;
+ postProps: Post['props'];
+ updatePostProps: (postProps: Post['props']) => void;
focus: () => void;
}
@@ -45,9 +50,13 @@ export default function QuickActions({
canUploadFiles,
value,
fileCount,
+ isPostPriorityEnabled,
+ canShowPostPriority,
maxFileCount,
updateValue,
addFiles,
+ postProps,
+ updatePostProps,
focus,
}: Props) {
const atDisabled = value[value.length - 1] === '@';
@@ -58,6 +67,7 @@ export default function QuickActions({
const fileActionTestID = `${testID}.file_action`;
const imageActionTestID = `${testID}.image_action`;
const cameraActionTestID = `${testID}.camera_action`;
+ const postPriorityActionTestID = `${testID}.post_priority_action`;
const uploadProps = {
disabled: !canUploadFiles,
@@ -98,6 +108,13 @@ export default function QuickActions({
testID={cameraActionTestID}
{...uploadProps}
/>
+ {isPostPriorityEnabled && canShowPostPriority && (
+
+ )}
);
}
diff --git a/app/components/post_draft/send_handler/send_handler.tsx b/app/components/post_draft/send_handler/send_handler.tsx
index 5ad1c5c41..b17b6c721 100644
--- a/app/components/post_draft/send_handler/send_handler.tsx
+++ b/app/components/post_draft/send_handler/send_handler.tsx
@@ -29,6 +29,7 @@ type Props = {
testID?: string;
channelId: string;
rootId: string;
+ canShowPostPriority?: boolean;
setIsFocused: (isFocused: boolean) => void;
// From database
@@ -64,6 +65,7 @@ export default function SendHandler({
membersCount = 0,
cursorPosition,
rootId,
+ canShowPostPriority,
useChannelMentions,
userIsOutOfOffice,
customEmojis,
@@ -82,6 +84,8 @@ export default function SendHandler({
const [channelTimezoneCount, setChannelTimezoneCount] = useState(0);
const [sendingMessage, setSendingMessage] = useState(false);
+ const [postProps, setPostProps] = useState({});
+
const canSend = useCallback(() => {
if (sendingMessage) {
return false;
@@ -114,14 +118,19 @@ export default function SendHandler({
channel_id: channelId,
root_id: rootId,
message: value,
- };
+ } as Post;
+
+ if (Object.keys(postProps).length) {
+ post.props = postProps;
+ }
createPost(serverUrl, post, postFiles);
clearDraft();
setSendingMessage(false);
+ setPostProps({});
DeviceEventEmitter.emit(Events.POST_LIST_SCROLL_TO_BOTTOM, rootId ? Screens.THREAD : Screens.CHANNEL);
- }, [files, currentUserId, channelId, rootId, value, clearDraft]);
+ }, [files, currentUserId, channelId, rootId, value, clearDraft, postProps]);
const showSendToAllOrChannelOrHereAlert = useCallback((calculatedMembersCount: number, atHere: boolean) => {
const notifyAllMessage = DraftUtils.buildChannelWideMentionMessage(intl, calculatedMembersCount, Boolean(isTimezoneEnabled), channelTimezoneCount, atHere);
@@ -281,6 +290,7 @@ export default function SendHandler({
channelId={channelId}
currentUserId={currentUserId}
rootId={rootId}
+ canShowPostPriority={canShowPostPriority}
cursorPosition={cursorPosition}
updateCursorPosition={updateCursorPosition}
value={value}
@@ -292,6 +302,8 @@ export default function SendHandler({
canSend={canSend()}
maxMessageLength={maxMessageLength}
updatePostInputTop={updatePostInputTop}
+ postProps={postProps}
+ updatePostProps={setPostProps}
setIsFocused={setIsFocused}
/>
);
diff --git a/app/components/post_list/post/header/header.tsx b/app/components/post_list/post/header/header.tsx
index 700cf1370..fafe6035b 100644
--- a/app/components/post_list/post/header/header.tsx
+++ b/app/components/post_list/post/header/header.tsx
@@ -33,13 +33,13 @@ type HeaderProps = {
isEphemeral: boolean;
isMilitaryTime: boolean;
isPendingOrFailed: boolean;
- isPostPriorityEnabled: boolean;
isSystemPost: boolean;
isTimezoneEnabled: boolean;
isWebHook: boolean;
location: string;
post: PostModel;
rootPostAuthor?: UserModel;
+ showPostPriority: boolean;
shouldRenderReplyButton?: boolean;
teammateNameDisplay: string;
}
@@ -78,8 +78,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
const Header = (props: HeaderProps) => {
const {
author, commentCount = 0, currentUser, enablePostUsernameOverride, isAutoResponse, isCRTEnabled, isCustomStatusEnabled,
- isEphemeral, isMilitaryTime, isPendingOrFailed, isPostPriorityEnabled, isSystemPost, isTimezoneEnabled, isWebHook,
- location, post, rootPostAuthor, shouldRenderReplyButton, teammateNameDisplay,
+ isEphemeral, isMilitaryTime, isPendingOrFailed, isSystemPost, isTimezoneEnabled, isWebHook,
+ location, post, rootPostAuthor, showPostPriority, shouldRenderReplyButton, teammateNameDisplay,
} = props;
const theme = useTheme();
const style = getStyleSheet(theme);
@@ -132,7 +132,7 @@ const Header = (props: HeaderProps) => {
style={style.time}
testID='post_header.date_time'
/>
- {Boolean(isPostPriorityEnabled && post.props?.priority) && (
+ {showPostPriority && (
;
- const isProrityPost = Boolean(isPostPriorityEnabled && post.props?.priority);
+
+ // If the post is a priority post:
+ // 1. Show the priority label in channel screen
+ // 2. Show the priority label in thread screen for the root post
+ const showPostPriority = Boolean(isPostPriorityEnabled && post.props?.priority) && (location !== Screens.THREAD || !post.rootId);
+
const sameSequence = hasReplies ? (hasReplies && post.rootId) : !post.rootId;
- if (!isProrityPost && hasSameRoot && isConsecutivePost && sameSequence) {
+ if (!showPostPriority && hasSameRoot && isConsecutivePost && sameSequence) {
consecutiveStyle = styles.consective;
postAvatar = ;
} else {
@@ -256,13 +261,13 @@ const Post = ({
differentThreadSequence={differentThreadSequence}
isAutoResponse={isAutoResponder}
isCRTEnabled={isCRTEnabled}
- isPostPriorityEnabled={isPostPriorityEnabled}
isEphemeral={isEphemeral}
isPendingOrFailed={isPendingOrFailed}
isSystemPost={isSystemPost}
isWebHook={isWebHook}
location={location}
post={post}
+ showPostPriority={showPostPriority}
shouldRenderReplyButton={shouldRenderReplyButton}
/>
);
diff --git a/app/components/post_priority/post_priority_label.tsx b/app/components/post_priority/post_priority_label.tsx
index 8eb35505b..3432b6554 100644
--- a/app/components/post_priority/post_priority_label.tsx
+++ b/app/components/post_priority/post_priority_label.tsx
@@ -6,22 +6,22 @@ import {useIntl} from 'react-intl';
import {StyleProp, StyleSheet, Text, View, ViewStyle} from 'react-native';
import CompassIcon from '@components/compass_icon';
-import {PostPriorityTypes} from '@constants/post';
+import {PostPriorityColors, PostPriorityType} from '@constants/post';
import {typography} from '@utils/typography';
const style = StyleSheet.create({
container: {
+ alignSelf: 'flex-start',
flexDirection: 'row',
borderRadius: 4,
alignItems: 'center',
- height: 16,
paddingHorizontal: 4,
},
urgent: {
- backgroundColor: '#D24B4E',
+ backgroundColor: PostPriorityColors.URGENT,
},
important: {
- backgroundColor: '#5D89EA',
+ backgroundColor: PostPriorityColors.IMPORTANT,
},
label: {
color: '#fff',
@@ -35,7 +35,7 @@ const style = StyleSheet.create({
});
type Props = {
- label: string;
+ label: PostPriorityType;
};
const PostPriorityLabel = ({label}: Props) => {
@@ -44,7 +44,7 @@ const PostPriorityLabel = ({label}: Props) => {
const containerStyle: StyleProp = [style.container];
let iconName = '';
let labelText = '';
- if (label === PostPriorityTypes.URGENT) {
+ if (label === PostPriorityType.URGENT) {
containerStyle.push(style.urgent);
iconName = 'alert-outline';
labelText = intl.formatMessage({id: 'post_priority.label.urgent', defaultMessage: 'URGENT'});
diff --git a/app/components/post_priority/post_priority_picker/index.tsx b/app/components/post_priority/post_priority_picker/index.tsx
new file mode 100644
index 000000000..9de0b58d0
--- /dev/null
+++ b/app/components/post_priority/post_priority_picker/index.tsx
@@ -0,0 +1,124 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {useIntl} from 'react-intl';
+import {View} from 'react-native';
+
+import FormattedText from '@components/formatted_text';
+import {PostPriorityColors, PostPriorityType} from '@constants/post';
+import {useTheme} from '@context/theme';
+import {useIsTablet} from '@hooks/device';
+import {makeStyleSheetFromTheme} from '@utils/theme';
+import {typography} from '@utils/typography';
+
+import PostPriorityPickerItem from './post_priority_picker_item';
+
+export type PostPriorityData = {
+ priority: PostPriorityType;
+};
+
+type Props = {
+ data: PostPriorityData;
+ onSubmit: (data: PostPriorityData) => void;
+};
+
+const getStyle = makeStyleSheetFromTheme((theme: Theme) => ({
+ container: {
+ backgroundColor: theme.centerChannelBg,
+ height: 200,
+ },
+ titleContainer: {
+ alignItems: 'center',
+ flexDirection: 'row',
+ },
+ title: {
+ color: theme.centerChannelColor,
+ ...typography('Body', 600, 'SemiBold'),
+ },
+ betaContainer: {
+ backgroundColor: PostPriorityColors.IMPORTANT,
+ borderRadius: 4,
+ paddingHorizontal: 4,
+ marginLeft: 8,
+ },
+ beta: {
+ color: '#fff',
+ ...typography('Body', 25, 'SemiBold'),
+ },
+
+ optionsContainer: {
+ paddingVertical: 12,
+ },
+}));
+
+const PostPriorityPicker = ({data, onSubmit}: Props) => {
+ const intl = useIntl();
+ const theme = useTheme();
+ const isTablet = useIsTablet();
+ const style = getStyle(theme);
+
+ // For now, we just have one option but the spec suggest we have more in the next phase
+ // const [data, setData] = React.useState(defaultData);
+
+ const handleUpdatePriority = React.useCallback((priority: PostPriorityType) => {
+ onSubmit({priority});
+ }, [onSubmit]);
+
+ return (
+
+ {!isTablet &&
+
+
+
+
+
+
+ }
+
+
+
+
+
+
+ );
+};
+
+export default PostPriorityPicker;
diff --git a/app/components/post_priority/post_priority_picker/post_priority_picker_item.tsx b/app/components/post_priority/post_priority_picker/post_priority_picker_item.tsx
new file mode 100644
index 000000000..7c613ae29
--- /dev/null
+++ b/app/components/post_priority/post_priority_picker/post_priority_picker_item.tsx
@@ -0,0 +1,34 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+
+import OptionItem, {OptionItemProps} from '@components/option_item';
+import {useTheme} from '@context/theme';
+import {makeStyleSheetFromTheme} from '@utils/theme';
+import {typography} from '@utils/typography';
+
+const getStyle = makeStyleSheetFromTheme((theme: Theme) => ({
+ optionLabelTextStyle: {
+ color: theme.centerChannelColor,
+ ...typography('Body', 200, 'Regular'),
+ },
+}));
+
+const PostPriorityPickerItem = (props: Omit) => {
+ const theme = useTheme();
+ const style = getStyle(theme);
+
+ const testID = `post_priority_picker_item.${props.value || 'standard'}`;
+
+ return (
+
+ );
+};
+
+export default PostPriorityPickerItem;
diff --git a/app/constants/post.ts b/app/constants/post.ts
index 036336858..99a6083dc 100644
--- a/app/constants/post.ts
+++ b/app/constants/post.ts
@@ -34,17 +34,22 @@ export const PostTypes: Record = {
CUSTOM_CALLS: 'custom_calls',
};
-export const PostPriorityTypes: Record = {
- URGENT: 'urgent',
- IMPORTANT: 'important',
+export const PostPriorityColors = {
+ URGENT: '#D24B4E',
+ IMPORTANT: '#5D89EA',
};
+export enum PostPriorityType {
+ STANDARD = '',
+ URGENT = 'urgent',
+ IMPORTANT = 'important',
+}
+
export const POST_TIME_TO_FAIL = 10000;
export default {
POST_COLLAPSE_TIMEOUT: 1000 * 60 * 5,
POST_TYPES: PostTypes,
- POST_PRIORITY_TYPES: PostPriorityTypes,
USER_ACTIVITY_POST_TYPES: [
PostTypes.ADD_TO_CHANNEL,
PostTypes.JOIN_CHANNEL,
diff --git a/app/screens/channel/channel.tsx b/app/screens/channel/channel.tsx
index 9e04cbd81..05c8c12dc 100644
--- a/app/screens/channel/channel.tsx
+++ b/app/screens/channel/channel.tsx
@@ -166,6 +166,7 @@ const Channel = ({
testID='channel.post_draft'
containerHeight={containerHeight}
isChannelScreen={true}
+ canShowPostPriority={true}
/>
>
}
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index d7ae5cfcf..783196678 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -695,6 +695,11 @@
"post_message_view.edited": "(edited)",
"post_priority.label.important": "IMPORTANT",
"post_priority.label.urgent": "URGENT",
+ "post_priority.picker.beta": "BETA",
+ "post_priority.picker.label.important": "Important",
+ "post_priority.picker.label.standard": "Standard",
+ "post_priority.picker.label.urgent": "Urgent",
+ "post_priority.picker.title": "Message priority",
"post.options.title": "Options",
"post.reactions.title": "Reactions",
"posts_view.newMsg": "New Messages",