[IMPROVE] Add emoji picker to iOS (#4366)

* [IMPROVE] Add emoji picker to iOS

* clean left and right buttons

* fix the redux in emojipicker

* fix behavior when emoji keyboard is openning

* added isIOS

* fix show reactions when emoji is open

* minor tweak

* add provider

* fix baseurl

* minor tweak

* create closeEmojiAndAction and added to record

* fix actionsheet for omnichannel

* fix action sheet

* fix close emoji when navigate to other screen

* added iactionsheetprovider to roomview

* clean variables

* fix theme

* close the emojikeyboard when click on message

* apoint package.json to new pr

* fix branch

* fix package.json
This commit is contained in:
Reinaldo Neto 2022-08-08 15:38:01 -03:00 committed by GitHub
parent a4f171a12d
commit e38aedcbff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 129 additions and 149 deletions

View File

@ -65,6 +65,7 @@ class EmojiCategory extends React.Component<IEmojiCategory> {
initialNumToRender={45}
removeClippedSubviews
{...scrollPersistTaps}
keyboardDismissMode={'none'}
/>
);
}

View File

@ -17,7 +17,7 @@ import protectedFunction from '../../lib/methods/helpers/protectedFunction';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
import log from '../../lib/methods/helpers/log';
import { themes } from '../../lib/constants';
import { TSupportedThemes, withTheme } from '../../theme';
import { TSupportedThemes } from '../../theme';
import { IEmoji, TGetCustomEmoji, IApplicationState, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions';
interface IEmojiPickerProps {
@ -198,7 +198,8 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
}
const mapStateToProps = (state: IApplicationState) => ({
customEmojis: state.customEmojis
customEmojis: state.customEmojis,
baseUrl: state.share.server.server || state.server.server
});
export default connect(mapStateToProps)(withTheme(EmojiPicker));
export default connect(mapStateToProps)(EmojiPicker);

View File

@ -1,39 +1,28 @@
import React from 'react';
import { View } from 'react-native';
import { KeyboardRegistry } from 'react-native-ui-lib/keyboard';
import { Provider } from 'react-redux';
import { store } from '../../lib/store/auxStore';
import store from '../../lib/store';
import EmojiPicker from '../EmojiPicker';
import styles from './styles';
import { themes } from '../../lib/constants';
import { TSupportedThemes, withTheme } from '../../theme';
import { TSupportedThemes } from '../../theme';
interface IMessageBoxEmojiKeyboard {
theme: TSupportedThemes;
}
export default class EmojiKeyboard extends React.PureComponent<IMessageBoxEmojiKeyboard, any> {
private readonly baseUrl: string;
constructor(props: IMessageBoxEmojiKeyboard) {
super(props);
const state = store.getState();
this.baseUrl = state.share.server.server || state.server.server;
}
onEmojiSelected = (emoji: string) => {
const EmojiKeyboard = ({ theme }: { theme: TSupportedThemes }) => {
const onEmojiSelected = (emoji: string) => {
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
};
render() {
const { theme } = this.props;
return (
<Provider store={store}>
<View
style={[styles.emojiKeyboardContainer, { borderTopColor: themes[theme].borderColor }]}
testID='messagebox-keyboard-emoji'>
<EmojiPicker onEmojiSelected={this.onEmojiSelected} baseUrl={this.baseUrl} theme={theme} />
<EmojiPicker onEmojiSelected={onEmojiSelected} theme={theme} />
</View>
</Provider>
);
}
}
KeyboardRegistry.registerKeyboard('EmojiKeyboard', () => withTheme(EmojiKeyboard));
};
KeyboardRegistry.registerKeyboard('EmojiKeyboard', () => EmojiKeyboard);

View File

@ -1,24 +0,0 @@
import React from 'react';
import { View } from 'react-native';
import { ActionsButton, CancelEditingButton } from './buttons';
import styles from './styles';
interface IMessageBoxLeftButtons {
showMessageBoxActions(): void;
editing: boolean;
editCancel(): void;
isActionsEnabled: boolean;
}
const LeftButtons = React.memo(({ showMessageBoxActions, editing, editCancel, isActionsEnabled }: IMessageBoxLeftButtons) => {
if (editing) {
return <CancelEditingButton onPress={editCancel} />;
}
if (isActionsEnabled) {
return <ActionsButton onPress={showMessageBoxActions} />;
}
return <View style={styles.buttonsWhitespace} />;
});
export default LeftButtons;

View File

@ -17,6 +17,7 @@ interface IMessageBoxRecordAudioProps {
permissionToUpload: boolean;
recordingCallback: Function;
onFinish: Function;
onStart: Function;
}
const RECORDING_EXTENSION = '.m4a';
@ -116,6 +117,9 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
};
startRecordingAudio = async () => {
const { onStart } = this.props;
onStart();
logEvent(events.ROOM_AUDIO_RECORD);
if (!this.isRecorderBusy) {
this.isRecorderBusy = true;

View File

@ -1,17 +0,0 @@
import React from 'react';
import { SendButton } from './buttons';
interface IMessageBoxRightButtons {
showSend: boolean;
submit(): void;
}
const RightButtons = ({ showSend, submit }: IMessageBoxRightButtons) => {
if (showSend) {
return <SendButton onPress={submit} />;
}
return null;
};
export default RightButtons;

View File

@ -1,6 +1,7 @@
import React from 'react';
import { View } from 'react-native';
import { isIOS } from '../../lib/methods/helpers';
import { ActionsButton, SendButton } from './buttons';
import styles from './styles';
@ -18,7 +19,7 @@ const RightButtons = React.memo(({ showSend, submit, showMessageBoxActions, isAc
if (isActionsEnabled) {
return <ActionsButton onPress={showMessageBoxActions} />;
}
return <View style={styles.buttonsWhitespace} />;
return !isIOS ? <View style={styles.buttonsWhitespace} /> : null;
});
export default RightButtons;

View File

@ -4,3 +4,5 @@ export const MENTIONS_TRACKING_TYPE_COMMANDS = '/';
export const MENTIONS_TRACKING_TYPE_ROOMS = '#';
export const MENTIONS_TRACKING_TYPE_CANNED = '!';
export const MENTIONS_COUNT_TO_DISPLAY = 4;
export const TIMEOUT_CLOSE_EMOJI = 300;

View File

@ -19,11 +19,7 @@ import RecordAudio from './RecordAudio';
import I18n from '../../i18n';
import ReplyPreview from './ReplyPreview';
import { themes } from '../../lib/constants';
// @ts-ignore
// eslint-disable-next-line import/extensions,import/no-unresolved
import LeftButtons from './LeftButtons';
// @ts-ignore
// eslint-disable-next-line import/extensions,import/no-unresolved
import RightButtons from './RightButtons';
import { canUploadFile } from '../../lib/methods/helpers/media';
import EventEmiter from '../../lib/methods/helpers/events';
@ -37,12 +33,13 @@ import {
MENTIONS_TRACKING_TYPE_COMMANDS,
MENTIONS_TRACKING_TYPE_EMOJIS,
MENTIONS_TRACKING_TYPE_ROOMS,
MENTIONS_TRACKING_TYPE_USERS
MENTIONS_TRACKING_TYPE_USERS,
TIMEOUT_CLOSE_EMOJI
} from './constants';
import CommandsPreview from './CommandsPreview';
import { getUserSelector } from '../../selectors/login';
import Navigation from '../../lib/navigation/appNavigation';
import { withActionSheet } from '../ActionSheet';
import { TActionSheetOptionsItem, withActionSheet } from '../ActionSheet';
import { sanitizeLikeString } from '../../lib/database/utils';
import { CustomIcon } from '../CustomIcon';
import { forceJpgExtension } from './forceJpgExtension';
@ -58,14 +55,12 @@ import {
} from '../../definitions';
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods';
import { hasPermission, debounce, isAndroid, isTablet } from '../../lib/methods/helpers';
import { hasPermission, debounce, isAndroid, isIOS, isTablet } from '../../lib/methods/helpers';
import { Services } from '../../lib/services';
import { TSupportedThemes } from '../../theme';
import { ChatsStackParamList } from '../../stacks/types';
if (isAndroid) {
require('./EmojiKeyboard');
}
require('./EmojiKeyboard');
const imagePickerConfig = {
cropping: true,
@ -270,6 +265,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}, 500);
});
this.unsubscribeBlur = navigation.addListener('blur', () => {
this.closeEmoji();
this.component?.blur();
});
}
@ -330,6 +326,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (nextProps.theme !== theme) {
return true;
}
if (nextState.showEmojiKeyboard !== showEmojiKeyboard) {
return true;
}
if (!isFocused()) {
return false;
}
@ -342,9 +341,6 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (nextProps.editing !== editing) {
return true;
}
if (nextState.showEmojiKeyboard !== showEmojiKeyboard) {
return true;
}
if (nextState.trackingType !== trackingType) {
return true;
}
@ -809,7 +805,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const { permissionToUpload } = this.state;
const { showActionSheet, goToCannedResponses } = this.props;
const options = [];
const options: TActionSheetOptionsItem[] = [];
if (goToCannedResponses) {
options.push({
title: I18n.t('Canned_Responses'),
@ -847,7 +843,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
icon: 'discussions',
onPress: this.createDiscussion
});
showActionSheet({ options });
this.closeEmojiAndAction(showActionSheet, { options });
};
editCancel = () => {
@ -883,6 +880,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
this.setState({ showEmojiKeyboard: false });
};
closeEmojiAndAction = (action?: Function, params?: any) => {
const { showEmojiKeyboard } = this.state;
this.closeEmoji();
setTimeout(() => action && action(params), showEmojiKeyboard && isIOS ? TIMEOUT_CLOSE_EMOJI : null);
};
submit = async () => {
const { tshow } = this.state;
const { onSubmit, rid: roomId, tmid, showSend, sharing } = this.props;
@ -1089,6 +1093,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
recordingCallback={this.recordingCallback}
onFinish={this.finishAudioMessage}
permissionToUpload={permissionToUpload}
onStart={this.closeEmoji}
/>
);
@ -1112,14 +1117,11 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const textInputAndButtons = !recording ? (
<>
<LeftButtons
theme={theme}
showEmojiKeyboard={showEmojiKeyboard}
editing={editing}
showMessageBoxActions={this.showMessageBoxActions}
editCancel={this.editCancel}
openEmoji={this.openEmoji}
closeEmoji={this.closeEmoji}
isActionsEnabled={isActionsEnabled}
/>
<TextInput
ref={component => (this.component = component)}
@ -1138,7 +1140,6 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
{...isAndroidTablet}
/>
<RightButtons
theme={theme}
showSend={showSend}
submit={this.submit}
showMessageBoxActions={this.showMessageBoxActions}
@ -1187,6 +1188,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
renderContent={this.renderContent}
kbInputRef={this.component}
kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null}
kbInitialProps={{ theme }}
onKeyboardResigned={this.onKeyboardResigned}
onItemSelected={this.onEmojiSelected}
trackInteractive

