New reaction picker as bottom action sheet

This commit is contained in:
Danish Ahmed Mirza 2022-07-14 19:20:41 +05:30 committed by Danish
parent de9036edeb
commit b05d876946
10 changed files with 293 additions and 257 deletions

View File

@ -140,8 +140,8 @@ const ActionSheet = React.memo(
style={{ ...styles.container, ...bottomSheet }} style={{ ...styles.container, ...bottomSheet }}
backgroundStyle={{ backgroundColor: colors.focusedBackground }} backgroundStyle={{ backgroundColor: colors.focusedBackground }}
onChange={index => index === -1 && onClose()} onChange={index => index === -1 && onClose()}
// We need this to allow horizontal swipe gestures inside bottom sheet like in reaction picker activeOffsetY={[-1, 1]}
enableContentPanningGesture={data?.enableContentPanningGesture ?? true} failOffsetX={[-5, 5]}
{...androidTablet} {...androidTablet}
> >
<BottomSheetContent options={data?.options} hide={hide} children={data?.children} hasCancel={data?.hasCancel} /> <BottomSheetContent options={data?.options} hide={hide} children={data?.children} hasCancel={data?.hasCancel} />

View File

@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import { FlatList, Text, TouchableOpacity } from 'react-native'; import { Text, TouchableOpacity } from 'react-native';
import { BottomSheetFlatList } from '@gorhom/bottom-sheet';
import { FlatList as GHFlatList } from 'react-native-gesture-handler';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
import styles from './styles'; import styles from './styles';
@ -10,7 +12,7 @@ import { IEmoji, IEmojiCategory } from '../../definitions/IEmoji';
const MAX_EMOJI_SIZE = 50; const MAX_EMOJI_SIZE = 50;
const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => { const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => {
if (emoji && emoji.isCustom) { if (emoji?.isCustom || emoji?.name) {
return ( return (
<CustomEmoji <CustomEmoji
style={[styles.customCategoryEmoji, { height: size - 16, width: size - 16 }]} style={[styles.customCategoryEmoji, { height: size - 16, width: size - 16 }]}
@ -26,40 +28,46 @@ const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => {
); );
}; };
const EmojiCategory = React.memo(({ baseUrl, onEmojiSelected, emojis, width, tabsCount, ...props }: IEmojiCategory) => { const EmojiCategory = React.memo(
const emojiSize = width ? Math.min(width / tabsCount, MAX_EMOJI_SIZE) : MAX_EMOJI_SIZE; ({ baseUrl, onEmojiSelected, emojis, width, tabsCount, isBottomSheet, ...props }: IEmojiCategory) => {
const numColumns = Math.trunc(width ? width / emojiSize : tabsCount); const emojiSize = width ? Math.min(width / tabsCount, MAX_EMOJI_SIZE) : MAX_EMOJI_SIZE;
const renderItem = (emoji: IEmoji) => ( const numColumns = Math.trunc(width ? width / emojiSize : tabsCount);
<TouchableOpacity
activeOpacity={0.7}
// @ts-ignore
key={emoji && emoji.isCustom ? emoji.content : emoji}
onPress={() => onEmojiSelected(emoji)}
testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`}>
{renderEmoji(emoji, emojiSize, baseUrl)}
</TouchableOpacity>
);
if (!width) { const FlatList = isBottomSheet ? BottomSheetFlatList : GHFlatList;
return null;
const renderItem = (emoji: IEmoji) => (
<TouchableOpacity
activeOpacity={0.7}
// @ts-ignore
key={emoji && emoji.isCustom ? emoji.content : emoji}
onPress={() => onEmojiSelected(emoji)}
testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`}
>
{renderEmoji(emoji, emojiSize, baseUrl)}
</TouchableOpacity>
);
if (!width) {
return null;
}
return (
<FlatList
// rerender FlatList in case of width changes
key={`emoji-category-${width}`}
// @ts-ignore
keyExtractor={item => (item && item.isCustom && item.content) || item}
data={emojis}
extraData={{ baseUrl, onEmojiSelected, width, ...props }}
renderItem={({ item }) => renderItem(item)}
numColumns={numColumns}
initialNumToRender={45}
removeClippedSubviews
{...scrollPersistTaps}
keyboardDismissMode={'none'}
/>
);
} }
);
return (
<FlatList
// rerender FlatList in case of width changes
key={`emoji-category-${width}`}
// @ts-ignore
keyExtractor={item => (item && item.isCustom && item.content) || item}
data={emojis}
extraData={{ baseUrl, onEmojiSelected, width, ...props }}
renderItem={({ item }) => renderItem(item)}
numColumns={numColumns}
initialNumToRender={45}
removeClippedSubviews
{...scrollPersistTaps}
keyboardDismissMode={'none'}
/>
);
});
export default EmojiCategory; export default EmojiCategory;

View File

@ -14,19 +14,11 @@ const TabBar = React.memo(({ activeTab, tabs, goToPage, tabEmojiStyle }: ITabBar
<TouchableOpacity <TouchableOpacity
activeOpacity={0.7} activeOpacity={0.7}
key={tab} key={tab}
onPress={() => { onPress={() => goToPage?.(i)}
if (goToPage) {
goToPage(i);
}
}}
style={styles.tab} style={styles.tab}
testID={`reaction-picker-${tab}`}> testID={`reaction-picker-${tab}`}>
<Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text> <Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text>
{activeTab === i ? ( <View style={activeTab === i ? [styles.activeTabLine, { backgroundColor: colors.tintColor }] : styles.tabLine} />
<View style={[styles.activeTabLine, { backgroundColor: colors.tintColor }]} />
) : (
<View style={styles.tabLine} />
)}
</TouchableOpacity> </TouchableOpacity>
))} ))}
</View> </View>

