forked from Ivasoft/mattermost-mobile
Improve cold start (#6868)
This commit is contained in:
152
app/components/emoji/emoji.tsx
Normal file
152
app/components/emoji/emoji.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import React from 'react';
|
||||
import {
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {fetchCustomEmojiInBatch} from '@actions/remote/custom_emoji';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {queryCustomEmojisByName} from '@queries/servers/custom_emoji';
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {EmojiIndicesByAlias, Emojis} from '@utils/emoji';
|
||||
import {isUnicodeEmoji} from '@utils/emoji/helpers';
|
||||
|
||||
import type {EmojiProps} from '@typings/components/emoji';
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const assetImages = new Map([['mattermost.png', require('@assets/images/emojis/mattermost.png')]]);
|
||||
|
||||
const Emoji = (props: EmojiProps) => {
|
||||
const {
|
||||
customEmojis,
|
||||
customEmojiStyle,
|
||||
displayTextOnly,
|
||||
emojiName,
|
||||
literal = '',
|
||||
testID,
|
||||
textStyle,
|
||||
} = props;
|
||||
const serverUrl = useServerUrl();
|
||||
let assetImage = '';
|
||||
let unicode;
|
||||
let imageUrl = '';
|
||||
const name = emojiName.trim();
|
||||
if (EmojiIndicesByAlias.has(name)) {
|
||||
const emoji = Emojis[EmojiIndicesByAlias.get(name)!];
|
||||
if (emoji.category === 'custom') {
|
||||
assetImage = emoji.fileName;
|
||||
} else {
|
||||
unicode = emoji.image;
|
||||
}
|
||||
} else {
|
||||
const custom = customEmojis.find((ce) => ce.name === name);
|
||||
if (custom) {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
imageUrl = client.getCustomEmojiImageUrl(custom.id);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
} else if (name && !isUnicodeEmoji(name)) {
|
||||
fetchCustomEmojiInBatch(serverUrl, name);
|
||||
}
|
||||
}
|
||||
|
||||
let size = props.size;
|
||||
let fontSize = size;
|
||||
if (!size && textStyle) {
|
||||
const flatten = StyleSheet.flatten(textStyle);
|
||||
fontSize = flatten.fontSize;
|
||||
size = fontSize;
|
||||
}
|
||||
|
||||
if (displayTextOnly || (!imageUrl && !assetImage && !unicode)) {
|
||||
return (
|
||||
<Text
|
||||
style={textStyle}
|
||||
testID={testID}
|
||||
>
|
||||
{literal}
|
||||
</Text>);
|
||||
}
|
||||
|
||||
const width = size;
|
||||
const height = size;
|
||||
|
||||
if (unicode && !imageUrl) {
|
||||
const codeArray = unicode.split('-');
|
||||
const code = codeArray.reduce((acc: string, c: string) => {
|
||||
return acc + String.fromCodePoint(parseInt(c, 16));
|
||||
}, '');
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={[textStyle, {fontSize: size, color: '#000'}]}
|
||||
testID={testID}
|
||||
>
|
||||
{code}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (assetImage) {
|
||||
const key = Platform.OS === 'android' ? (`${assetImage}-${height}-${width}`) : null;
|
||||
|
||||
const image = assetImages.get(assetImage);
|
||||
if (!image) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<FastImage
|
||||
key={key}
|
||||
source={image}
|
||||
style={[customEmojiStyle, {width, height}]}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!imageUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Android can't change the size of an image after its first render, so
|
||||
// force a new image to be rendered when the size changes
|
||||
const key = Platform.OS === 'android' ? (`${imageUrl}-${height}-${width}`) : null;
|
||||
|
||||
return (
|
||||
<FastImage
|
||||
key={key}
|
||||
style={[customEmojiStyle, {width, height}]}
|
||||
source={{uri: imageUrl}}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const withCustomEmojis = withObservables(['emojiName'], ({database, emojiName}: WithDatabaseArgs & {emojiName: string}) => {
|
||||
const hasEmojiBuiltIn = EmojiIndicesByAlias.has(emojiName);
|
||||
|
||||
const displayTextOnly = hasEmojiBuiltIn ? of$(false) : observeConfigBooleanValue(database, 'EnableCustomEmoji').pipe(
|
||||
switchMap((value) => of$(!value)),
|
||||
);
|
||||
|
||||
return {
|
||||
displayTextOnly,
|
||||
customEmojis: hasEmojiBuiltIn ? of$([]) : queryCustomEmojisByName(database, [emojiName]).observe(),
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(withCustomEmojis(Emoji));
|
||||
@@ -1,165 +1,21 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import React from 'react';
|
||||
import {
|
||||
Platform,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextStyle,
|
||||
} from 'react-native';
|
||||
import FastImage, {ImageStyle} from 'react-native-fast-image';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
import React, {useMemo} from 'react';
|
||||
|
||||
import {fetchCustomEmojiInBatch} from '@actions/remote/custom_emoji';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {queryCustomEmojisByName} from '@queries/servers/custom_emoji';
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {EmojiIndicesByAlias, Emojis} from '@utils/emoji';
|
||||
import {isUnicodeEmoji} from '@utils/emoji/helpers';
|
||||
import {EmojiComponent, EmojiProps} from '@typings/components/emoji';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji';
|
||||
let emojiComponent: EmojiComponent;
|
||||
|
||||
const assetImages = new Map([['mattermost.png', require('@assets/images/emojis/mattermost.png')]]);
|
||||
|
||||
type Props = {
|
||||
emojiName: string;
|
||||
displayTextOnly?: boolean;
|
||||
literal?: string;
|
||||
size?: number;
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
customEmojiStyle?: StyleProp<ImageStyle>;
|
||||
customEmojis: CustomEmojiModel[];
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
const Emoji = (props: Props) => {
|
||||
const {
|
||||
customEmojis,
|
||||
customEmojiStyle,
|
||||
displayTextOnly,
|
||||
emojiName,
|
||||
literal = '',
|
||||
testID,
|
||||
textStyle,
|
||||
} = props;
|
||||
const serverUrl = useServerUrl();
|
||||
let assetImage = '';
|
||||
let unicode;
|
||||
let imageUrl = '';
|
||||
const name = emojiName.trim();
|
||||
if (EmojiIndicesByAlias.has(name)) {
|
||||
const emoji = Emojis[EmojiIndicesByAlias.get(name)!];
|
||||
if (emoji.category === 'custom') {
|
||||
assetImage = emoji.fileName;
|
||||
} else {
|
||||
unicode = emoji.image;
|
||||
const EmojiWrapper = (props: Omit<EmojiProps, 'customEmojis'>) => {
|
||||
const Emoji = useMemo(() => {
|
||||
if (!emojiComponent) {
|
||||
emojiComponent = require('./emoji').default;
|
||||
}
|
||||
} else {
|
||||
const custom = customEmojis.find((ce) => ce.name === name);
|
||||
if (custom) {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
imageUrl = client.getCustomEmojiImageUrl(custom.id);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
} else if (name && !isUnicodeEmoji(name)) {
|
||||
fetchCustomEmojiInBatch(serverUrl, name);
|
||||
}
|
||||
}
|
||||
return emojiComponent;
|
||||
}, []);
|
||||
|
||||
let size = props.size;
|
||||
let fontSize = size;
|
||||
if (!size && textStyle) {
|
||||
const flatten = StyleSheet.flatten(textStyle);
|
||||
fontSize = flatten.fontSize;
|
||||
size = fontSize;
|
||||
}
|
||||
|
||||
if (displayTextOnly || (!imageUrl && !assetImage && !unicode)) {
|
||||
return (
|
||||
<Text
|
||||
style={textStyle}
|
||||
testID={testID}
|
||||
>
|
||||
{literal}
|
||||
</Text>);
|
||||
}
|
||||
|
||||
const width = size;
|
||||
const height = size;
|
||||
|
||||
if (unicode && !imageUrl) {
|
||||
const codeArray = unicode.split('-');
|
||||
const code = codeArray.reduce((acc: string, c: string) => {
|
||||
return acc + String.fromCodePoint(parseInt(c, 16));
|
||||
}, '');
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={[textStyle, {fontSize: size, color: '#000'}]}
|
||||
testID={testID}
|
||||
>
|
||||
{code}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (assetImage) {
|
||||
const key = Platform.OS === 'android' ? (`${assetImage}-${height}-${width}`) : null;
|
||||
|
||||
const image = assetImages.get(assetImage);
|
||||
if (!image) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<FastImage
|
||||
key={key}
|
||||
source={image}
|
||||
style={[customEmojiStyle, {width, height}]}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!imageUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Android can't change the size of an image after its first render, so
|
||||
// force a new image to be rendered when the size changes
|
||||
const key = Platform.OS === 'android' ? (`${imageUrl}-${height}-${width}`) : null;
|
||||
|
||||
return (
|
||||
<FastImage
|
||||
key={key}
|
||||
style={[customEmojiStyle, {width, height}]}
|
||||
source={{uri: imageUrl}}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
return (<Emoji {...props}/>);
|
||||
};
|
||||
|
||||
const withCustomEmojis = withObservables(['emojiName'], ({database, emojiName}: WithDatabaseArgs & {emojiName: string}) => {
|
||||
const hasEmojiBuiltIn = EmojiIndicesByAlias.has(emojiName);
|
||||
|
||||
const displayTextOnly = hasEmojiBuiltIn ? of$(false) : observeConfigBooleanValue(database, 'EnableCustomEmoji').pipe(
|
||||
switchMap((value) => of$(!value)),
|
||||
);
|
||||
|
||||
return {
|
||||
displayTextOnly,
|
||||
customEmojis: hasEmojiBuiltIn ? of$([]) : queryCustomEmojisByName(database, [emojiName]).observe(),
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(withCustomEmojis(Emoji));
|
||||
export default EmojiWrapper;
|
||||
|
||||
@@ -9,13 +9,13 @@ import {Dimensions, GestureResponderEvent, Platform, StyleProp, Text, TextStyle,
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import Emoji from '@components/emoji';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import Hashtag from '@components/markdown/hashtag';
|
||||
import {computeTextStyle} from '@utils/markdown';
|
||||
import {blendColors, changeOpacity, concatStyles, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {getScheme} from '@utils/url';
|
||||
|
||||
import AtMention from './at_mention';
|
||||
import ChannelMention, {ChannelMentions} from './channel_mention';
|
||||
import Hashtag from './hashtag';
|
||||
import MarkdownBlockQuote from './markdown_block_quote';
|
||||
import MarkdownCodeBlock from './markdown_code_block';
|
||||
import MarkdownImage from './markdown_image';
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
|
||||
import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, {useCallback} from 'react';
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, StyleSheet, Text, TextStyle, TouchableOpacity, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import SyntaxHighlighter from '@components/syntax_highlight';
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {bottomSheet, dismissBottomSheet, goToScreen} from '@screens/navigation';
|
||||
@@ -19,6 +18,8 @@ import {getHighlightLanguageFromNameOrAlias, getHighlightLanguageName} from '@ut
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import type {SyntaxHiglightProps} from '@typings/components/syntax_highlight';
|
||||
|
||||
type MarkdownCodeBlockProps = {
|
||||
language: string;
|
||||
content: string;
|
||||
@@ -27,6 +28,8 @@ type MarkdownCodeBlockProps = {
|
||||
|
||||
const MAX_LINES = 4;
|
||||
|
||||
let syntaxHighlighter: (props: SyntaxHiglightProps) => JSX.Element;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
bottomSheet: {
|
||||
@@ -70,6 +73,13 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc
|
||||
const theme = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const style = getStyleSheet(theme);
|
||||
const SyntaxHighlighter = useMemo(() => {
|
||||
if (!syntaxHighlighter) {
|
||||
syntaxHighlighter = require('@components/syntax_highlight').default;
|
||||
}
|
||||
|
||||
return syntaxHighlighter;
|
||||
}, []);
|
||||
|
||||
const handlePress = useCallback(preventDoubleTap(() => {
|
||||
const screen = Screens.CODE;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {StyleSheet, TextStyle, View} from 'react-native';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import {github, monokai, solarizedDark, solarizedLight} from 'react-syntax-highlighter/dist/cjs/styles/hljs';
|
||||
|
||||
@@ -10,12 +10,7 @@ import {useTheme} from '@context/theme';
|
||||
|
||||
import CodeHighlightRenderer from './renderer';
|
||||
|
||||
type Props = {
|
||||
code: string;
|
||||
language: string;
|
||||
textStyle: TextStyle;
|
||||
selectable?: boolean;
|
||||
}
|
||||
import type {SyntaxHiglightProps} from '@typings/components/syntax_highlight';
|
||||
|
||||
const codeTheme: Record<string, any> = {
|
||||
github,
|
||||
@@ -34,7 +29,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
const Highlighter = ({code, language, textStyle, selectable = false}: Props) => {
|
||||
const Highlighter = ({code, language, textStyle, selectable = false}: SyntaxHiglightProps) => {
|
||||
const theme = useTheme();
|
||||
const style = codeTheme[theme.codeTheme] || github;
|
||||
const preTagStyle = useMemo(() => [
|
||||
|
||||
19
types/components/emoji.ts
Normal file
19
types/components/emoji.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji';
|
||||
import type {StyleProp, TextStyle} from 'react-native';
|
||||
import type {ImageStyle} from 'react-native-fast-image';
|
||||
|
||||
export type EmojiProps = {
|
||||
emojiName: string;
|
||||
displayTextOnly?: boolean;
|
||||
literal?: string;
|
||||
size?: number;
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
customEmojiStyle?: StyleProp<ImageStyle>;
|
||||
customEmojis: CustomEmojiModel[];
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
export type EmojiComponent = (props: Omit<EmojiProps, 'customEmojis'>) => JSX.Element;
|
||||
11
types/components/syntax_highlight.ts
Normal file
11
types/components/syntax_highlight.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {TextStyle} from 'react-native';
|
||||
|
||||
export type SyntaxHiglightProps = {
|
||||
code: string;
|
||||
language: string;
|
||||
textStyle: TextStyle;
|
||||
selectable?: boolean;
|
||||
};
|
||||
Reference in New Issue
Block a user