View File

@ -58,6 +58,7 @@ interface IMessageContainerProps {
jumpToMessage?: (link: string) => void;
onPress?: () => void;
theme: TSupportedThemes;
closeEmojiAndAction?: (action?: Function, params?: any) => void;
}
interface IMessageContainerState {
@ -114,6 +115,14 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
}
}
closeEmojiAndAction = () => {
const { closeEmojiAndAction } = this.props;
if (closeEmojiAndAction) {
closeEmojiAndAction(this.onPress);
}
};
onPress = debounce(
() => {
const { onPress } = this.props;
@ -373,7 +382,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
value={{
user,
baseUrl,
onPress: this.onPress,
onPress: this.closeEmojiAndAction,
onLongPress: this.onLongPress,
reactionInit: this.reactionInit,
onErrorPress: this.onErrorPress,

View File

@ -14,7 +14,6 @@ const margin = isAndroid ? 40 : 20;
const maxSize = 400;
interface IReactionPickerProps {
baseUrl: string;
message?: any;
show: boolean;
isMasterDetail: boolean;
@ -42,7 +41,7 @@ class ReactionPicker extends React.Component<IReactionPickerProps> {
};
render() {
const { width, height, show, baseUrl, reactionClose, isMasterDetail, theme } = this.props;
const { width, height, show, reactionClose, isMasterDetail, theme } = this.props;
let widthStyle = width - margin;
let heightStyle = Math.min(width, height) - margin * 2;
@ -70,7 +69,7 @@ class ReactionPicker extends React.Component<IReactionPickerProps> {
}
]}
testID='reaction-picker'>
<EmojiPicker theme={theme} onEmojiSelected={this.onEmojiSelected} baseUrl={baseUrl} />
<EmojiPicker theme={theme} onEmojiSelected={this.onEmojiSelected} />
</View>
</Modal>
) : null;
@ -78,7 +77,6 @@ class ReactionPicker extends React.Component<IReactionPickerProps> {
}
const mapStateToProps = (state: IApplicationState) => ({
baseUrl: state.server.server,
isMasterDetail: state.app.isMasterDetail
});

View File

@ -13,7 +13,7 @@ import { events, logEvent } from '../../lib/methods/helpers/log';
import { isTeamRoom } from '../../lib/methods/helpers/room';
import { IApplicationState, SubscriptionType, TMessageModel, TSubscriptionModel } from '../../definitions';
import { ChatsStackParamList } from '../../stacks/types';
import { IActionSheetProvider, TActionSheetOptionsItem, withActionSheet } from '../../containers/ActionSheet';
import { TActionSheetOptionsItem } from '../../containers/ActionSheet';
import i18n from '../../i18n';
import { showConfirmationAlert, showErrorAlert } from '../../lib/methods/helpers';
import { onHoldLivechat, returnLivechat } from '../../lib/services/restApi';
@ -21,10 +21,10 @@ import { closeLivechat as closeLivechatService } from '../../lib/methods/helpers
import { Services } from '../../lib/services';
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
interface IRightButtonsProps extends IActionSheetProvider {
interface IRightButtonsProps {
userId?: string;
threadsEnabled: boolean;
rid: string;
rid?: string;
t: string;
tmid?: string;
teamId?: string;
@ -34,7 +34,6 @@ interface IRightButtonsProps extends IActionSheetProvider {
status?: string;
dispatch: Dispatch;
encrypted?: boolean;
transferLivechatGuestPermission: boolean;
navigation: StackNavigationProp<ChatsStackParamList, 'RoomView'>;
omnichannelPermissions: {
canForwardGuest: boolean;
@ -42,6 +41,7 @@ interface IRightButtonsProps extends IActionSheetProvider {
canPlaceLivechatOnHold: boolean;
};
livechatRequestComment: boolean;
showActionSheet: Function;
departmentId?: string;
}
@ -187,6 +187,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
returnLivechat = () => {
const { rid } = this.props;
if (rid) {
showConfirmationAlert({
message: i18n.t('Would_you_like_to_return_the_inquiry'),
confirmationText: i18n.t('Yes'),
@ -198,10 +199,12 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
}
}
});
}
};
placeOnHoldLivechat = () => {
const { navigation, rid } = this.props;
if (rid) {
showConfirmationAlert({
title: i18n.t('Are_you_sure_question_mark'),
message: i18n.t('Would_like_to_place_on_hold'),
@ -215,6 +218,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
}
}
});
}
};
closeLivechat = async () => {
@ -234,6 +238,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
tagsList = await Services.getTagsList();
}
if (rid) {
if (!livechatRequestComment && !departmentInfo?.requestTagBeforeClosingChat) {
const comment = i18n.t('Chat_closed_by_agent');
return closeLivechatService({ rid, isMasterDetail, comment });
@ -247,6 +252,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
} else {
navigation.navigate('CloseLivechatView', { rid, departmentId, departmentInfo, tagsList });
}
}
};
showMoreActions = () => {
@ -267,6 +273,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
title: i18n.t('Forward_Chat'),
icon: 'chat-forward',
onPress: () => {
if (rid) {
if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', {
screen: 'ForwardLivechatView',
@ -276,6 +283,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
navigation.navigate('ForwardLivechatView', { rid });
}
}
}
});
}
@ -379,4 +387,4 @@ const mapStateToProps = (state: IApplicationState) => ({
livechatRequestComment: state.settings.Livechat_request_comment_when_closing_conversation as boolean
});
export default connect(mapStateToProps)(withActionSheet(RightButtonsContainer));
export default connect(mapStateToProps)(RightButtonsContainer);