View File

@ -19,147 +19,164 @@ import { IEmoji, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definiti
import { useAppSelector } from '../../lib/hooks'; import { useAppSelector } from '../../lib/hooks';
import { IEmojiPickerProps, EventTypes } from './interfaces'; import { IEmojiPickerProps, EventTypes } from './interfaces';
const EmojiPicker = React.memo(({ onItemClicked, tabEmojiStyle, isEmojiKeyboard = false }: IEmojiPickerProps) => { const EmojiPicker = React.memo(
const [frequentlyUsed, setFrequentlyUsed] = useState<IEmoji[]>([]); ({ onItemClicked, tabEmojiStyle, isEmojiKeyboard = false, searching = false, searchedEmojis = [] }: IEmojiPickerProps) => {
const [show, setShow] = useState(false); const [frequentlyUsed, setFrequentlyUsed] = useState<IEmoji[]>([]);
const [width, setWidth] = useState(null); const [show, setShow] = useState(false);
const { colors } = useTheme(); const [width, setWidth] = useState(null);
const { colors } = useTheme();
const allCustomEmojis: ICustomEmojis = useAppSelector(state => state.customEmojis); const baseUrl = useAppSelector(state => state.server?.server);
const baseUrl = useAppSelector(state=>state.server?.server) const allCustomEmojis: ICustomEmojis = useAppSelector(state => state.customEmojis);
const customEmojis = Object.keys(allCustomEmojis) const customEmojis = Object.keys(allCustomEmojis)
.filter(item => item === allCustomEmojis[item].name) .filter(item => item === allCustomEmojis[item].name)
.map(item => ({ .map(item => ({
content: allCustomEmojis[item].name, content: allCustomEmojis[item].name,
extension: allCustomEmojis[item].extension, extension: allCustomEmojis[item].extension,
isCustom: true isCustom: true
})); }));
useEffect(() => { useEffect(() => {
const init = async () => { const init = async () => {
await updateFrequentlyUsed(); await updateFrequentlyUsed();
setShow(true); setShow(true);
};
init();
}, []);
const handleEmojiSelect = (emoji: IEmoji) => {
try {
if (emoji.isCustom) {
_addFrequentlyUsed({
content: emoji.content,
extension: emoji.extension,
isCustom: true
});
onItemClicked(EventTypes.EMOJI_PRESSED, `:${emoji.content}:`);
} else {
const content = emoji;
_addFrequentlyUsed({ content, isCustom: false });
const shortname = `:${emoji}:`;
onItemClicked(EventTypes.EMOJI_PRESSED, shortnameToUnicode(shortname), shortname);
}
} catch (e) {
log(e);
}
}; };
init();
}, []);
const handleEmojiSelect = (emoji: IEmoji) => { const _addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => {
try { const db = database.active;
if (emoji.isCustom) { const freqEmojiCollection = db.get('frequently_used_emojis');
_addFrequentlyUsed({ let freqEmojiRecord: TFrequentlyUsedEmojiModel;
content: emoji.content, try {
extension: emoji.extension, freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
isCustom: true } catch (error) {
}); // Do nothing
onItemClicked(EventTypes.EMOJI_PRESSED, `:${emoji.content}:`);
} else {
const content = emoji;
_addFrequentlyUsed({ content, isCustom: false });
const shortname = `:${emoji}:`;
onItemClicked(EventTypes.EMOJI_PRESSED, shortnameToUnicode(shortname));
} }
} catch (e) {
log(e);
}
};
const _addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => { await db.write(async () => {
const db = database.active; if (freqEmojiRecord) {
const freqEmojiCollection = db.get('frequently_used_emojis'); await freqEmojiRecord.update(f => {
let freqEmojiRecord: TFrequentlyUsedEmojiModel; if (f.count) {
try { f.count += 1;
freqEmojiRecord = await freqEmojiCollection.find(emoji.content); }
} catch (error) { });
// Do nothing } else {
} await freqEmojiCollection.create(f => {
f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema);
await db.write(async () => { Object.assign(f, emoji);
if (freqEmojiRecord) { f.count = 1;
await freqEmojiRecord.update(f => { });
if (f.count) { }
f.count += 1; });
}
});
} else {
await freqEmojiCollection.create(f => {
f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema);
Object.assign(f, emoji);
f.count = 1;
});
}
}); });
});
const updateFrequentlyUsed = async () => { const updateFrequentlyUsed = async () => {
const db = database.active; const db = database.active;
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch(); const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']); const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
const frequentlyUsedEmojis = frequentlyUsedOrdered.map(item => { const frequentlyUsedEmojis = frequentlyUsedOrdered.map(item => {
if (item.isCustom) { if (item.isCustom) {
return { content: item.content, extension: item.extension, isCustom: item.isCustom }; return { content: item.content, extension: item.extension, isCustom: item.isCustom };
}
return shortnameToUnicode(`${item.content}`);
}) as IEmoji[];
setFrequentlyUsed(frequentlyUsedEmojis);
};
const onLayout = ({
nativeEvent: {
layout: { width }
} }
return shortnameToUnicode(`${item.content}`); }: any) => setWidth(width);
}) as IEmoji[];
setFrequentlyUsed(frequentlyUsedEmojis);
};
const onLayout = ({ const renderCategory = (category: keyof typeof emojisByCategory, i: number, label: string, tabsCount: number) => {
nativeEvent: { let emojis = [];
layout: { width } if (i === 0) {
} emojis = frequentlyUsed;
}: any) => setWidth(width); } else if (i === 1) {
emojis = customEmojis;
const renderCategory = (category: keyof typeof emojisByCategory, i: number, label: string, tabsCount: number) => { } else {
let emojis = []; emojis = emojisByCategory[category];
if (i === 0) { }
emojis = frequentlyUsed; return (
} else if (i === 1) { <EmojiCategory
emojis = customEmojis; emojis={emojis as IEmoji[]}
} else { onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)}
emojis = emojisByCategory[category]; style={styles.categoryContainer}
} width={width}
return ( baseUrl={baseUrl}
<EmojiCategory tabLabel={label}
emojis={emojis as IEmoji[]} tabsCount={tabsCount}
onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} isBottomSheet={!isEmojiKeyboard}
style={styles.categoryContainer}
width={width}
baseUrl={baseUrl}
tabLabel={label}
tabsCount={tabsCount}
/>
);
};
if (!show) {
return null;
}
const tabsCount = frequentlyUsed.length === 0 ? categories.tabs.length - 1 : categories.tabs.length;
return (
<View onLayout={onLayout} style={{ flex: 1 }}>
<ScrollableTabView
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} />}
contentProps={{
keyboardShouldPersistTaps: 'always',
keyboardDismissMode: 'none'
}}
style={{ backgroundColor: colors.focusedBackground }}>
{categories.tabs.map((tab: any, i) =>
i === 0 && frequentlyUsed.length === 0
? null // when no frequentlyUsed don't show the tab
: renderCategory(tab.category, i, tab.tabLabel, tabsCount)
)}
</ScrollableTabView>
{isEmojiKeyboard && (
<Footer
onSearchPressed={() => onItemClicked(EventTypes.SEARCH_PRESSED)}
onBackspacePressed={() => onItemClicked(EventTypes.BACKSPACE_PRESSED)}
/> />
)} );
</View> };
);
}); if (!show) {
return null;
}
const tabsCount = frequentlyUsed.length === 0 ? categories.tabs.length - 1 : categories.tabs.length;
return (
<View onLayout={onLayout} style={{ flex: 1 }}>
{searching ? (
<EmojiCategory
emojis={searchedEmojis as IEmoji[]}
onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)}
style={styles.categoryContainer}
width={width}
baseUrl={baseUrl}
tabLabel={'searching'}
tabsCount={tabsCount}
isBottomSheet={!isEmojiKeyboard}
/>
) : (
<ScrollableTabView
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} />}
contentProps={{
keyboardShouldPersistTaps: 'always',
keyboardDismissMode: 'none'
}}
style={{ backgroundColor: colors.focusedBackground }}
>
{categories.tabs.map((tab: any, i) =>
i === 0 && frequentlyUsed.length === 0
? null // when no frequentlyUsed don't show the tab
: renderCategory(tab.category, i, tab.tabLabel, tabsCount)
)}
</ScrollableTabView>
)}
{isEmojiKeyboard && (
<Footer
onSearchPressed={() => onItemClicked(EventTypes.SEARCH_PRESSED)}
onBackspacePressed={() => onItemClicked(EventTypes.BACKSPACE_PRESSED)}
/>
)}
</View>
);
}
);
export default EmojiPicker; export default EmojiPicker;

