mattermost-mobile/app/screens/attachment_options/attachment_options.tsx
Rajat Dabade e39b27be7e
New keyboard library Setup (#9278)
* Initial setup for new keyboard

* fix the offset calculation onMove by adding isKeyboardFullyOpened

* Done with the keyboard handling implementation

* Handled keyboard focus and blured state using context

* Added default height for input container

* Android support

* Tablet state handling

* Fix for refreshing offset in list

* Created a default context for mention post list

* Fix linter errors

* Fix tests

* Minor

* Fix the height issue for tablet view

* Review comments

* Dependency fix

* Reveiw comment

* keyboard animation only enabled with screen on top navigation

* added tests

* Added extra keyboard component for emoji picker (#9328)

* Added extra keyboard component

* handled swipe geature for extra keyboard

* scroll to bottom visible on emoji picker

* Check for stale event for keyboard geature area

* fix keyboard stale event for mid-gesture change swipe direction

* Closing emoji picker when message priority is opened

* changing to thread view closes emoji picker

* Close emoij picker and keyboard when attachment icons are clicked

* handle android emoji picker behaviour

* Remove emoji picker code

* fix tests

* Address reviev comments

* Test fixes

* usePreventDoubleTab for race condition and corrected comment on isInputAccessoryViewMode flag

* File attachments option in bottom sheet quick action (#9331)

* Added extra keyboard component

* handled swipe geature for extra keyboard

* File attachments option in bottom sheet quick action

* Updated tests

* i18n

* fix test

* Added tests

* Review comment

* fix tests

* Integrated Emoji picker to Extra keyboard component.  (#9339)

* Added extra keyboard component

* handled swipe geature for extra keyboard

* scroll to bottom visible on emoji picker

* Fix the post input container height change issue

* Added emoji picker with search functionality

* keyboard height not recorded fallback to default height, set search to false closing emoji picker

* fix search funcitonality in android

* scroll to end bottom dismissed with pressed

* Fixed the scroll issue for android

* fix the flickering post input issue

* Wired up the emoji picker

* Added keyboard and spaceback in emoji picker

* intl extract

* initial cursor position and simple calculation review comment

* separated grapheme to utils file

* Review comments

* nitpick

* Fix the bottom safe area and navigation stack restore fix

* Fix test

* disabled emoji picker in edit post screen

* change the name of the variable to name it clarier

* fix the tab gap issue in andriod

* disabled the emoji skin tone change on andriod

* fix the input container jump issue

* fix the failing test

* UX review

* added white space after the attachment icon

* When @ or / is press open keyboard on andriod also fix the scroll issue when keyboard is open

* back bottom closes emoji picker and opening keyboard jump fix

* reverted code

* fixed the flickering issue of input and scroll position fix

* Fix the gap issue in thread

* fix the warning reaminated issue when opening a channel

* Fix the extra space issue between input and emoij picker

* Minor fix for extra space between input and emoji picker

* Fix thread view bottom safe area and gap between keyboard and searched emojis

* Fix the keyboard issue in tablet

* Fix the warning issue react-native-keyboard-controller

* Fix tests

* Fix the tablet search hight issue

* Replaced the ExtraKeyboardProvider with KeyboardProvider from rnkc for permalink

* Bottom sheet jump issue resolved

* Search do not close after selecting emoji in search list

* Added the bottom inset height in search emoji for android

* fix buid issue

* Fix the cancel state to emoji picker

* use portals for autocomplete

* fix permalink extra space in post list

* Portal only for android

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Rahim Rahman <rahim.rahman@mattermost.com>
Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com>
Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2026-01-13 22:38:35 +05:30

188 lines
6 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useMemo} from 'react';
import {useIntl} from 'react-intl';
import {Alert, View} from 'react-native';
import {type CameraOptions} from 'react-native-image-picker';
import FormattedText from '@components/formatted_text';
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
import {Screens} from '@constants';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
import BottomSheet from '@screens/bottom_sheet';
import {dismissBottomSheet} from '@screens/navigation';
import {fileMaxWarning, uploadDisabledWarning} from '@utils/file';
import PickerUtil from '@utils/file/file_picker';
import {bottomSheetSnapPoint} from '@utils/helpers';
import {makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
import type {AvailableScreens} from '@typings/screens/navigation';
const TITLE_HEIGHT = 54;
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
title: {
color: theme.centerChannelColor,
...typography('Heading', 600, 'SemiBold'),
marginBottom: 8,
},
}));
type Props = {
componentId: AvailableScreens;
closeButtonId?: string;
onUploadFiles: (files: ExtractedFileInfo[]) => void;
maxFileCount?: number;
fileCount?: number;
maxFilesReached?: boolean;
canUploadFiles?: boolean;
testID?: string;
}
const AttachmentOptions: React.FC<Props> = ({
componentId,
closeButtonId = 'attachment-close-id',
onUploadFiles,
maxFileCount,
fileCount = 0,
maxFilesReached = false,
canUploadFiles = true,
testID,
}) => {
const theme = useTheme();
const isTablet = useIsTablet();
const intl = useIntl();
const styles = getStyleSheet(theme);
const picker = useMemo(() => new PickerUtil(intl, onUploadFiles), [intl, onUploadFiles]);
const checkCanUpload = () => {
if (!canUploadFiles) {
Alert.alert(
intl.formatMessage({
id: 'mobile.link.error.title',
defaultMessage: 'Error',
}),
uploadDisabledWarning(intl),
);
return false;
}
return true;
};
const checkMaxFiles = () => {
if (maxFilesReached && maxFileCount) {
Alert.alert(
intl.formatMessage({
id: 'mobile.link.error.title',
defaultMessage: 'Error',
}),
fileMaxWarning(intl, maxFileCount),
);
return true;
}
return false;
};
const onChooseFromPhotoLibrary = async () => {
await dismissBottomSheet(Screens.ATTACHMENT_OPTIONS);
if (!checkCanUpload() || checkMaxFiles()) {
return;
}
const selectionLimit = maxFileCount ? maxFileCount - fileCount : undefined;
picker.attachFileFromPhotoGallery(selectionLimit);
};
const onTakePhoto = async () => {
await dismissBottomSheet(Screens.ATTACHMENT_OPTIONS);
if (!checkCanUpload() || checkMaxFiles()) {
return;
}
const options: CameraOptions = {
quality: 0.8,
mediaType: 'photo',
saveToPhotos: true,
};
picker.attachFileFromCamera(options);
};
const onTakeVideo = async () => {
await dismissBottomSheet(Screens.ATTACHMENT_OPTIONS);
if (!checkCanUpload() || checkMaxFiles()) {
return;
}
const options: CameraOptions = {
quality: 0.8,
videoQuality: 'high',
mediaType: 'video',
saveToPhotos: true,
};
picker.attachFileFromCamera(options);
};
const onAttachFile = async () => {
await dismissBottomSheet(Screens.ATTACHMENT_OPTIONS);
if (!checkCanUpload() || checkMaxFiles()) {
return;
}
picker.attachFileFromFiles(undefined, true);
};
const renderContent = () => {
return (
<View>
{!isTablet &&
<FormattedText
id='mobile.file_attachment.title'
defaultMessage='Files and media'
style={styles.title}
/>
}
<SlideUpPanelItem
leftIcon='image-outline'
onPress={onChooseFromPhotoLibrary}
testID='file_attachment.photo_library'
text={intl.formatMessage({id: 'mobile.file_upload.library', defaultMessage: 'Choose from photo library'})}
/>
<SlideUpPanelItem
leftIcon='camera-outline'
onPress={onTakePhoto}
testID='file_attachment.take_photo'
text={intl.formatMessage({id: 'mobile.file_upload.camera_photo', defaultMessage: 'Take a photo'})}
/>
<SlideUpPanelItem
leftIcon='video-outline'
onPress={onTakeVideo}
testID='file_attachment.take_video'
text={intl.formatMessage({id: 'mobile.file_upload.camera_video', defaultMessage: 'Take a video'})}
/>
<SlideUpPanelItem
leftIcon='paperclip'
onPress={onAttachFile}
testID='file_attachment.attach_file'
text={intl.formatMessage({id: 'mobile.file_upload.browse', defaultMessage: 'Attach a file'})}
/>
</View>
);
};
const snapPoints = useMemo(() => {
const componentHeight = TITLE_HEIGHT + bottomSheetSnapPoint(4, ITEM_HEIGHT);
return [1, componentHeight];
}, []);
return (
<BottomSheet
componentId={componentId}
renderContent={renderContent}
closeButtonId={closeButtonId}
snapPoints={snapPoints}
testID={testID}
/>
);
};
export default AttachmentOptions;