forked from Ivasoft/mattermost-mobile
MM-41602 Gekidou long press menu UI only (#5950)
* skeleton in place * fix ts error * creating base option component * Added all options except reaction * moved options under /component/options * added destructive styling * skeleton - need polishing now * default emojis for quick reaction * rename files and small refactor * Properly close bottom sheet * redid reaction component * canSave, isSaved * canAddReaction condition * fix aligment * code clean up * fix opening on tablet * undo comment on local reaction action * undo needless formatting * clean up comment * shows selected reaction * fix marginTop and added title for Tablet * code clean up * investigating navigation * fixed navigation * Post options bottomSheet and renamed DrawerItem to MenuItem * renamed optionType to testID * update navigation_close_modal to close_bottom * removed context in favor of Pressable * Apply suggestions from code review Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * removed theme prop from PickReaction * en.json and code fixes * removed post_options from screen/index * removed post_options from screens constant * Revert "removed post_options from screen/index" This reverts commit24caa9773f. * Revert "removed post_options from screens constant" This reverts commit863e2faaf7. * fix theme import * remove useless margin * disabled post_options * refactored render method for post_options * fixing issue on iOS * corrections from PR review * fix for background on mobile * Fix stack navigation styles * i18n-extract output * Feedback review Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
@@ -30,7 +30,6 @@ exports[`DrawerItem should match snapshot 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 50,
|
||||
}
|
||||
@@ -38,13 +37,16 @@ exports[`DrawerItem should match snapshot 1`] = `
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"height": 50,
|
||||
"justifyContent": "center",
|
||||
"marginLeft": 5,
|
||||
"width": 45,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"height": 50,
|
||||
"justifyContent": "center",
|
||||
"marginLeft": 5,
|
||||
"width": 45,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
@@ -145,7 +147,6 @@ exports[`DrawerItem should match snapshot without separator and centered false 1
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 50,
|
||||
}
|
||||
@@ -153,13 +154,16 @@ exports[`DrawerItem should match snapshot without separator and centered false 1
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"height": 50,
|
||||
"justifyContent": "center",
|
||||
"marginLeft": 5,
|
||||
"width": 45,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"height": 50,
|
||||
"justifyContent": "center",
|
||||
"marginLeft": 5,
|
||||
"width": 45,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
@@ -6,7 +6,7 @@ import React from 'react';
|
||||
import {Preferences} from '@constants';
|
||||
import {renderWithIntl} from '@test/intl-test-helper';
|
||||
|
||||
import DrawerItem from './';
|
||||
import MenuItem from '.';
|
||||
|
||||
describe('DrawerItem', () => {
|
||||
const baseProps = {
|
||||
@@ -22,7 +22,7 @@ describe('DrawerItem', () => {
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = renderWithIntl(<DrawerItem {...baseProps}/>);
|
||||
const wrapper = renderWithIntl(<MenuItem {...baseProps}/>);
|
||||
|
||||
expect(wrapper.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -34,7 +34,7 @@ describe('DrawerItem', () => {
|
||||
separator: false,
|
||||
};
|
||||
const wrapper = renderWithIntl(
|
||||
<DrawerItem {...props}/>,
|
||||
<MenuItem {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.toJSON()).toMatchSnapshot();
|
||||
@@ -2,17 +2,20 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {ReactNode} from 'react';
|
||||
import {Platform, View} from 'react-native';
|
||||
import {Platform, StyleProp, View, ViewStyle} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type DrawerItemProps = {
|
||||
export const ITEM_HEIGHT = 50;
|
||||
|
||||
type MenuItemProps = {
|
||||
centered?: boolean;
|
||||
defaultMessage?: string;
|
||||
i18nId?: string;
|
||||
iconContainerStyle?: StyleProp<ViewStyle>;
|
||||
iconName?: string;
|
||||
isDestructor?: boolean;
|
||||
labelComponent?: ReactNode;
|
||||
@@ -23,11 +26,55 @@ type DrawerItemProps = {
|
||||
theme: Theme;
|
||||
};
|
||||
|
||||
const DrawerItem = (props: DrawerItemProps) => {
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
minHeight: ITEM_HEIGHT,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 45,
|
||||
height: ITEM_HEIGHT,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginLeft: 5,
|
||||
},
|
||||
icon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
fontSize: 24,
|
||||
},
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
},
|
||||
labelContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingTop: 14,
|
||||
paddingBottom: 14,
|
||||
},
|
||||
centerLabel: {
|
||||
textAlign: 'center',
|
||||
textAlignVertical: 'center',
|
||||
},
|
||||
label: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 17,
|
||||
textAlignVertical: 'center',
|
||||
includeFontPadding: false,
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
height: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const MenuItem = (props: MenuItemProps) => {
|
||||
const {
|
||||
centered,
|
||||
defaultMessage = '',
|
||||
i18nId,
|
||||
iconContainerStyle,
|
||||
iconName,
|
||||
isDestructor = false,
|
||||
labelComponent,
|
||||
@@ -87,7 +134,7 @@ const DrawerItem = (props: DrawerItemProps) => {
|
||||
>
|
||||
<View style={style.container}>
|
||||
{icon && (
|
||||
<View style={style.iconContainer}>
|
||||
<View style={[style.iconContainer, iconContainerStyle]}>
|
||||
{icon}
|
||||
</View>
|
||||
)}
|
||||
@@ -102,48 +149,4 @@ const DrawerItem = (props: DrawerItemProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flexDirection: 'row',
|
||||
minHeight: 50,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 45,
|
||||
height: 50,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginLeft: 5,
|
||||
},
|
||||
icon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
fontSize: 24,
|
||||
},
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
},
|
||||
labelContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingTop: 14,
|
||||
paddingBottom: 14,
|
||||
},
|
||||
centerLabel: {
|
||||
textAlign: 'center',
|
||||
textAlignVertical: 'center',
|
||||
},
|
||||
label: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 17,
|
||||
textAlignVertical: 'center',
|
||||
includeFontPadding: false,
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
height: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default DrawerItem;
|
||||
export default MenuItem;
|
||||
@@ -13,7 +13,8 @@ import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import * as Screens from '@constants/screens';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {showModalOverCurrentContext} from '@screens/navigation';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {bottomSheetModalOptions, showModal, showModalOverCurrentContext} from '@screens/navigation';
|
||||
import {fromAutoResponder, isFromWebhook, isPostPendingOrFailed, isSystemMessage} from '@utils/post';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
@@ -104,6 +105,7 @@ const Post = ({
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const theme = useTheme();
|
||||
const isTablet = useIsTablet();
|
||||
const styles = getStyleSheet(theme);
|
||||
const isAutoResponder = fromAutoResponder(post);
|
||||
const isPendingOrFailed = isPostPendingOrFailed(post);
|
||||
@@ -162,18 +164,16 @@ const Post = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const screen = 'PostOptions';
|
||||
const passProps = {
|
||||
location,
|
||||
post,
|
||||
showAddReaction,
|
||||
};
|
||||
|
||||
Keyboard.dismiss();
|
||||
const postOptionsRequest = requestAnimationFrame(() => {
|
||||
showModalOverCurrentContext(screen, passProps);
|
||||
cancelAnimationFrame(postOptionsRequest);
|
||||
});
|
||||
|
||||
const passProps = {location, post};
|
||||
const title = isTablet ? intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}) : '';
|
||||
|
||||
if (isTablet) {
|
||||
showModal(Screens.POST_OPTIONS, title, passProps, bottomSheetModalOptions(theme, 'close-post-options'));
|
||||
} else {
|
||||
showModalOverCurrentContext(Screens.POST_OPTIONS, passProps);
|
||||
}
|
||||
};
|
||||
|
||||
const highlightFlagged = isFlagged && !skipFlaggedHeader;
|
||||
|
||||
@@ -28,6 +28,7 @@ export const SETTINGS_SIDEBAR = 'SettingsSidebar';
|
||||
export const SSO = 'SSO';
|
||||
export const THREAD = 'Thread';
|
||||
export const USER_PROFILE = 'UserProfile';
|
||||
export const POST_OPTIONS = 'PostOptions';
|
||||
|
||||
export default {
|
||||
ABOUT,
|
||||
@@ -57,4 +58,5 @@ export default {
|
||||
SSO,
|
||||
THREAD,
|
||||
USER_PROFILE,
|
||||
POST_OPTIONS,
|
||||
};
|
||||
|
||||
@@ -45,10 +45,12 @@ const ThemeProvider = ({currentTeamId, children, themes}: Props) => {
|
||||
if (teamTheme?.value) {
|
||||
try {
|
||||
const theme = setThemeDefaults(JSON.parse(teamTheme.value) as Theme);
|
||||
EphemeralStore.theme = theme;
|
||||
requestAnimationFrame(() => {
|
||||
setNavigationStackStyles(theme);
|
||||
});
|
||||
if (theme !== EphemeralStore.theme) {
|
||||
EphemeralStore.theme = theme;
|
||||
requestAnimationFrame(() => {
|
||||
setNavigationStackStyles(theme);
|
||||
});
|
||||
}
|
||||
return theme;
|
||||
} catch {
|
||||
// no theme change
|
||||
|
||||
@@ -7,7 +7,7 @@ import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {updateLocalCustomStatus} from '@actions/local/user';
|
||||
import {unsetCustomStatus} from '@actions/remote/user';
|
||||
import DrawerItem from '@components/drawer_item';
|
||||
import MenuItem from '@components/menu_item';
|
||||
import {Events, Screens} from '@constants';
|
||||
import {SET_CUSTOM_STATUS_FAILURE} from '@constants/custom_status';
|
||||
import {useServerUrl} from '@context/server';
|
||||
@@ -68,7 +68,7 @@ const CustomStatus = ({isCustomStatusExpirySupported, isTablet, currentUser}: Cu
|
||||
}), [isTablet]);
|
||||
|
||||
return (
|
||||
<DrawerItem
|
||||
<MenuItem
|
||||
testID='settings.sidebar.custom_status.action'
|
||||
labelComponent={
|
||||
<CustomLabel
|
||||
|
||||
@@ -6,8 +6,8 @@ import {useIntl} from 'react-intl';
|
||||
import {TextStyle, View} from 'react-native';
|
||||
|
||||
import {logout} from '@actions/remote/session';
|
||||
import DrawerItem from '@components/drawer_item';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import MenuItem from '@components/menu_item';
|
||||
import {useServerDisplayName, useServerUrl} from '@context/server';
|
||||
import {alertServerLogout} from '@utils/server';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
@@ -49,7 +49,7 @@ const Settings = ({style, theme}: Props) => {
|
||||
}), [serverDisplayName, serverUrl, intl]);
|
||||
|
||||
return (
|
||||
<DrawerItem
|
||||
<MenuItem
|
||||
testID='account.logout.action'
|
||||
labelComponent={(
|
||||
<View>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
import React, {useCallback} from 'react';
|
||||
import {TextStyle} from 'react-native';
|
||||
|
||||
import DrawerItem from '@components/drawer_item';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import MenuItem from '@components/menu_item';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
type Props = {
|
||||
@@ -20,7 +20,7 @@ const SavedMessages = ({isTablet, style, theme}: Props) => {
|
||||
}), [isTablet]);
|
||||
|
||||
return (
|
||||
<DrawerItem
|
||||
<MenuItem
|
||||
testID='account.saved_messages.action'
|
||||
labelComponent={
|
||||
<FormattedText
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
import React, {useCallback} from 'react';
|
||||
import {TextStyle} from 'react-native';
|
||||
|
||||
import DrawerItem from '@components/drawer_item';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import MenuItem from '@components/menu_item';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
type Props = {
|
||||
@@ -20,7 +20,7 @@ const Settings = ({isTablet, style, theme}: Props) => {
|
||||
}), [isTablet]);
|
||||
|
||||
return (
|
||||
<DrawerItem
|
||||
<MenuItem
|
||||
testID='account.settings.action'
|
||||
labelComponent={
|
||||
<FormattedText
|
||||
|
||||
@@ -6,7 +6,7 @@ import {useIntl} from 'react-intl';
|
||||
import {DeviceEventEmitter, TextStyle} from 'react-native';
|
||||
|
||||
import {setStatus} from '@actions/remote/user';
|
||||
import DrawerItem from '@components/drawer_item';
|
||||
import MenuItem from '@components/menu_item';
|
||||
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import StatusLabel from '@components/status_label';
|
||||
import UserStatusIndicator from '@components/user_status';
|
||||
@@ -116,7 +116,7 @@ const UserStatus = ({currentUser, style, theme}: Props) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DrawerItem
|
||||
<MenuItem
|
||||
testID='account.status.action'
|
||||
labelComponent={
|
||||
<StatusLabel
|
||||
|
||||
@@ -5,8 +5,8 @@ import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {DeviceEventEmitter, TextStyle} from 'react-native';
|
||||
|
||||
import DrawerItem from '@components/drawer_item';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import MenuItem from '@components/menu_item';
|
||||
import {Events, Screens} from '@constants';
|
||||
import {ACCOUNT_OUTLINE_IMAGE} from '@constants/profile';
|
||||
import {showModal} from '@screens/navigation';
|
||||
@@ -32,7 +32,7 @@ const YourProfile = ({isTablet, style, theme}: Props) => {
|
||||
}), [isTablet, theme]);
|
||||
|
||||
return (
|
||||
<DrawerItem
|
||||
<MenuItem
|
||||
testID='account.your_profile.action'
|
||||
labelComponent={
|
||||
<FormattedText
|
||||
|
||||
@@ -21,7 +21,7 @@ const withGestures = (Screen: React.ComponentType, styles: StyleProp<ViewStyle>)
|
||||
if (Platform.OS === 'android') {
|
||||
return (
|
||||
<GestureHandlerRootView style={[{flex: 1}, styles]}>
|
||||
<Screen {...props}/>
|
||||
<Screen {...props}/>
|
||||
</GestureHandlerRootView>
|
||||
)
|
||||
}
|
||||
@@ -186,9 +186,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
|
||||
// case 'PinnedPosts':
|
||||
// screen = require('@screens/pinned_posts').default;
|
||||
// break;
|
||||
// case 'PostOptions':
|
||||
// screen = require('@screens/post_options').default;
|
||||
// break;
|
||||
case Screens.POST_OPTIONS:
|
||||
screen = withServerDatabase(require('@screens/post_options').default);
|
||||
break;
|
||||
// case 'ReactionList':
|
||||
// screen = require('@screens/reaction_list').default;
|
||||
// break;
|
||||
|
||||
@@ -75,6 +75,30 @@ export const loginAnimationOptions = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const bottomSheetModalOptions = (theme: Theme, closeButtonId: string) => {
|
||||
const closeButton = CompassIcon.getImageSourceSync('close', 24, theme.centerChannelColor);
|
||||
return {
|
||||
modalPresentationStyle: OptionsModalPresentationStyle.formSheet,
|
||||
modal: {
|
||||
swipeToDismiss: true,
|
||||
},
|
||||
topBar: {
|
||||
leftButtons: [{
|
||||
id: closeButtonId,
|
||||
icon: closeButton,
|
||||
testID: closeButtonId,
|
||||
}],
|
||||
leftButtonColor: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
background: {
|
||||
color: theme.centerChannelBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Navigation.setDefaultOptions({
|
||||
layout: {
|
||||
|
||||
@@ -565,32 +589,12 @@ export async function bottomSheet({title, renderContent, snapPoints, initialSnap
|
||||
const isTablet = Device.IS_TABLET && !isSplitView;
|
||||
|
||||
if (isTablet) {
|
||||
const closeButton = CompassIcon.getImageSourceSync('close', 24, theme.centerChannelColor);
|
||||
showModal(Screens.BOTTOM_SHEET, title, {
|
||||
closeButtonId,
|
||||
initialSnapIndex,
|
||||
renderContent,
|
||||
snapPoints,
|
||||
}, {
|
||||
modalPresentationStyle: OptionsModalPresentationStyle.formSheet,
|
||||
modal: {
|
||||
swipeToDismiss: true,
|
||||
},
|
||||
topBar: {
|
||||
leftButtons: [{
|
||||
id: closeButtonId,
|
||||
icon: closeButton,
|
||||
testID: closeButtonId,
|
||||
}],
|
||||
leftButtonColor: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
background: {
|
||||
color: theme.centerChannelBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
},
|
||||
});
|
||||
}, bottomSheetModalOptions(theme, closeButtonId));
|
||||
} else {
|
||||
showModalOverCurrentContext(Screens.BOTTOM_SHEET, {
|
||||
initialSnapIndex,
|
||||
|
||||
73
app/screens/post_options/components/options/base_option.tsx
Normal file
73
app/screens/post_options/components/options/base_option.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import MenuItem from '@components/menu_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
destructive: {
|
||||
color: theme.dndIndicator,
|
||||
},
|
||||
label: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 200),
|
||||
},
|
||||
iconContainerStyle: {
|
||||
marginLeft: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
type BaseOptionType = {
|
||||
i18nId: string;
|
||||
defaultMessage: string;
|
||||
iconName: string;
|
||||
onPress: () => void;
|
||||
testID: string;
|
||||
isDestructive?: boolean;
|
||||
}
|
||||
|
||||
const BaseOption = ({
|
||||
i18nId,
|
||||
defaultMessage,
|
||||
iconName,
|
||||
onPress,
|
||||
testID,
|
||||
isDestructive = false,
|
||||
}: BaseOptionType) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const labelStyles = useMemo(() => {
|
||||
if (isDestructive) {
|
||||
return [styles.label, styles.destructive];
|
||||
}
|
||||
return styles.label;
|
||||
}, [isDestructive, styles.label, styles.destructive]);
|
||||
|
||||
const label = useMemo(() => (
|
||||
<FormattedText
|
||||
id={i18nId}
|
||||
defaultMessage={defaultMessage}
|
||||
style={labelStyles}
|
||||
/>
|
||||
), [i18nId, defaultMessage, labelStyles]);
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
testID={testID}
|
||||
labelComponent={label}
|
||||
iconContainerStyle={styles.iconContainerStyle}
|
||||
iconName={iconName}
|
||||
onPress={onPress}
|
||||
separator={false}
|
||||
theme={theme}
|
||||
isDestructor={isDestructive}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default BaseOption;
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {t} from '@i18n';
|
||||
|
||||
import BaseOption from './base_option';
|
||||
|
||||
const CopyPermalinkOption = () => {
|
||||
const handleCopyLink = () => {
|
||||
//todo:
|
||||
};
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={t('get_post_link_modal.title')}
|
||||
defaultMessage='Copy Link'
|
||||
onPress={handleCopyLink}
|
||||
iconName='link-variant'
|
||||
testID='post.options.copy.permalink'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyPermalinkOption;
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {t} from '@i18n';
|
||||
|
||||
import BaseOption from './base_option';
|
||||
|
||||
const CopyTextOption = () => {
|
||||
const handleCopyText = () => {
|
||||
//todo:
|
||||
};
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={t('mobile.post_info.copy_text')}
|
||||
defaultMessage='Copy Text'
|
||||
iconName='content-copy'
|
||||
onPress={handleCopyText}
|
||||
testID='post.options.copy.text'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyTextOption;
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {t} from '@i18n';
|
||||
|
||||
import BaseOption from './base_option';
|
||||
|
||||
//fixme: wire up handleDeletePost
|
||||
const DeletePostOption = () => {
|
||||
const handleDeletePost = () => null;
|
||||
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={t('post_info.del')}
|
||||
defaultMessage='Delete'
|
||||
iconName='trash-can-outline'
|
||||
onPress={handleDeletePost}
|
||||
testID='post.options.delete.post'
|
||||
isDestructive={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeletePostOption;
|
||||
26
app/screens/post_options/components/options/edit_option.tsx
Normal file
26
app/screens/post_options/components/options/edit_option.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {t} from '@i18n';
|
||||
|
||||
import BaseOption from './base_option';
|
||||
|
||||
const EditOption = () => {
|
||||
const handleEdit = () => {
|
||||
//todo:
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={t('post_info.edit')}
|
||||
defaultMessage='Edit'
|
||||
onPress={handleEdit}
|
||||
iconName='pencil-outline'
|
||||
testID='post.options.edit'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditOption;
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {Screens} from '@constants';
|
||||
import {t} from '@i18n';
|
||||
|
||||
import BaseOption from './base_option';
|
||||
|
||||
type FollowThreadOptionProps = {
|
||||
thread?: any;
|
||||
location?: typeof Screens[keyof typeof Screens];
|
||||
};
|
||||
|
||||
//todo: to implement CRT follow thread
|
||||
|
||||
const FollowThreadOption = ({thread}: FollowThreadOptionProps) => {
|
||||
let id: string;
|
||||
let defaultMessage: string;
|
||||
let icon: string;
|
||||
|
||||
if (thread.is_following) {
|
||||
icon = 'message-minus-outline';
|
||||
if (thread?.participants?.length) {
|
||||
id = t('threads.unfollowThread');
|
||||
defaultMessage = 'Unfollow Thread';
|
||||
} else {
|
||||
id = t('threads.unfollowMessage');
|
||||
defaultMessage = 'Unfollow Message';
|
||||
}
|
||||
} else {
|
||||
icon = 'message-plus-outline';
|
||||
if (thread?.participants?.length) {
|
||||
id = t('threads.followThread');
|
||||
defaultMessage = 'Follow Thread';
|
||||
} else {
|
||||
id = t('threads.followMessage');
|
||||
defaultMessage = 'Follow Message';
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleFollow = () => {
|
||||
//todo:
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={id}
|
||||
defaultMessage={defaultMessage}
|
||||
testID='post.options.follow.thread'
|
||||
iconName={icon}
|
||||
onPress={handleToggleFollow}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default FollowThreadOption;
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {t} from '@i18n';
|
||||
|
||||
import BaseOption from './base_option';
|
||||
|
||||
//fixme: wire up canMarkAsUnread
|
||||
const MarkAsUnreadOption = () => {
|
||||
const handleMarkUnread = () => {
|
||||
//todo:
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={t('mobile.post_info.mark_unread')}
|
||||
defaultMessage='Mark as Unread'
|
||||
iconName='mark-as-unread'
|
||||
onPress={handleMarkUnread}
|
||||
testID='post.options.mark.unread'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkAsUnreadOption;
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {t} from '@i18n';
|
||||
|
||||
import BaseOption from './base_option';
|
||||
|
||||
type PinChannelProps = {
|
||||
isPostPinned: boolean;
|
||||
}
|
||||
|
||||
//fixme: wire up handlePinChannel
|
||||
const PinChannelOption = ({isPostPinned}: PinChannelProps) => {
|
||||
//todo: add useCallback for the handler callbacks
|
||||
const handlePinPost = () => null;
|
||||
const handleUnpinPost = () => null;
|
||||
|
||||
let defaultMessage;
|
||||
let id;
|
||||
let key;
|
||||
let onPress;
|
||||
|
||||
if (isPostPinned) {
|
||||
defaultMessage = 'Unpin from Channel';
|
||||
id = t('mobile.post_info.unpin');
|
||||
key = 'unpin';
|
||||
onPress = handleUnpinPost;
|
||||
} else {
|
||||
defaultMessage = 'Pin to Channel';
|
||||
id = t('mobile.post_info.pin');
|
||||
key = 'pin';
|
||||
onPress = handlePinPost;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={id}
|
||||
defaultMessage={defaultMessage}
|
||||
iconName='pin-outline'
|
||||
onPress={onPress}
|
||||
testID={`post.options.${key}.channel`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default PinChannelOption;
|
||||
25
app/screens/post_options/components/options/reply_option.tsx
Normal file
25
app/screens/post_options/components/options/reply_option.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {t} from '@i18n';
|
||||
|
||||
import BaseOption from './base_option';
|
||||
|
||||
const ReplyOption = () => {
|
||||
const handleReply = () => {
|
||||
//todo:
|
||||
};
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={t('mobile.post_info.reply')}
|
||||
defaultMessage='Reply'
|
||||
iconName='reply-outline'
|
||||
onPress={handleReply}
|
||||
testID='post.options.reply'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReplyOption;
|
||||
38
app/screens/post_options/components/options/save_option.tsx
Normal file
38
app/screens/post_options/components/options/save_option.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {t} from '@i18n';
|
||||
|
||||
import BaseOption from './base_option';
|
||||
|
||||
type CopyTextProps = {
|
||||
isSaved: boolean;
|
||||
}
|
||||
|
||||
const SaveOption = ({isSaved}: CopyTextProps) => {
|
||||
const handleUnsavePost = () => {
|
||||
//todo:
|
||||
};
|
||||
|
||||
const handleSavePost = () => {
|
||||
//todo:
|
||||
};
|
||||
|
||||
const id = isSaved ? t('mobile.post_info.unsave') : t('mobile.post_info.save');
|
||||
const defaultMessage = isSaved ? 'Unsave' : 'Save';
|
||||
const onPress = isSaved ? handleUnsavePost : handleSavePost;
|
||||
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={id}
|
||||
defaultMessage={defaultMessage}
|
||||
iconName='bookmark-outline'
|
||||
onPress={onPress}
|
||||
testID='post.options.flag.unflag'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SaveOption;
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.04),
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 4,
|
||||
},
|
||||
icon: {
|
||||
...typography('Body', 1000),
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type PickReactionProps = {
|
||||
openEmojiPicker: () => void;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
const PickReaction = ({openEmojiPicker, width, height}: PickReactionProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
return (
|
||||
<View
|
||||
style={[styles.container, {
|
||||
width, height,
|
||||
}]}
|
||||
>
|
||||
<CompassIcon
|
||||
onPress={openEmojiPicker}
|
||||
name='emoticon-plus-outline'
|
||||
style={styles.icon}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default PickReaction;
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {Pressable, PressableStateCallbackType, View} from 'react-native';
|
||||
|
||||
import Emoji from '@components/emoji';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
emoji: {
|
||||
color: '#000',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
highlight: {
|
||||
backgroundColor: changeOpacity(theme.linkColor, 0.1),
|
||||
},
|
||||
reactionContainer: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.04),
|
||||
borderRadius: 4,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type ReactionProps = {
|
||||
onPressReaction: (emoji: string) => void;
|
||||
emoji: string;
|
||||
iconSize: number;
|
||||
containerSize: number;
|
||||
}
|
||||
const Reaction = ({onPressReaction, emoji, iconSize, containerSize}: ReactionProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const handleReactionPressed = useCallback(() => {
|
||||
onPressReaction(emoji);
|
||||
}, [onPressReaction, emoji]);
|
||||
|
||||
const highlightedStyle = useCallback(({pressed}: PressableStateCallbackType) => pressed && styles.highlight, [styles.highlight]);
|
||||
const reactionStyle = useMemo(() => [
|
||||
styles.reactionContainer,
|
||||
{
|
||||
width: containerSize,
|
||||
height: containerSize,
|
||||
},
|
||||
], [containerSize]);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
key={emoji}
|
||||
onPress={handleReactionPressed}
|
||||
style={highlightedStyle}
|
||||
>
|
||||
<View
|
||||
style={reactionStyle}
|
||||
>
|
||||
<Emoji
|
||||
emojiName={emoji}
|
||||
textStyle={styles.emoji}
|
||||
size={iconSize}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
export default Reaction;
|
||||
41
app/screens/post_options/components/reaction_bar/index.ts
Normal file
41
app/screens/post_options/components/reaction_bar/index.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {catchError, switchMap} from 'rxjs/operators';
|
||||
|
||||
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
import {safeParseJSON} from '@utils/helpers';
|
||||
|
||||
import ReactionBar from './reaction_bar';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type SystemModel from '@typings/database/models/servers/system';
|
||||
|
||||
const DEFAULT_EMOJIS = [
|
||||
'thumbsup',
|
||||
'smiley',
|
||||
'white_check_mark',
|
||||
'heart',
|
||||
'eyes',
|
||||
'raised_hands',
|
||||
];
|
||||
|
||||
const mergeRecentWithDefault = (recentEmojis: string[]) => {
|
||||
const filterUsed = DEFAULT_EMOJIS.filter((e) => !recentEmojis.includes(e));
|
||||
return recentEmojis.concat(filterUsed).splice(0, 6);
|
||||
};
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
|
||||
recentEmojis: database.
|
||||
get<SystemModel>(MM_TABLES.SERVER.SYSTEM).
|
||||
findAndObserve(SYSTEM_IDENTIFIERS.RECENT_REACTIONS).
|
||||
pipe(
|
||||
switchMap((recent) => of$(mergeRecentWithDefault(safeParseJSON(recent.value) as string[]))),
|
||||
catchError(() => of$(mergeRecentWithDefault([]))),
|
||||
),
|
||||
}));
|
||||
|
||||
export default withDatabase(enhanced(ReactionBar));
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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 {DeviceEventEmitter, useWindowDimensions, View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {Events, Screens} from '@constants';
|
||||
import {
|
||||
LARGE_CONTAINER_SIZE,
|
||||
LARGE_ICON_SIZE,
|
||||
REACTION_PICKER_HEIGHT,
|
||||
SMALL_CONTAINER_SIZE,
|
||||
SMALL_ICON_BREAKPOINT,
|
||||
SMALL_ICON_SIZE,
|
||||
} from '@constants/reaction_picker';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {showModal} from '@screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import PickReaction from './components/pick_reaction';
|
||||
import Reaction from './components/reaction';
|
||||
|
||||
type QuickReactionProps = {
|
||||
recentEmojis: string[];
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
height: REACTION_PICKER_HEIGHT,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const ReactionBar = ({recentEmojis = []}: QuickReactionProps) => {
|
||||
const theme = useTheme();
|
||||
const intl = useIntl();
|
||||
const {width} = useWindowDimensions();
|
||||
const isSmallDevice = width < SMALL_ICON_BREAKPOINT;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const handleEmojiPress = useCallback((emoji: string) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('>>> selected this emoji', emoji);
|
||||
}, []);
|
||||
|
||||
const openEmojiPicker = useCallback(async () => {
|
||||
DeviceEventEmitter.emit(Events.CLOSE_BOTTOM_SHEET);
|
||||
await EphemeralStore.waitUntilScreensIsRemoved(Screens.BOTTOM_SHEET);
|
||||
|
||||
const closeButton = CompassIcon.getImageSourceSync('close', 24, theme.sidebarHeaderTextColor);
|
||||
const screen = Screens.EMOJI_PICKER;
|
||||
const title = intl.formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'});
|
||||
const passProps = {closeButton, onEmojiPress: handleEmojiPress};
|
||||
|
||||
showModal(screen, title, passProps);
|
||||
}, [intl, theme]);
|
||||
|
||||
let containerSize = LARGE_CONTAINER_SIZE;
|
||||
let iconSize = LARGE_ICON_SIZE;
|
||||
|
||||
if (isSmallDevice) {
|
||||
containerSize = SMALL_CONTAINER_SIZE;
|
||||
iconSize = SMALL_ICON_SIZE;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{
|
||||
recentEmojis.map((emoji) => {
|
||||
return (
|
||||
<Reaction
|
||||
key={emoji}
|
||||
onPressReaction={handleEmojiPress}
|
||||
emoji={emoji}
|
||||
iconSize={iconSize}
|
||||
containerSize={containerSize}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
<PickReaction
|
||||
openEmojiPicker={openEmojiPicker}
|
||||
width={containerSize}
|
||||
height={containerSize}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReactionBar;
|
||||
6
app/screens/post_options/index.ts
Normal file
6
app/screens/post_options/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import PostOptions from './post_options';
|
||||
|
||||
export default PostOptions;
|
||||
108
app/screens/post_options/post_options.tsx
Normal file
108
app/screens/post_options/post_options.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {ITEM_HEIGHT} from '@components/menu_item';
|
||||
import {Screens} from '@constants';
|
||||
import BottomSheet from '@screens/bottom_sheet';
|
||||
import {isSystemMessage} from '@utils/post';
|
||||
|
||||
import CopyLinkOption from './components/options/copy_link_option';
|
||||
import CopyTextOption from './components/options/copy_text_option';
|
||||
import DeletePostOption from './components/options/delete_post_option';
|
||||
import EditOption from './components/options/edit_option';
|
||||
import FollowThreadOption from './components/options/follow_option';
|
||||
import MarkAsUnreadOption from './components/options/mark_unread_option';
|
||||
import PinChannelOption from './components/options/pin_channel_option';
|
||||
import ReplyOption from './components/options/reply_option';
|
||||
import SaveOption from './components/options/save_option';
|
||||
import ReactionBar from './components/reaction_bar';
|
||||
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
//fixme: some props are optional - review them
|
||||
|
||||
type PostOptionsProps = {
|
||||
canAddReaction?: boolean;
|
||||
canCopyPermalink?: boolean;
|
||||
canCopyText?: boolean;
|
||||
canDelete?: boolean;
|
||||
canEdit?: boolean;
|
||||
canEditUntil?: number;
|
||||
canMarkAsUnread?: boolean;
|
||||
canPin?: boolean;
|
||||
canSave?: boolean;
|
||||
canReply?: boolean;
|
||||
isSaved?: boolean;
|
||||
location: typeof Screens[keyof typeof Screens];
|
||||
post: PostModel;
|
||||
thread?: Partial<PostModel>;
|
||||
};
|
||||
|
||||
const PostOptions = ({
|
||||
canAddReaction = true,
|
||||
canCopyPermalink = true,
|
||||
canCopyText = true,
|
||||
canDelete = true,
|
||||
canEdit = true,
|
||||
canEditUntil = -1,
|
||||
canMarkAsUnread = true,
|
||||
canPin = true,
|
||||
canReply = true,
|
||||
canSave = true,
|
||||
isSaved = true,
|
||||
location,
|
||||
post,
|
||||
thread,
|
||||
}: PostOptionsProps) => {
|
||||
const shouldRenderEdit = canEdit && (canEditUntil === -1 || canEditUntil > Date.now());
|
||||
const shouldRenderFollow = !(location !== Screens.CHANNEL || !thread);
|
||||
|
||||
const snapPoints = [
|
||||
canAddReaction, canCopyPermalink, canCopyText,
|
||||
canDelete, shouldRenderEdit, shouldRenderFollow,
|
||||
canMarkAsUnread, canPin, canReply, canSave,
|
||||
].reduce((acc, v) => {
|
||||
return v ? acc + 1 : acc;
|
||||
}, 0);
|
||||
|
||||
const renderContent = () => {
|
||||
return (
|
||||
<>
|
||||
{canAddReaction && <ReactionBar/>}
|
||||
{canReply && <ReplyOption/>}
|
||||
{shouldRenderFollow &&
|
||||
<FollowThreadOption
|
||||
location={location}
|
||||
thread={thread}
|
||||
/>
|
||||
}
|
||||
{canMarkAsUnread && !isSystemMessage(post) && (
|
||||
<MarkAsUnreadOption/>
|
||||
)}
|
||||
{canCopyPermalink && <CopyLinkOption/>}
|
||||
{canSave &&
|
||||
<SaveOption
|
||||
isSaved={isSaved}
|
||||
/>
|
||||
}
|
||||
{canCopyText && <CopyTextOption/>}
|
||||
{canPin && <PinChannelOption isPostPinned={post.isPinned}/>}
|
||||
{shouldRenderEdit && <EditOption/>}
|
||||
{canDelete && <DeletePostOption/>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
renderContent={renderContent}
|
||||
closeButtonId='close-post-options'
|
||||
initialSnapIndex={0}
|
||||
snapPoints={[((snapPoints + 2) * ITEM_HEIGHT), 10]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostOptions;
|
||||
@@ -5,7 +5,7 @@ import merge from 'deepmerge';
|
||||
import {StatusBar, StyleSheet} from 'react-native';
|
||||
import tinyColor from 'tinycolor2';
|
||||
|
||||
import {Preferences, Screens} from '@constants';
|
||||
import {Preferences} from '@constants';
|
||||
import {appearanceControlledScreens, mergeNavigationOptions} from '@screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
|
||||
@@ -129,7 +129,7 @@ export function setNavigatorStyles(componentId: string, theme: Theme, additional
|
||||
|
||||
export function setNavigationStackStyles(theme: Theme) {
|
||||
EphemeralStore.allNavigationComponentIds.forEach((componentId) => {
|
||||
if (componentId !== Screens.BOTTOM_SHEET && !appearanceControlledScreens.includes(componentId)) {
|
||||
if (!appearanceControlledScreens.includes(componentId)) {
|
||||
setNavigatorStyles(componentId, theme);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"emoji_skin.medium_light_skin_tone": "medium light skin tone",
|
||||
"emoji_skin.medium_skin_tone": "medium skin tone",
|
||||
"file_upload.fileAbove": "Files must be less than {max}",
|
||||
"get_post_link_modal.title": "Copy Link",
|
||||
"intro.add_people": "Add People",
|
||||
"intro.channel_details": "Details",
|
||||
"intro.created_by": "created by {creator} on {date}.",
|
||||
@@ -259,6 +260,13 @@
|
||||
"mobile.permission_denied_dismiss": "Don't Allow",
|
||||
"mobile.permission_denied_retry": "Settings",
|
||||
"mobile.post_info.add_reaction": "Add Reaction",
|
||||
"mobile.post_info.copy_text": "Copy Text",
|
||||
"mobile.post_info.mark_unread": "Mark as Unread",
|
||||
"mobile.post_info.pin": "Pin to Channel",
|
||||
"mobile.post_info.reply": "Reply",
|
||||
"mobile.post_info.save": "Save",
|
||||
"mobile.post_info.unpin": "Unpin from Channel",
|
||||
"mobile.post_info.unsave": "Unsave",
|
||||
"mobile.post_pre_header.flagged": "Saved",
|
||||
"mobile.post_pre_header.pinned": "Pinned",
|
||||
"mobile.post_pre_header.pinned_flagged": "Pinned and Saved",
|
||||
@@ -368,6 +376,8 @@
|
||||
"post_body.deleted": "(message deleted)",
|
||||
"post_info.auto_responder": "AUTOMATIC REPLY",
|
||||
"post_info.bot": "BOT",
|
||||
"post_info.del": "Delete",
|
||||
"post_info.edit": "Edit",
|
||||
"post_info.guest": "GUEST",
|
||||
"post_info.system": "System",
|
||||
"post_message_view.edited": "(edited)",
|
||||
@@ -397,6 +407,10 @@
|
||||
"status_dropdown.set_ooo": "Out Of Office",
|
||||
"team_list.no_other_teams.description": "To join another team, ask a Team Admin for an invitation, or create your own team.",
|
||||
"team_list.no_other_teams.title": "No additional teams to join",
|
||||
"threads.followMessage": "Follow Message",
|
||||
"threads.followThread": "Follow Thread",
|
||||
"threads.unfollowMessage": "Unfollow Message",
|
||||
"threads.unfollowThread": "Unfollow Thread",
|
||||
"user.edit_profile.email.auth_service": "Login occurs through {service}. Email cannot be updated. Email address used for notifications is {email}.",
|
||||
"user.edit_profile.email.web_client": "Email must be updated using a web client or desktop application.",
|
||||
"user.settings.general.email": "Email",
|
||||
|
||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -5,6 +5,7 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mattermost-mobile",
|
||||
"version": "2.0.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache 2.0",
|
||||
|
||||
Reference in New Issue
Block a user