forked from Ivasoft/mattermost-mobile
[Gekidou MM-46365] fix reaction bar space and update bottom sheet styles (#6634)
* updated styles for reaction bar and made pick reaction pressable * removed unused style import * removed unused style import * updated user avatars, user presence, thread options bottom sheets to new style also * updated status bottom sheet, thread options, and some tweaks to the bottom sheet main file * fixed a few minor styling issues * used proper bottom inset in user presence bottom sheet * various updates to bottom sheets * used negative top position instead of negative margin * updated camera type bottom sheet * further refinements to bottom sheets * updates to emoji bar and profile image picker Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
@@ -283,7 +283,6 @@ const OptionItem = ({
|
||||
}
|
||||
</View>
|
||||
);
|
||||
|
||||
if (Object.values(TouchableOptionTypes).includes(type)) {
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress}>
|
||||
|
||||
@@ -2,60 +2,36 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
import {CameraOptions} from 'react-native-image-picker';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import SlideUpPanelItem from '@components/slide_up_panel_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = {
|
||||
onPress: (options: CameraOptions) => void;
|
||||
}
|
||||
|
||||
const getStyle = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
center: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
height: 200,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
options: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-evenly',
|
||||
width: '100%',
|
||||
marginBottom: 50,
|
||||
},
|
||||
optionContainer: {
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
title: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
text: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 15,
|
||||
...typography('Heading', 600, 'SemiBold'),
|
||||
marginBottom: 8,
|
||||
},
|
||||
|
||||
}));
|
||||
|
||||
const CameraType = ({onPress}: Props) => {
|
||||
const theme = useTheme();
|
||||
const isTablet = useIsTablet();
|
||||
const style = getStyle(theme);
|
||||
const intl = useIntl();
|
||||
|
||||
const onPhoto = async () => {
|
||||
const options: CameraOptions = {
|
||||
@@ -80,54 +56,26 @@ const CameraType = ({onPress}: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<View>
|
||||
{!isTablet &&
|
||||
<FormattedText
|
||||
id='camera_type.title'
|
||||
defaultMessage='Choose an action'
|
||||
id='mobile.camera_type.title'
|
||||
defaultMessage='Camera options'
|
||||
style={style.title}
|
||||
/>
|
||||
}
|
||||
<View style={style.options}>
|
||||
<View style={style.flex}>
|
||||
<TouchableWithFeedback
|
||||
onPress={onPhoto}
|
||||
testID='camera_type.photo'
|
||||
>
|
||||
<View style={style.center}>
|
||||
<CompassIcon
|
||||
color={theme.centerChannelColor}
|
||||
name='camera-outline'
|
||||
size={38}
|
||||
/>
|
||||
<FormattedText
|
||||
id='camera_type.photo.option'
|
||||
defaultMessage='Capture Photo'
|
||||
style={style.text}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
</View>
|
||||
<View style={style.flex}>
|
||||
<TouchableWithFeedback
|
||||
onPress={onVideo}
|
||||
testID='camera_type.video'
|
||||
>
|
||||
<View style={style.center}>
|
||||
<CompassIcon
|
||||
color={theme.centerChannelColor}
|
||||
name='video-outline'
|
||||
size={38}
|
||||
/>
|
||||
<FormattedText
|
||||
id='camera_type.video.option'
|
||||
defaultMessage='Record Video'
|
||||
style={style.text}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
</View>
|
||||
</View>
|
||||
<SlideUpPanelItem
|
||||
icon='camera-outline'
|
||||
onPress={onPhoto}
|
||||
testID='camera_type.photo'
|
||||
text={intl.formatMessage({id: 'camera_type.photo.option', defaultMessage: 'Capture Photo'})}
|
||||
/>
|
||||
<SlideUpPanelItem
|
||||
icon='video-outline'
|
||||
onPress={onVideo}
|
||||
testID='camera_type.video'
|
||||
text={intl.formatMessage({id: 'camera_type.video.option', defaultMessage: 'Record Video'})}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,14 +5,18 @@ import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Alert, StyleSheet} from 'react-native';
|
||||
import {CameraOptions} from 'react-native-image-picker';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {ICON_SIZE} from '@constants/post_draft';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {TITLE_HEIGHT} from '@screens/bottom_sheet/content';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
import {fileMaxWarning} from '@utils/file';
|
||||
import PickerUtil from '@utils/file/file_picker';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
import CameraType from './camera_type';
|
||||
@@ -36,6 +40,7 @@ export default function CameraQuickAction({
|
||||
}: QuickActionAttachmentProps) {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
|
||||
const handleButtonPress = useCallback((options: CameraOptions) => {
|
||||
const picker = new PickerUtil(intl,
|
||||
@@ -64,14 +69,15 @@ export default function CameraQuickAction({
|
||||
return;
|
||||
}
|
||||
|
||||
const snap = bottomSheetSnapPoint(2, ITEM_HEIGHT, bottom);
|
||||
bottomSheet({
|
||||
title: intl.formatMessage({id: 'camera_type.title', defaultMessage: 'Choose an action'}),
|
||||
title: intl.formatMessage({id: 'mobile.camera_type.title', defaultMessage: 'Camera options'}),
|
||||
renderContent,
|
||||
snapPoints: [200, 10],
|
||||
snapPoints: [TITLE_HEIGHT + snap, 10],
|
||||
theme,
|
||||
closeButtonId: 'camera-close-id',
|
||||
});
|
||||
}, [intl, theme, renderContent, maxFilesReached, maxFileCount]);
|
||||
}, [intl, theme, renderContent, maxFilesReached, maxFileCount, bottom]);
|
||||
|
||||
const actionTestID = disabled ? `${testID}.disabled` : testID;
|
||||
const color = disabled ? changeOpacity(theme.centerChannelColor, 0.16) : changeOpacity(theme.centerChannelColor, 0.64);
|
||||
|
||||
@@ -30,7 +30,7 @@ const getStyle = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
},
|
||||
title: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 600, 'SemiBold'),
|
||||
...typography('Heading', 600, 'SemiBold'),
|
||||
},
|
||||
betaContainer: {
|
||||
backgroundColor: PostPriorityColors.IMPORTANT,
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleProp, Text, TouchableOpacity, View, ViewStyle} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {TITLE_HEIGHT} from '@screens/bottom_sheet/content';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
@@ -19,6 +22,7 @@ import UsersList from './users_list';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const OVERFLOW_DISPLAY_LIMIT = 99;
|
||||
const USER_ROW_HEIGHT = 40;
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
@@ -88,9 +92,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
marginBottom: 12,
|
||||
},
|
||||
listHeaderText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
...typography('Body', 75, 'SemiBold'),
|
||||
textTransform: 'uppercase',
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Heading', 600, 'SemiBold'),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -99,6 +102,7 @@ const UserAvatarsStack = ({breakAt = 3, channelId, location, style: baseContaine
|
||||
const theme = useTheme();
|
||||
const intl = useIntl();
|
||||
const isTablet = useIsTablet();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
|
||||
const showParticipantsList = useCallback(preventDoubleTap(() => {
|
||||
const renderContent = () => (
|
||||
@@ -119,15 +123,16 @@ const UserAvatarsStack = ({breakAt = 3, channelId, location, style: baseContaine
|
||||
/>
|
||||
</>
|
||||
);
|
||||
const snap = bottomSheetSnapPoint(Math.min(users.length, 5), USER_ROW_HEIGHT, bottom);
|
||||
bottomSheet({
|
||||
closeButtonId: 'close-set-user-status',
|
||||
renderContent,
|
||||
initialSnapIndex: 1,
|
||||
snapPoints: ['90%', '50%', 10],
|
||||
snapPoints: ['90%', TITLE_HEIGHT + snap, 10],
|
||||
title: intl.formatMessage({id: 'mobile.participants.header', defaultMessage: 'Thread Participants'}),
|
||||
theme,
|
||||
});
|
||||
}), [isTablet, theme, users, channelId, location]);
|
||||
}), [isTablet, theme, users, channelId, location, bottom]);
|
||||
|
||||
const displayUsers = users.slice(0, breakAt);
|
||||
const overflowUsersCount = Math.min(users.length - displayUsers.length, OVERFLOW_DISPLAY_LIMIT);
|
||||
|
||||
@@ -25,6 +25,7 @@ type AtMentionItemProps = {
|
||||
showFullName: boolean;
|
||||
testID?: string;
|
||||
isCustomStatusEnabled: boolean;
|
||||
pictureContainerStyle?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const getName = (user: UserProfile | UserModel | undefined, showFullName: boolean, isCurrentUser: boolean, intl: IntlShape) => {
|
||||
@@ -95,6 +96,7 @@ const UserItem = ({
|
||||
showFullName,
|
||||
testID,
|
||||
isCustomStatusEnabled,
|
||||
pictureContainerStyle,
|
||||
}: AtMentionItemProps) => {
|
||||
const theme = useTheme();
|
||||
const style = getStyleFromTheme(theme);
|
||||
@@ -116,7 +118,7 @@ const UserItem = ({
|
||||
style={[style.row, containerStyle]}
|
||||
testID={userItemTestId}
|
||||
>
|
||||
<View style={style.rowPicture}>
|
||||
<View style={[style.rowPicture, pictureContainerStyle]}>
|
||||
<ProfilePicture
|
||||
author={user}
|
||||
size={24}
|
||||
|
||||
@@ -11,7 +11,7 @@ export const DEFAULT_EMOJIS = [
|
||||
];
|
||||
export const LARGE_ICON_SIZE = 32;
|
||||
export const LARGE_CONTAINER_SIZE = 48;
|
||||
export const REACTION_PICKER_HEIGHT = 72;
|
||||
export const REACTION_PICKER_HEIGHT = 48;
|
||||
export const SMALL_CONTAINER_SIZE = 44;
|
||||
export const SMALL_ICON_BREAKPOINT = 410;
|
||||
export const SMALL_ICON_SIZE = 28;
|
||||
|
||||
@@ -26,8 +26,8 @@ const styles = StyleSheet.create({
|
||||
icon_container: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
marginTop: 2,
|
||||
marginRight: 8,
|
||||
top: -1,
|
||||
marginRight: 4,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -45,9 +45,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
right: 16,
|
||||
right: 20,
|
||||
borderTopWidth: 1,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
marginBottom: 20,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -83,7 +84,7 @@ const BottomSheetContent = ({buttonText, buttonIcon, children, disableButton, on
|
||||
</>
|
||||
{showButton && (
|
||||
<>
|
||||
<View style={[styles.separator, {width: separatorWidth, marginBottom: (isTablet ? TITLE_SEPARATOR_MARGIN_TABLET : TITLE_SEPARATOR_MARGIN)}]}/>
|
||||
<View style={[styles.separator, {width: separatorWidth}]}/>
|
||||
<Button
|
||||
disabled={disableButton}
|
||||
onPress={onPress}
|
||||
|
||||
@@ -28,6 +28,7 @@ type SlideUpPanelProps = {
|
||||
}
|
||||
|
||||
export const PADDING_TOP_MOBILE = 20;
|
||||
export const PADDING_TOP_TABLET = 8;
|
||||
|
||||
const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderContent, snapPoints = ['90%', '50%', 50], testID}: SlideUpPanelProps) => {
|
||||
const sheetRef = useRef<RNBottomSheet>(null);
|
||||
@@ -100,7 +101,7 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo
|
||||
|
||||
const backdropStyle = useAnimatedStyle(() => ({
|
||||
opacity: withTiming(backdropOpacity.value, {duration: 250, easing: Easing.inOut(Easing.linear)}),
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||
}));
|
||||
|
||||
const renderBackdrop = () => {
|
||||
@@ -126,9 +127,11 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.16),
|
||||
borderWidth: isTablet ? 0 : 1,
|
||||
opacity: 1,
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: isTablet ? 0 : PADDING_TOP_MOBILE,
|
||||
paddingTop: isTablet ? PADDING_TOP_TABLET : PADDING_TOP_MOBILE,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
alignSelf: 'center',
|
||||
@@ -154,7 +157,7 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo
|
||||
<RNBottomSheet
|
||||
ref={sheetRef}
|
||||
snapPoints={snapPoints}
|
||||
borderRadius={10}
|
||||
borderRadius={12}
|
||||
initialSnap={snapPoints.length - 1}
|
||||
renderContent={renderContainerContent}
|
||||
onCloseEnd={handleCloseEnd}
|
||||
|
||||
@@ -56,7 +56,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
const CustomStatusInput = ({emoji, isStatusSet, onChangeText, onClearHandle, onOpenEmojiPicker, text, theme}: Props) => {
|
||||
const style = getStyleSheet(theme);
|
||||
const intl = useIntl();
|
||||
const placeholder = intl.formatMessage({id: 'custom_status.set_status', defaultMessage: 'Set a Status'});
|
||||
const placeholder = intl.formatMessage({id: 'custom_status.set_status', defaultMessage: 'Set a custom status'});
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -332,7 +332,7 @@ class CustomStatusModal extends NavigationComponent<Props, State> {
|
||||
action={intl.formatMessage({id: 'mobile.custom_status.modal_confirm', defaultMessage: 'Done'})}
|
||||
onPress={this.handleSetStatus}
|
||||
testID='custom_status'
|
||||
title={intl.formatMessage({id: 'mobile.routes.custom_status', defaultMessage: 'Set a Status'})}
|
||||
title={intl.formatMessage({id: 'mobile.routes.custom_status', defaultMessage: 'Set a custom status'})}
|
||||
/>
|
||||
}
|
||||
<SafeAreaView
|
||||
|
||||
@@ -8,8 +8,11 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {Client} from '@client/rest';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import PanelItem from '@screens/edit_profile/components/panel_item';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
@@ -17,11 +20,11 @@ import PickerUtil from '@utils/file/file_picker';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const hitSlop = {top: 100, bottom: 20, right: 20, left: 100};
|
||||
const ACTION_HEIGHT = 55;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
@@ -38,6 +41,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
right: 0,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
title: {
|
||||
...typography('Heading', 600, 'SemiBold'),
|
||||
color: theme.centerChannelColor,
|
||||
marginBottom: 8,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -72,16 +80,24 @@ const ProfileImagePicker = ({
|
||||
}: ImagePickerProps) => {
|
||||
const theme = useTheme();
|
||||
const intl = useIntl();
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const serverUrl = useServerUrl();
|
||||
const pictureUtils = useMemo(() => new PickerUtil(intl, uploadFiles), [uploadFiles, intl]);
|
||||
const canRemovePicture = hasPictureUrl(user, serverUrl);
|
||||
const styles = getStyleSheet(theme);
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const showFileAttachmentOptions = useCallback(preventDoubleTap(() => {
|
||||
const renderContent = () => {
|
||||
return (
|
||||
<>
|
||||
{!isTablet &&
|
||||
<FormattedText
|
||||
id='user.edit_profile.profile_photo.change_photo'
|
||||
defaultMessage='Change profile photo'
|
||||
style={styles.title}
|
||||
/>
|
||||
}
|
||||
<PanelItem
|
||||
pickerAction='takePhoto'
|
||||
pictureUtils={pictureUtils}
|
||||
@@ -106,16 +122,16 @@ const ProfileImagePicker = ({
|
||||
};
|
||||
|
||||
const snapPointsCount = canRemovePicture ? 5 : 4;
|
||||
const snapPoint = bottomSheetSnapPoint(snapPointsCount, ACTION_HEIGHT, insets.bottom);
|
||||
const snapPoint = bottomSheetSnapPoint(snapPointsCount, ITEM_HEIGHT, bottom);
|
||||
|
||||
return bottomSheet({
|
||||
closeButtonId: 'close-edit-profile',
|
||||
renderContent,
|
||||
snapPoints: [snapPoint, 10],
|
||||
title: '',
|
||||
title: 'Change profile photo',
|
||||
theme,
|
||||
});
|
||||
}), [canRemovePicture, onRemoveProfileImage, insets, pictureUtils, theme]);
|
||||
}), [canRemovePicture, onRemoveProfileImage, bottom, pictureUtils, theme]);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
|
||||
@@ -31,7 +31,7 @@ const CustomStatusText = ({isStatusSet, customStatus, testID}: CustomStatusTextP
|
||||
text = (
|
||||
<FormattedText
|
||||
id='mobile.routes.custom_status'
|
||||
defaultMessage='Set a Status'
|
||||
defaultMessage='Set a custom status'
|
||||
style={styles.text}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -78,7 +78,7 @@ const CustomStatus = ({isTablet, currentUser}: CustomStatusProps) => {
|
||||
if (isTablet) {
|
||||
DeviceEventEmitter.emit(Events.ACCOUNT_SELECT_TABLET_VIEW, Screens.CUSTOM_STATUS);
|
||||
} else {
|
||||
showModal(Screens.CUSTOM_STATUS, intl.formatMessage({id: 'mobile.routes.custom_status', defaultMessage: 'Set a Status'}));
|
||||
showModal(Screens.CUSTOM_STATUS, intl.formatMessage({id: 'mobile.routes.custom_status', defaultMessage: 'Set a custom status'}));
|
||||
}
|
||||
setShowRetryMessage(false);
|
||||
}), [isTablet]);
|
||||
|
||||
@@ -7,12 +7,15 @@ import {TouchableOpacity, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {setStatus} from '@actions/remote/user';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import StatusLabel from '@components/status_label';
|
||||
import UserStatusIndicator from '@components/user_status';
|
||||
import General from '@constants/general';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {TITLE_HEIGHT} from '@screens/bottom_sheet/content';
|
||||
import {bottomSheet, dismissBottomSheet, dismissModal} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
@@ -37,6 +40,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
spacer: {
|
||||
marginLeft: 16,
|
||||
},
|
||||
listHeader: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
listHeaderText: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Heading', 600, 'SemiBold'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -47,22 +57,33 @@ type Props = {
|
||||
};
|
||||
const UserStatus = ({currentUser}: Props) => {
|
||||
const intl = useIntl();
|
||||
const insets = useSafeAreaInsets();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const serverUrl = useServerUrl();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const handleSetStatus = useCallback(preventDoubleTap(() => {
|
||||
const renderContent = () => {
|
||||
return (
|
||||
<>
|
||||
{!isTablet && (
|
||||
<View style={styles.listHeader}>
|
||||
<FormattedText
|
||||
id='user_status.title'
|
||||
defaultMessage={'Status'}
|
||||
style={styles.listHeaderText}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<SlideUpPanelItem
|
||||
icon='check-circle'
|
||||
iconStyles={{color: theme.onlineIndicator}}
|
||||
onPress={() => setUserStatus(ONLINE)}
|
||||
testID='user_status.online.option'
|
||||
text={intl.formatMessage({
|
||||
id: 'mobile.set_status.online',
|
||||
id: 'user_status.online',
|
||||
defaultMessage: 'Online',
|
||||
})}
|
||||
textStyles={styles.label}
|
||||
@@ -73,7 +94,7 @@ const UserStatus = ({currentUser}: Props) => {
|
||||
onPress={() => setUserStatus(AWAY)}
|
||||
testID='user_status.away.option'
|
||||
text={intl.formatMessage({
|
||||
id: 'mobile.set_status.away',
|
||||
id: 'user_status.away',
|
||||
defaultMessage: 'Away',
|
||||
})}
|
||||
textStyles={styles.label}
|
||||
@@ -84,7 +105,7 @@ const UserStatus = ({currentUser}: Props) => {
|
||||
onPress={() => setUserStatus(DND)}
|
||||
testID='user_status.dnd.option'
|
||||
text={intl.formatMessage({
|
||||
id: 'mobile.set_status.dnd',
|
||||
id: 'user_status.dnd',
|
||||
defaultMessage: 'Do Not Disturb',
|
||||
})}
|
||||
textStyles={styles.label}
|
||||
@@ -95,7 +116,7 @@ const UserStatus = ({currentUser}: Props) => {
|
||||
onPress={() => setUserStatus(OFFLINE)}
|
||||
testID='user_status.offline.option'
|
||||
text={intl.formatMessage({
|
||||
id: 'mobile.set_status.offline',
|
||||
id: 'user_status.offline',
|
||||
defaultMessage: 'Offline',
|
||||
})}
|
||||
textStyles={styles.label}
|
||||
@@ -104,15 +125,15 @@ const UserStatus = ({currentUser}: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const snapPoint = bottomSheetSnapPoint(4, ITEM_HEIGHT, insets.bottom);
|
||||
const snapPoint = bottomSheetSnapPoint(4, ITEM_HEIGHT, bottom);
|
||||
bottomSheet({
|
||||
closeButtonId: 'close-set-user-status',
|
||||
renderContent,
|
||||
snapPoints: [snapPoint, 10],
|
||||
title: intl.formatMessage({id: 'account.user_status.title', defaultMessage: 'User Presence'}),
|
||||
snapPoints: [(snapPoint + TITLE_HEIGHT), 10],
|
||||
title: intl.formatMessage({id: 'user_status.title', defaultMessage: 'Status'}),
|
||||
theme,
|
||||
});
|
||||
}), [theme, insets]);
|
||||
}), [theme, bottom]);
|
||||
|
||||
const updateStatus = useCallback((status: string) => {
|
||||
const userStatus = {
|
||||
|
||||
@@ -61,7 +61,7 @@ const ServerList = ({servers}: Props) => {
|
||||
testID='server_list'
|
||||
title={intl.formatMessage({id: 'your.servers', defaultMessage: 'Your servers'})}
|
||||
>
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.container, {marginTop: isTablet ? 12 : 0}]}>
|
||||
<FlatList
|
||||
data={servers}
|
||||
renderItem={renderServer}
|
||||
|
||||
@@ -59,6 +59,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
badge: {
|
||||
left: 18,
|
||||
top: -5,
|
||||
borderColor: theme.centerChannelBg,
|
||||
},
|
||||
button: {
|
||||
borderRadius: 8,
|
||||
|
||||
@@ -18,7 +18,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
height: 1,
|
||||
},
|
||||
};
|
||||
@@ -110,7 +110,6 @@ const Filter = ({initialFilter, setFilter, title}: FilterProps) => {
|
||||
showTitle={!isTablet}
|
||||
testID='search.filters'
|
||||
title={title}
|
||||
titleSeparator={true}
|
||||
>
|
||||
<View>
|
||||
<FlatList
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// 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 React, {useCallback, useMemo} from 'react';
|
||||
import {Pressable, PressableStateCallbackType, View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {LARGE_ICON_SIZE} from '@constants/reaction_picker';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
@@ -17,9 +17,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
justifyContent: 'center',
|
||||
borderRadius: 4,
|
||||
},
|
||||
highlight: {
|
||||
backgroundColor: changeOpacity(theme.buttonBg, 0.08),
|
||||
borderRadius: 4,
|
||||
},
|
||||
icon: {
|
||||
...typography('Body', 1000),
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: LARGE_ICON_SIZE,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -32,19 +36,31 @@ type PickReactionProps = {
|
||||
const PickReaction = ({openEmojiPicker, width, height}: PickReactionProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const highlightedStyle = useCallback(({pressed}: PressableStateCallbackType) => (pressed ? styles.highlight : undefined), [styles.highlight]);
|
||||
const pickReactionStyle = useMemo(() => [
|
||||
styles.container,
|
||||
{
|
||||
width,
|
||||
height,
|
||||
},
|
||||
], [width, height]);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[styles.container, {
|
||||
width, height,
|
||||
}]}
|
||||
testID='post_options.reaction_bar.pick_reaction.button'
|
||||
<Pressable
|
||||
onPress={openEmojiPicker}
|
||||
style={highlightedStyle}
|
||||
>
|
||||
<CompassIcon
|
||||
onPress={openEmojiPicker}
|
||||
name='emoticon-plus-outline'
|
||||
style={styles.icon}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={pickReactionStyle}
|
||||
testID='post_options.reaction_bar.pick_reaction.button'
|
||||
>
|
||||
<CompassIcon
|
||||
name='emoticon-plus-outline'
|
||||
style={styles.icon}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
highlight: {
|
||||
backgroundColor: changeOpacity(theme.linkColor, 0.1),
|
||||
backgroundColor: changeOpacity(theme.buttonBg, 0.08),
|
||||
borderRadius: 4,
|
||||
},
|
||||
reactionContainer: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.04),
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from '@constants/reaction_picker';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {dismissBottomSheet, showModal} from '@screens/navigation';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
@@ -37,6 +38,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
alignItems: 'center',
|
||||
height: REACTION_PICKER_HEIGHT,
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 8,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -49,6 +51,8 @@ const ReactionBar = ({recentEmojis = [], postId}: QuickReactionProps) => {
|
||||
const isSmallDevice = width < SMALL_ICON_BREAKPOINT;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const handleEmojiPress = useCallback(async (emoji: string) => {
|
||||
await dismissBottomSheet(Screens.POST_OPTIONS);
|
||||
addReaction(serverUrl, postId, emoji);
|
||||
@@ -74,7 +78,7 @@ const ReactionBar = ({recentEmojis = [], postId}: QuickReactionProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.container, {marginTop: isTablet ? 12 : 0}]}>
|
||||
{
|
||||
recentEmojis.map((emoji) => {
|
||||
return (
|
||||
|
||||
@@ -5,6 +5,8 @@ import React, {useCallback, useEffect, useRef} from 'react';
|
||||
import {ListRenderItemInfo, StyleSheet} from 'react-native';
|
||||
import {FlatList} from 'react-native-gesture-handler';
|
||||
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
|
||||
import Item from './item';
|
||||
|
||||
import type ReactionModel from '@typings/database/models/servers/reaction';
|
||||
@@ -29,6 +31,7 @@ const style = StyleSheet.create({
|
||||
});
|
||||
|
||||
const EmojiBar = ({emojiSelected, reactionsByName, setIndex, sortedReactions}: Props) => {
|
||||
const isTablet = useIsTablet();
|
||||
const listRef = useRef<FlatList<string>>(null);
|
||||
|
||||
const scrollToIndex = (index: number, animated = false) => {
|
||||
@@ -81,7 +84,7 @@ const EmojiBar = ({emojiSelected, reactionsByName, setIndex, sortedReactions}: P
|
||||
horizontal={true}
|
||||
ref={listRef}
|
||||
renderItem={renderItem}
|
||||
style={style.container}
|
||||
style={[style.container, {marginTop: isTablet ? 12 : 0}]}
|
||||
onScrollToIndexFailed={onScrollToIndexFailed}
|
||||
overScrollMode='never'
|
||||
/>
|
||||
|
||||
@@ -21,6 +21,11 @@ type Props = {
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
marginBottom: 8,
|
||||
paddingLeft: 0,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
picture: {
|
||||
marginLeft: 0,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -44,6 +49,7 @@ const Reactor = ({channelId, location, user}: Props) => {
|
||||
<TouchableOpacity onPress={openUserProfile}>
|
||||
<UserItem
|
||||
containerStyle={style.container}
|
||||
pictureContainerStyle={style.picture}
|
||||
user={user}
|
||||
testID='reactions.reactor_item'
|
||||
/>
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import React from 'react';
|
||||
import React, {useMemo} from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {CopyPermalinkOption, FollowThreadOption, ReplyOption, SaveOption} from '@components/common_post_options';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
@@ -14,9 +15,12 @@ import {useIsTablet} from '@hooks/device';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import BottomSheet from '@screens/bottom_sheet';
|
||||
import {dismissModal} from '@screens/navigation';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import {TITLE_HEIGHT} from '../bottom_sheet/content';
|
||||
|
||||
import MarkAsUnreadOption from './options/mark_as_unread_option';
|
||||
import OpenInChannelOption from './options/open_in_channel_option';
|
||||
|
||||
@@ -35,12 +39,11 @@ type ThreadOptionsProps = {
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
listHeader: {
|
||||
marginBottom: 12,
|
||||
marginBottom: 8,
|
||||
},
|
||||
listHeaderText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
textTransform: 'uppercase',
|
||||
...typography('Body', 75, 'SemiBold'),
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Heading', 600, 'SemiBold'),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -56,7 +59,7 @@ const ThreadOptions = ({
|
||||
}: ThreadOptionsProps) => {
|
||||
const theme = useTheme();
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const close = () => {
|
||||
@@ -103,6 +106,8 @@ const ThreadOptions = ({
|
||||
);
|
||||
}
|
||||
|
||||
const snapPoint = useMemo(() => TITLE_HEIGHT + bottomSheetSnapPoint(options.length, ITEM_HEIGHT, bottom), [bottom, options.length]);
|
||||
|
||||
const renderContent = () => (
|
||||
<>
|
||||
{!isTablet && (
|
||||
@@ -124,7 +129,7 @@ const ThreadOptions = ({
|
||||
closeButtonId={THREAD_OPTIONS_BUTTON}
|
||||
componentId={Screens.THREAD_OPTIONS}
|
||||
initialSnapIndex={0}
|
||||
snapPoints={[((options.length + 2) * ITEM_HEIGHT), 10]}
|
||||
snapPoints={[snapPoint, 10]}
|
||||
testID='thread_options'
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"account.logout": "Log out",
|
||||
"account.logout_from": "Log out of {serverName}",
|
||||
"account.settings": "Settings",
|
||||
"account.user_status.title": "User Presence",
|
||||
"account.your_profile": "Your Profile",
|
||||
"alert.channel_deleted.description": "The channel {displayName} has been archived.",
|
||||
"alert.channel_deleted.title": "Archived channel",
|
||||
@@ -94,7 +93,6 @@
|
||||
"browse_channels.showSharedChannels": "Show: Shared Channels",
|
||||
"browse_channels.title": "Browse channels",
|
||||
"camera_type.photo.option": "Capture Photo",
|
||||
"camera_type.title": "Choose an action",
|
||||
"camera_type.video.option": "Record Video",
|
||||
"center_panel.archived.closeChannel": "Close Channel",
|
||||
"channel_header.directchannel.you": "{displayName} (you)",
|
||||
@@ -203,6 +201,7 @@
|
||||
"combined_system_message.removed_from_team.two": "{firstUser} and {secondUser} were **removed from the team**.",
|
||||
"combined_system_message.you": "You",
|
||||
"connection_banner.connected": "Connection restored",
|
||||
"connection_banner.connecting": "Connecting...",
|
||||
"connection_banner.not_connected": "No internet connection",
|
||||
"connection_banner.not_reachable": "The server is not reachable",
|
||||
"create_direct_message.title": "Create Direct Message",
|
||||
@@ -222,7 +221,7 @@
|
||||
"custom_status.expiry.at": "at",
|
||||
"custom_status.expiry.until": "Until",
|
||||
"custom_status.failure_message": "Failed to update status. Try again",
|
||||
"custom_status.set_status": "Set a Status",
|
||||
"custom_status.set_status": "Set a custom status",
|
||||
"custom_status.suggestions.in_a_meeting": "In a meeting",
|
||||
"custom_status.suggestions.on_a_vacation": "On a vacation",
|
||||
"custom_status.suggestions.out_for_lunch": "Out for lunch",
|
||||
@@ -426,6 +425,7 @@
|
||||
"mobile.calls_you": "(you)",
|
||||
"mobile.camera_photo_permission_denied_description": "Take photos and upload them to your server or save them to your device. Open Settings to grant {applicationName} read and write access to your camera.",
|
||||
"mobile.camera_photo_permission_denied_title": "{applicationName} would like to access your camera",
|
||||
"mobile.camera_type.title": "Camera options",
|
||||
"mobile.channel_info.alertNo": "No",
|
||||
"mobile.channel_info.alertYes": "Yes",
|
||||
"mobile.channel_list.recent": "Recent",
|
||||
@@ -575,7 +575,7 @@
|
||||
"mobile.reset_status.title_ooo": "Disable \"Out Of Office\"?",
|
||||
"mobile.routes.code": "{language} Code",
|
||||
"mobile.routes.code.noLanguage": "Code",
|
||||
"mobile.routes.custom_status": "Set a Status",
|
||||
"mobile.routes.custom_status": "Set a custom status",
|
||||
"mobile.routes.table": "Table",
|
||||
"mobile.routes.user_profile": "Profile",
|
||||
"mobile.screen.settings": "Settings",
|
||||
@@ -605,10 +605,7 @@
|
||||
"mobile.server_url.invalid_format": "URL must start with http:// or https://",
|
||||
"mobile.session_expired": "Please log in to continue receiving notifications. Sessions for {siteName} are configured to expire every {daysCount, number} {daysCount, plural, one {day} other {days}}.",
|
||||
"mobile.session_expired.title": "Session Expired",
|
||||
"mobile.set_status.away": "Away",
|
||||
"mobile.set_status.dnd": "Do Not Disturb",
|
||||
"mobile.set_status.offline": "Offline",
|
||||
"mobile.set_status.online": "Online",
|
||||
"mobile.storage_permission_denied_description": "Upload files to your server. Open Settings to grant {applicationName} Read and Write access to files on this device.",
|
||||
"mobile.storage_permission_denied_title": "{applicationName} would like to access your files",
|
||||
"mobile.suggestion.members": "Members",
|
||||
@@ -917,8 +914,14 @@
|
||||
"unsupported_server.message": "Your server, {serverDisplayName}, is running an unsupported server version. You may experience compatibility issues that cause crashes or severe bugs breaking core functionality of the app. Please contact your System Administrator to upgrade your Mattermost server.",
|
||||
"unsupported_server.title": "Unsupported server version",
|
||||
"user_profile.custom_status": "Custom Status",
|
||||
"user_status.away": "Away",
|
||||
"user_status.dnd": "Do Not Disturb",
|
||||
"user_status.offline": "Offline",
|
||||
"user_status.online": "Online",
|
||||
"user_status.title": "Status",
|
||||
"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.edit_profile.profile_photo.change_photo": "Change profile photo",
|
||||
"user.settings.general.email": "Email",
|
||||
"user.settings.general.field_handled_externally": "Some fields below are handled through your login provider. If you want to change them, you’ll need to do so through your login provider.",
|
||||
"user.settings.general.firstName": "First Name",
|
||||
@@ -929,6 +932,7 @@
|
||||
"user.settings.notifications.email_threads.description": "Notify me about all replies to threads I'm following",
|
||||
"user.tutorial.long_press": "Long-press on an item to view a user's profile",
|
||||
"video.download": "Download video",
|
||||
"video.download_description": "This video must be downloaded to play it.",
|
||||
"video.failed_description": "An error occurred while trying to play the video.",
|
||||
"your.servers": "Your servers"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user