diff --git a/app/constants/snack_bar.ts b/app/constants/snack_bar.ts index 21b063653..57491b93f 100644 --- a/app/constants/snack_bar.ts +++ b/app/constants/snack_bar.ts @@ -13,16 +13,24 @@ export const SNACK_BAR_TYPE = keyMirror({ MESSAGE_COPIED: null, MUTE_CHANNEL: null, REMOVE_CHANNEL_USER: null, + TEXT_COPIED: null, UNFAVORITE_CHANNEL: null, UNMUTE_CHANNEL: null, UNFOLLOW_THREAD: null, }); +export const MESSAGE_TYPE = { + SUCCESS: 'success', + ERROR: 'error', + DEFAULT: 'default', +}; + type SnackBarConfig = { id: string; defaultMessage: string; iconName: string; canUndo: boolean; + type?: typeof MESSAGE_TYPE[keyof typeof MESSAGE_TYPE]; }; export const SNACK_BAR_CONFIG: Record = { @@ -55,6 +63,7 @@ export const SNACK_BAR_CONFIG: Record = { defaultMessage: 'Link copied to clipboard', iconName: 'link-variant', canUndo: false, + type: MESSAGE_TYPE.SUCCESS, }, MESSAGE_COPIED: { id: t('snack.bar.message.copied'), @@ -74,6 +83,13 @@ export const SNACK_BAR_CONFIG: Record = { iconName: 'check', canUndo: true, }, + TEXT_COPIED: { + id: t('snack.bar.text.copied'), + defaultMessage: 'Copied to clipboard', + iconName: 'content-copy', + canUndo: false, + type: MESSAGE_TYPE.SUCCESS, + }, UNFAVORITE_CHANNEL: { id: t('snack.bar.unfavorite.channel'), defaultMessage: 'This channel was unfavorited', diff --git a/app/constants/versions.ts b/app/constants/versions.ts index bb1a55ab8..05a70da3e 100644 --- a/app/constants/versions.ts +++ b/app/constants/versions.ts @@ -2,3 +2,10 @@ // See LICENSE.txt for license information. export const GM_AS_DM_VERSION = [9, 1, 0]; + +export const OS_VERSION = { + ANDROID: 'android', + IOS: 'ios', +}; + +export const ANDROID_33 = 33; diff --git a/app/screens/channel_info/title/public_private/public_private.tsx b/app/screens/channel_info/title/public_private/public_private.tsx index 96fd2ee25..f74cdbb3c 100644 --- a/app/screens/channel_info/title/public_private/public_private.tsx +++ b/app/screens/channel_info/title/public_private/public_private.tsx @@ -1,10 +1,20 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react'; -import {Text} from 'react-native'; +import {useManagedConfig} from '@mattermost/react-native-emm'; +import Clipboard from '@react-native-clipboard/clipboard'; +import React, {useCallback} from 'react'; +import {useIntl} from 'react-intl'; +import {Platform, StyleSheet, Text, View} from 'react-native'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; +import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item'; +import {SNACK_BAR_TYPE} from '@constants/snack_bar'; +import {ANDROID_33, OS_VERSION} from '@constants/versions'; import {useTheme} from '@context/theme'; +import {bottomSheet, dismissBottomSheet} from '@screens/navigation'; +import {bottomSheetSnapPoint} from '@utils/helpers'; +import {showSnackBar} from '@utils/snack_bar'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; @@ -25,11 +35,69 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ }, })); +const style = StyleSheet.create({ + bottomsheet: { + flex: 1, + }, +}); + const PublicPrivate = ({displayName, purpose}: Props) => { + const intl = useIntl(); const theme = useTheme(); + const managedConfig = useManagedConfig(); + const {bottom} = useSafeAreaInsets(); + const styles = getStyleSheet(theme); const publicPrivateTestId = 'channel_info.title.public_private'; + const onCopy = useCallback(async () => { + Clipboard.setString(purpose!); + await dismissBottomSheet(); + if ((Platform.OS === OS_VERSION.ANDROID && Number(Platform.Version) < ANDROID_33) || Platform.OS === OS_VERSION.IOS) { + showSnackBar({barType: SNACK_BAR_TYPE.TEXT_COPIED}); + } + }, [purpose]); + + const handleLongPress = useCallback(() => { + if (managedConfig?.copyAndPasteProtection !== 'true') { + const renderContent = () => { + return ( + + + { + dismissBottomSheet(); + }} + testID={`${publicPrivateTestId}.bottom_sheet.cancel`} + text={intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})} + /> + + ); + }; + + bottomSheet({ + closeButtonId: 'close-mardown-link', + renderContent, + snapPoints: [1, bottomSheetSnapPoint(2, ITEM_HEIGHT, bottom)], + title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}), + theme, + }); + } + }, [ + bottom, + theme, + onCopy, + intl.formatMessage, + managedConfig?.copyAndPasteProtection, + ]); + return ( <> { {Boolean(purpose) && diff --git a/app/screens/snack_bar/index.tsx b/app/screens/snack_bar/index.tsx index 653acdb0c..05e5a3a52 100644 --- a/app/screens/snack_bar/index.tsx +++ b/app/screens/snack_bar/index.tsx @@ -19,7 +19,7 @@ import Animated, { import Toast, {TOAST_HEIGHT} from '@components/toast'; import {Navigation as NavigationConstants, Screens} from '@constants'; -import {SNACK_BAR_CONFIG, SNACK_BAR_TYPE} from '@constants/snack_bar'; +import {MESSAGE_TYPE, SNACK_BAR_CONFIG} from '@constants/snack_bar'; import {TABLET_SIDEBAR_WIDTH} from '@constants/view'; import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; @@ -145,6 +145,22 @@ const SnackBar = ({ ] as AnimatedStyleProp; }, [theme, barType]); + const toastStyle = useMemo(() => { + let backgroundColor: string; + switch (config?.type) { + case MESSAGE_TYPE.SUCCESS: + backgroundColor = theme.onlineIndicator; + break; + case MESSAGE_TYPE.ERROR: + backgroundColor = theme.errorTextColor; + break; + default: + backgroundColor = theme.centerChannelColor; + break; + } + return [styles.toast, {backgroundColor}]; + }, [theme, config?.type]); + const animatedMotion = useAnimatedStyle(() => { return { opacity: interpolate(offset.value, [0, 100], [1, 0], Extrapolation.EXTEND), @@ -254,7 +270,7 @@ const SnackBar = ({ {id: config.id, defaultMessage: config.defaultMessage}, messageValues, )} - style={[styles.toast, barType === SNACK_BAR_TYPE.LINK_COPIED && {backgroundColor: theme.onlineIndicator}]} + style={toastStyle} textStyle={styles.text} > {config.canUndo && onAction && ( diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index eb879a5e3..6c656d9bd 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -125,6 +125,7 @@ "channel_info.convert_private_title": "Convert {displayName} to a private channel?", "channel_info.copied": "Copied", "channel_info.copy_link": "Copy Link", + "channel_info.copy_purpose_text": "Copy Purpose Text", "channel_info.custom_status": "Custom status:", "channel_info.edit_header": "Edit Header", "channel_info.error_close": "Close", @@ -1000,6 +1001,7 @@ "snack.bar.message.copied": "Text copied to clipboard", "snack.bar.mute.channel": "This channel was muted", "snack.bar.remove.user": "1 member was removed from the channel", + "snack.bar.text.copied": "Copied to clipboard", "snack.bar.undo": "Undo", "snack.bar.unfavorite.channel": "This channel was unfavorited", "snack.bar.unfollow.thread": "Thread unfollowed",