View File

@ -1,5 +1,7 @@
import { StyleProp, TextStyle } from 'react-native'; import { StyleProp, TextStyle } from 'react-native';
import { IEmoji } from '../../definitions';
export enum EventTypes { export enum EventTypes {
EMOJI_PRESSED = 'emojiPressed', EMOJI_PRESSED = 'emojiPressed',
BACKSPACE_PRESSED = 'backspacePressed', BACKSPACE_PRESSED = 'backspacePressed',
@ -7,9 +9,11 @@ export enum EventTypes {
} }
export interface IEmojiPickerProps { export interface IEmojiPickerProps {
onItemClicked: (event: EventTypes, emoji?: string) => void; onItemClicked: (event: EventTypes, emoji?: string, shortname?: string) => void;
tabEmojiStyle?: StyleProp<TextStyle>; tabEmojiStyle?: StyleProp<TextStyle>;
isEmojiKeyboard?: boolean; isEmojiKeyboard?: boolean;
searching?: boolean;
searchedEmojis?: (string | IEmoji)[];
} }
export interface IFooterProps { export interface IFooterProps {

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { View, Text, TouchableOpacity, TextInput, FlatList } from 'react-native'; import { View, Text, TouchableOpacity, TextInput, FlatList } from 'react-native';
import { orderBy } from 'lodash'; import { orderBy } from 'lodash';
import FormTextInput from '../TextInput/FormTextInput'; import { FormTextInput } from '../TextInput/FormTextInput';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { CustomIcon } from '../CustomIcon'; import { CustomIcon } from '../CustomIcon';
@ -31,7 +31,7 @@ const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => {
const EmojiSearchbar = React.forwardRef<TextInput, IEmojiSearchbarProps>( const EmojiSearchbar = React.forwardRef<TextInput, IEmojiSearchbarProps>(
({ openEmoji, onChangeText, emojis, onEmojiSelected, baseUrl }, ref) => { ({ openEmoji, onChangeText, emojis, onEmojiSelected, baseUrl }, ref) => {
const { colors, theme } = useTheme(); const { colors } = useTheme();
const [searchText, setSearchText] = useState<string>(''); const [searchText, setSearchText] = useState<string>('');
const [frequentlyUsed, setFrequentlyUsed] = useState([]); const [frequentlyUsed, setFrequentlyUsed] = useState([]);
@ -103,7 +103,6 @@ const EmojiSearchbar = React.forwardRef<TextInput, IEmojiSearchbarProps>(
style={[styles.emojiSearchbar, { backgroundColor: colors.passcodeButtonActive }]} style={[styles.emojiSearchbar, { backgroundColor: colors.passcodeButtonActive }]}
containerStyle={styles.textInputContainer} containerStyle={styles.textInputContainer}
value={searchText} value={searchText}
theme={theme}
onClearInput={() => handleTextChange('')} onClearInput={() => handleTextChange('')}
iconRight={'search'} iconRight={'search'}
/> />

View File

@ -36,6 +36,7 @@ export interface IEmojiCategory {
style: StyleProp<ImageStyle>; style: StyleProp<ImageStyle>;
tabLabel: string; tabLabel: string;
tabsCount: number; tabsCount: number;
isBottomSheet: boolean;
} }
export type TGetCustomEmoji = (name: string) => any; export type TGetCustomEmoji = (name: string) => any;

View File

@ -1,87 +1,86 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { connect } from 'react-redux'; import { Q } from '@nozbe/watermelondb';
import Modal from 'react-native-modal';
import EmojiPicker from '../../containers/EmojiPicker'; import EmojiPicker from '../../containers/EmojiPicker';
import { isAndroid } from '../../lib/methods/helpers'; import { useTheme } from '../../theme';
import { themes } from '../../lib/constants';
import { TSupportedThemes, withTheme } from '../../theme';
import styles from './styles'; import styles from './styles';
import { IApplicationState } from '../../definitions';
import { EventTypes } from '../../containers/EmojiPicker/interfaces'; import { EventTypes } from '../../containers/EmojiPicker/interfaces';
import { FormTextInput } from '../../containers/TextInput/FormTextInput';
const margin = isAndroid ? 40 : 20; import I18n from '../../i18n';
const maxSize = 400; import { sanitizeLikeString } from '../../lib/database/utils';
import { emojis } from '../../containers/EmojiPicker/emojis';
import database from '../../lib/database';
import { debounce } from '../../lib/methods/helpers/debounce';
interface IReactionPickerProps { interface IReactionPickerProps {
message?: any; message?: any;
show: boolean; show: boolean;
isMasterDetail: boolean;
reactionClose: () => void; reactionClose: () => void;
onEmojiSelected: (shortname: string, id: string) => void; onEmojiSelected: (shortname: string, id: string) => void;
width: number; width: number;
height: number; height: number;
theme: TSupportedThemes;
} }
class ReactionPicker extends React.Component<IReactionPickerProps> { const MAX_EMOJIS_TO_DISPLAY = 20;
shouldComponentUpdate(nextProps: IReactionPickerProps) {
const { show, width, height } = this.props;
return nextProps.show !== show || width !== nextProps.width || height !== nextProps.height;
}
onEmojiSelected = (_eventType: EventTypes, emoji?: string, shortname?: string) => { const ReactionPicker = React.memo(({ onEmojiSelected, message, reactionClose }: IReactionPickerProps) => {
const { colors } = useTheme();
const [searchText, setSearchText] = React.useState<string>('');
const [searchedEmojis, setSearchedEmojis] = React.useState<any[]>([]);
const [searching, setSearching] = React.useState<boolean>(false);
const handleTextChange = (text: string) => {
setSearching(text !== '');
setSearchText(text);
searchEmojis(text);
};
const searchEmojis = debounce(async (keyword: string) => {
const likeString = sanitizeLikeString(keyword);
const whereClause = [];
if (likeString) {
whereClause.push(Q.where('name', Q.like(`${likeString}%`)));
}
const db = database.active;
const customEmojisCollection = db.get('custom_emojis');
const customEmojis = await (await customEmojisCollection.query(...whereClause).fetch()).slice(0, MAX_EMOJIS_TO_DISPLAY / 2);
const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MAX_EMOJIS_TO_DISPLAY / 2);
const mergedEmojis = [...customEmojis, ...filteredEmojis];
setSearchedEmojis(mergedEmojis);
}, 300);
const handleEmojiSelect = (_eventType: EventTypes, emoji?: string, shortname?: string) => {
// standard emojis: `emoji` is unicode and `shortname` is :joy: // standard emojis: `emoji` is unicode and `shortname` is :joy:
// custom emojis: only `emoji` is returned with shortname type (:joy:) // custom emojis: only `emoji` is returned with shortname type (:joy:)
// to set reactions, we need shortname type // to set reactions, we need shortname type
const { onEmojiSelected, message } = this.props;
if (message) { if (message) {
// @ts-ignore // @ts-ignore
onEmojiSelected(shortname || emoji, message.id); onEmojiSelected(shortname || emoji, message.id);
} }
reactionClose();
}; };
render() { return (
const { width, height, show, reactionClose, isMasterDetail, theme } = this.props; <View style={[styles.reactionPickerContainer]} testID='reaction-picker'>
<View style={styles.searchbarContainer}>
let widthStyle = width - margin; <FormTextInput
let heightStyle = Math.min(width, height) - margin * 2; autoCapitalize='none'
autoCorrect={false}
if (isMasterDetail) { blurOnSubmit
widthStyle = maxSize; placeholder={I18n.t('Search_emoji')}
heightStyle = maxSize; returnKeyType='search'
} underlineColorAndroid='transparent'
onChangeText={handleTextChange}
return show ? ( style={[styles.reactionPickerSearchbar, { backgroundColor: colors.passcodeButtonActive }]}
<Modal value={searchText}
isVisible={show} onClearInput={() => handleTextChange('')}
style={{ alignItems: 'center' }} iconRight={'search'}
onBackdropPress={reactionClose} />
onBackButtonPress={reactionClose} </View>
animationIn='fadeIn' <EmojiPicker onItemClicked={handleEmojiSelect} searching={searching} searchedEmojis={searchedEmojis} />
animationOut='fadeOut' </View>
backdropOpacity={themes[theme].backdropOpacity} );
>
<View
style={[
styles.reactionPickerContainer,
{
width: widthStyle,
height: heightStyle
}
]}
testID='reaction-picker'
>
<EmojiPicker onItemClicked={this.onEmojiSelected} />
</View>
</Modal>
) : null;
}
}
const mapStateToProps = (state: IApplicationState) => ({
isMasterDetail: state.app.isMasterDetail
}); });
export default connect(mapStateToProps)(withTheme(ReactionPicker)); export default ReactionPicker;

View File

@ -828,12 +828,32 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
this.setState({ selectedMessage: undefined, replying: false, replyWithMention: false }); this.setState({ selectedMessage: undefined, replying: false, replyWithMention: false });
}; };
showReactionPicker = () => {
const { showActionSheet, width, height } = this.props;
const { reacting, selectedMessage } = this.state;
showActionSheet &&
showActionSheet({
children: (
<ReactionPicker
show={reacting}
message={selectedMessage}
onEmojiSelected={this.onReactionPress}
reactionClose={this.onReactionClose}
width={width}
height={height}
/>
),
snaps: [400, '100%']
});
};
onReactionInit = (message: TAnyMessageModel) => { onReactionInit = (message: TAnyMessageModel) => {
this.setState({ selectedMessage: message, reacting: true }); this.setState({ selectedMessage: message }, this.showReactionPicker);
}; };
onReactionClose = () => { onReactionClose = () => {
this.setState({ selectedMessage: undefined, reacting: false }); const { hideActionSheet } = this.props;
this.setState({ selectedMessage: undefined, reacting: false }, hideActionSheet);
}; };
onMessageLongPress = (message: TAnyMessageModel) => { onMessageLongPress = (message: TAnyMessageModel) => {
@ -1493,8 +1513,8 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
render() { render() {
console.count(`${this.constructor.name}.render calls`); console.count(`${this.constructor.name}.render calls`);
const { room, selectedMessage, loading, reacting } = this.state; const { room, loading } = this.state;
const { user, baseUrl, theme, navigation, Hide_System_Messages, width, height, serverVersion } = this.props; const { user, baseUrl, theme, navigation, Hide_System_Messages, width, serverVersion } = this.props;
const { rid, t } = room; const { rid, t } = room;
let sysMes; let sysMes;
let bannerClosed; let bannerClosed;
@ -1526,15 +1546,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
/> />
{this.renderFooter()} {this.renderFooter()}
{this.renderActions()} {this.renderActions()}
<ReactionPicker
show={reacting}
message={selectedMessage}
onEmojiSelected={this.onReactionPress}
reactionClose={this.onReactionClose}
width={width}
height={height}
theme={theme}
/>
<UploadProgress rid={rid} user={user} baseUrl={baseUrl} width={width} /> <UploadProgress rid={rid} user={user} baseUrl={baseUrl} width={width} />
<JoinCode ref={this.joinCode} onJoin={this.onJoin} rid={rid} t={t} theme={theme} /> <JoinCode ref={this.joinCode} onJoin={this.onJoin} rid={rid} t={t} theme={theme} />
</SafeAreaView> </SafeAreaView>

View File

@ -15,9 +15,7 @@ export default StyleSheet.create({
marginVertical: 15 marginVertical: 15
}, },
reactionPickerContainer: { reactionPickerContainer: {
borderRadius: 4, height: '100%'
flexDirection: 'column',
overflow: 'hidden'
}, },
bannerContainer: { bannerContainer: {
paddingVertical: 12, paddingVertical: 12,
@ -64,5 +62,12 @@ export default StyleSheet.create({
previewMode: { previewMode: {
fontSize: 16, fontSize: 16,
...sharedStyles.textMedium ...sharedStyles.textMedium
},
searchbarContainer: {
marginBottom: 10,
paddingHorizontal: 15
},
reactionPickerSearchbar: {
paddingHorizontal: 20
} }
}); });