View File

@ -99,7 +99,7 @@ import {
hasPermission
} from '../../lib/methods/helpers';
import { Services } from '../../lib/services';
import { withActionSheet, TActionSheetOptions } from '../../containers/ActionSheet';
import { withActionSheet, IActionSheetProvider } from '../../containers/ActionSheet';
type TStateAttrsUpdate = keyof IRoomViewState;
@ -150,7 +150,7 @@ const roomAttrsUpdate = [
't'
] as TRoomUpdate[];
interface IRoomViewProps extends IBaseScreen<ChatsStackParamList, 'RoomView'> {
interface IRoomViewProps extends IActionSheetProvider, IBaseScreen<ChatsStackParamList, 'RoomView'> {
user: Pick<ILoggedUser, 'id' | 'username' | 'token' | 'showMessageInMainThread'>;
appState: string;
useRealName?: boolean;
@ -170,7 +170,6 @@ interface IRoomViewProps extends IBaseScreen<ChatsStackParamList, 'RoomView'> {
transferLivechatGuestPermission?: string[]; // TODO: Check if its the correct type
viewCannedResponsesPermission?: string[]; // TODO: Check if its the correct type
livechatAllowManualOnHold?: boolean;
showActionSheet: (options: TActionSheetOptions) => void;
}
interface IRoomViewState {
@ -185,7 +184,7 @@ interface IRoomViewState {
fname?: string;
prid?: string;
joinCodeRequired?: boolean;
status?: boolean;
status?: string;
lastMessage?: ILastMessage;
sysMes?: boolean;
onHold?: boolean;
@ -635,6 +634,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
encrypted={encrypted}
navigation={navigation}
toggleFollowThread={this.toggleFollowThread}
showActionSheet={this.showActionSheet}
departmentId={departmentId}
/>
)
@ -785,7 +785,12 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
};
errorActionsShow = (message: TAnyMessageModel) => {
this.messageErrorActions?.showMessageErrorActions(message);
this.messagebox?.current?.closeEmojiAndAction(this.messageErrorActions?.showMessageErrorActions, message);
};
showActionSheet = (options: any) => {
const { showActionSheet } = this.props;
this.messagebox?.current?.closeEmojiAndAction(showActionSheet, options);
};
onEditInit = (message: TAnyMessageModel) => {
@ -834,7 +839,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
};
onMessageLongPress = (message: TAnyMessageModel) => {
this.messageActions?.showMessageActions(message);
this.messagebox?.current?.closeEmojiAndAction(this.messageActions?.showMessageActions, message);
};
showAttachment = (attachment: IAttachment) => {
@ -857,7 +862,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
this.setState({ selectedMessage: message });
const { showActionSheet, baseUrl, width } = this.props;
const { selectedMessage } = this.state;
showActionSheet({
this.messagebox?.current?.closeEmojiAndAction(showActionSheet, {
children: (
<ReactionsList
reactions={selectedMessage?.reactions}
@ -1346,6 +1351,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
jumpToMessage={this.jumpToMessageByUrl}
highlighted={highlightedMessage === item.id}
theme={theme}
closeEmojiAndAction={this.messagebox?.current?.closeEmojiAndAction}
/>
);
}

View File

@ -117,7 +117,7 @@
"react-native-simple-crypto": "RocketChat/react-native-simple-crypto#0.5.0",
"react-native-slowlog": "^1.0.2",
"react-native-svg": "^12.3.0",
"react-native-ui-lib": "RocketChat/react-native-ui-lib#minor-improvements",
"react-native-ui-lib": "RocketChat/react-native-ui-lib",
"react-native-unimodules": "^0.14.8",
"react-native-vector-icons": "9.1.0",
"react-native-webview": "10.3.2",

View File

@ -15491,9 +15491,9 @@ react-native-text-size@4.0.0-rc.1:
resolved "https://registry.yarnpkg.com/react-native-text-size/-/react-native-text-size-4.0.0-rc.1.tgz#1e048d345dd6a5a8e1269e0585c1a5948c478da5"
integrity sha512-CysqjU2jK6Yc+a+kEI222pUyTY2ywcU2HqbFqf1KHymW6OPTdvBBHqbEJKL0QiLhQaFYDbqicM+h990s9TP00g==
react-native-ui-lib@RocketChat/react-native-ui-lib#minor-improvements:
version "4.2.1"
resolved "https://codeload.github.com/RocketChat/react-native-ui-lib/tar.gz/a80f38aaa947849736ce8643253991cdcb639414"
react-native-ui-lib@RocketChat/react-native-ui-lib:
version "4.2.0"
resolved "https://codeload.github.com/RocketChat/react-native-ui-lib/tar.gz/d20c1bcd09b694fc5133fc2232fd510f5f4ba581"
dependencies:
babel-plugin-transform-inline-environment-variables "^0.0.2"
color "^3.1.0"