Merge branch 'main' of https://github.com/mattermost/mattermost-mobile into MM-42835
This commit is contained in:
commit
0262b22a02
25 changed files with 280 additions and 283 deletions
36
NOTICE.txt
36
NOTICE.txt
|
|
@ -2636,42 +2636,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## react-native-user-agent
|
||||
|
||||
This product contains 'react-native-user-agent' by Bebnev Anton.
|
||||
|
||||
Library that helps you to get mobile application user agent and web view user agent strings.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/bebnev/react-native-user-agent
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Anton Bebnev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## react-native-vector-icons
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ android {
|
|||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 442
|
||||
versionCode 443
|
||||
versionName "2.0.0"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import com.wix.detox.Detox;
|
||||
import com.wix.detox.config.DetoxConfig;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
|
@ -19,10 +20,11 @@ public class DetoxTest {
|
|||
|
||||
@Test
|
||||
public void runDetoxTests() {
|
||||
Detox.DetoxIdlePolicyConfig idlePolicyConfig = new Detox.DetoxIdlePolicyConfig();
|
||||
idlePolicyConfig.masterTimeoutSec = 60;
|
||||
idlePolicyConfig.idleResourceTimeoutSec = 30;
|
||||
DetoxConfig detoxConfig = new DetoxConfig();
|
||||
detoxConfig.idlePolicyConfig.masterTimeoutSec = 90;
|
||||
detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60;
|
||||
detoxConfig.rnContextLoadTimeoutSec = (BuildConfig.DEBUG ? 180 : 60);
|
||||
|
||||
Detox.runTests(mActivityRule, idlePolicyConfig);
|
||||
Detox.runTests(mActivityRule, detoxConfig);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ const Status = ({author, statusSize, statusStyle, theme}: Props) => {
|
|||
styles.statusWrapper,
|
||||
statusStyle,
|
||||
{borderRadius: statusSize / 2},
|
||||
]), [statusStyle]);
|
||||
]), [statusStyle, styles]);
|
||||
const isBot = author && (('isBot' in author) ? author.isBot : author.is_bot);
|
||||
if (author?.status && !isBot) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -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(() => [
|
||||
|
|
|
|||
|
|
@ -11,6 +11,18 @@ export const useKeyboardTrackingPaused = (keyboardTrackingRef: RefObject<Keyboar
|
|||
const isPostDraftPaused = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
keyboardTrackingRef.current?.resumeTracking(trackerId);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const onCommandComplete = () => {
|
||||
const id = NavigationStore.getVisibleScreen();
|
||||
if (screens.includes(id) && isPostDraftPaused.current) {
|
||||
isPostDraftPaused.current = false;
|
||||
keyboardTrackingRef.current?.resumeTracking(trackerId);
|
||||
}
|
||||
};
|
||||
|
||||
const commandListener = Navigation.events().registerCommandListener(() => {
|
||||
if (!isPostDraftPaused.current) {
|
||||
isPostDraftPaused.current = true;
|
||||
|
|
@ -19,16 +31,17 @@ export const useKeyboardTrackingPaused = (keyboardTrackingRef: RefObject<Keyboar
|
|||
});
|
||||
|
||||
const commandCompletedListener = Navigation.events().registerCommandCompletedListener(() => {
|
||||
const id = NavigationStore.getVisibleScreen();
|
||||
if (screens.includes(id) && isPostDraftPaused.current) {
|
||||
isPostDraftPaused.current = false;
|
||||
keyboardTrackingRef.current?.resumeTracking(trackerId);
|
||||
}
|
||||
onCommandComplete();
|
||||
});
|
||||
|
||||
const popListener = Navigation.events().registerScreenPoppedListener(() => {
|
||||
onCommandComplete();
|
||||
});
|
||||
|
||||
return () => {
|
||||
commandListener.remove();
|
||||
commandCompletedListener.remove();
|
||||
popListener.remove();
|
||||
};
|
||||
}, [trackerId]);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
RetryTypes,
|
||||
} from '@mattermost/react-native-network-client';
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
import UserAgent from 'react-native-user-agent';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import LocalConfig from '@assets/config.json';
|
||||
import {Client} from '@client/rest';
|
||||
|
|
@ -95,11 +95,11 @@ class NetworkManager {
|
|||
};
|
||||
|
||||
private buildConfig = async () => {
|
||||
const userAgent = UserAgent.getUserAgent();
|
||||
const userAgent = `Mattermost Mobile/${DeviceInfo.getVersion()}+${DeviceInfo.getBuildNumber()} (${DeviceInfo.getSystemName()}; ${DeviceInfo.getSystemVersion()}; ${DeviceInfo.getModel()})`;
|
||||
const managedConfig = ManagedApp.enabled ? Emm.getManagedConfig<ManagedConfig>() : undefined;
|
||||
const headers: Record<string, string> = {
|
||||
...this.DEFAULT_CONFIG.headers,
|
||||
[ClientConstants.HEADER_USER_AGENT]: userAgent,
|
||||
...this.DEFAULT_CONFIG.headers,
|
||||
};
|
||||
|
||||
const config = {
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ const Modifier = ({item, searchRef, searchValue, setSearchValue}: Props) => {
|
|||
setSearchValue(newValue);
|
||||
if (item.cursorPosition) {
|
||||
const position = newValue.length + item.cursorPosition;
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
searchRef.current?.setNativeProps({selection: {start: position, end: position}});
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"ios.release": {
|
||||
"type": "ios.app",
|
||||
"binaryPath": "../ios/Build/Products/Release-iphonesimulator/Mattermost.app",
|
||||
"build": "cd ../fastlane && NODE_ENV=production bundle exec fastlane ios simulator && cd ../detox"
|
||||
"build": "cd .. && npm run build:ios-sim && cd detox"
|
||||
},
|
||||
"android.debug": {
|
||||
"type": "android.apk",
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
"ios.simulator": {
|
||||
"type": "ios.simulator",
|
||||
"device": {
|
||||
"type": "iPhone 13"
|
||||
"type": "iPhone 14"
|
||||
}
|
||||
},
|
||||
"android.emulator": {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
"e2e:android-test-release": "detox test -c android.emu.release --record-logs failing --take-screenshots failing",
|
||||
"e2e:ios-test": "IOS=true detox test -c ios.sim.debug",
|
||||
"e2e:ios-build-release": "detox build -c ios.sim.release",
|
||||
"e2e:ios-test-release": "IOS=true detox test -c ios.sim.release --headless --record-logs failing --take-screenshots failing",
|
||||
"e2e:ios-test-release": "IOS=true detox test -c ios.sim.release --record-logs failing --take-screenshots failing",
|
||||
"check": "npm run lint && npm run tsc",
|
||||
"lint": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet .",
|
||||
"start:webhook": "node webhook_server.js",
|
||||
|
|
|
|||
|
|
@ -355,39 +355,27 @@ platform :ios do
|
|||
update_identifiers
|
||||
replace_assets
|
||||
|
||||
sh 'rm -rf ../build-ios/'
|
||||
sh 'cd ../ios/ && xcodebuild -workspace Mattermost.xcworkspace/ -scheme Mattermost -sdk iphoneos -configuration Release -parallelizeTargets -resultBundlePath ../build-ios/result -derivedDataPath ../build-ios/ CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO'
|
||||
sh 'cd ../build-ios/ && mkdir -p Payload && cp -R ../build-ios/Build/Products/Release-iphoneos/Mattermost.app Payload/ && zip -r Mattermost-unsigned.ipa Payload/'
|
||||
sh 'mv ../build-ios/Mattermost-unsigned.ipa ../'
|
||||
sh 'rm -rf ../build-ios/'
|
||||
build_ios_unsigned_or_simulator(
|
||||
more_xc_args: "-sdk iphoneos CODE_SIGNING_ALLOWED=NO",
|
||||
rel_build_dir: "Release-iphoneos",
|
||||
output_file: "Mattermost-unsigned.ipa",
|
||||
)
|
||||
end
|
||||
|
||||
lane :simulator do
|
||||
UI.success('Building iOS app for simulator')
|
||||
|
||||
ENV['APP_NAME'] = 'Mattermost'
|
||||
ENV['REPLACE_ASSETS'] = 'true'
|
||||
ENV['BUILD_FOR_RELEASE'] = 'true'
|
||||
ENV['APP_SCHEME'] = 'mattermost'
|
||||
output_file = "Mattermost-simulator-x86_64.app.zip"
|
||||
|
||||
update_identifiers
|
||||
replace_assets
|
||||
|
||||
data_path = 'ios'
|
||||
if ENV['CIRCLECI'] == 'true'
|
||||
data_path = 'build-ios'
|
||||
end
|
||||
|
||||
sh 'rm -rf ../ios/build/'
|
||||
sh 'rm -rf ../build-ios/'
|
||||
sh "cd ../ios && xcodebuild -workspace Mattermost.xcworkspace -scheme Mattermost -arch x86_64 -sdk iphonesimulator -configuration Release -parallelizeTargets -derivedDataPath ../#{data_path} ENABLE_BITCODE=NO CODE_SIGN_IDENTITY='' CODE_SIGNING_REQUIRED=NO ENABLE_BITCODE=NO"
|
||||
sh "cd ../#{data_path}/Build/Products/Release-iphonesimulator && zip -r Mattermost-simulator-x86_64.app.zip Mattermost.app"
|
||||
sh "mv ../#{data_path}/Build/Products/Release-iphonesimulator/Mattermost-simulator-x86_64.app.zip ../"
|
||||
sh 'rm -rf ../build-ios/'
|
||||
build_ios_unsigned_or_simulator(
|
||||
more_xc_args: "-sdk iphonesimulator -arch x86_64",
|
||||
rel_build_dir: "Release-iphonesimulator",
|
||||
output_file: output_file,
|
||||
)
|
||||
|
||||
upload_file_to_s3({
|
||||
:os_type => "ios",
|
||||
:file => "Mattermost-simulator-x86_64.app.zip"
|
||||
:file => output_file
|
||||
})
|
||||
end
|
||||
|
||||
|
|
@ -599,6 +587,24 @@ platform :ios do
|
|||
)
|
||||
end
|
||||
|
||||
def build_ios_unsigned_or_simulator(more_xc_args:, rel_build_dir:, output_file:)
|
||||
root_dir = Dir[File.expand_path('..')].first
|
||||
ios_build_dir = "#{root_dir}/ios/Build/Products"
|
||||
|
||||
ENV['APP_NAME'] = 'Mattermost'
|
||||
ENV['REPLACE_ASSETS'] = 'true'
|
||||
ENV['BUILD_FOR_RELEASE'] = 'true'
|
||||
ENV['APP_SCHEME'] = 'mattermost'
|
||||
|
||||
update_identifiers
|
||||
replace_assets
|
||||
|
||||
sh "rm -rf #{ios_build_dir}/"
|
||||
sh "cd #{root_dir}/ios && xcodebuild -workspace Mattermost.xcworkspace -scheme Mattermost -configuration Release -parallelizeTargets CODE_SIGN_IDENTITY='' CODE_SIGNING_REQUIRED=NO SYMROOT='#{ios_build_dir}' #{more_xc_args} "
|
||||
sh "cd #{ios_build_dir}/#{rel_build_dir} && zip -r #{output_file} Mattermost.app"
|
||||
sh "mv #{ios_build_dir}/#{rel_build_dir}/#{output_file} #{root_dir}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
platform :android do
|
||||
|
|
|
|||
|
|
@ -1095,7 +1095,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 442;
|
||||
CURRENT_PROJECT_VERSION = 443;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
|
|
@ -1139,7 +1139,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 442;
|
||||
CURRENT_PROJECT_VERSION = 443;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
|
|
@ -1282,7 +1282,7 @@
|
|||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 442;
|
||||
CURRENT_PROJECT_VERSION = 443;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
|
|
@ -1333,7 +1333,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 442;
|
||||
CURRENT_PROJECT_VERSION = 443;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>442</string>
|
||||
<string>443</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>442</string>
|
||||
<string>443</string>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>OpenSans-Bold.ttf</string>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>442</string>
|
||||
<string>443</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
|
|
|||
|
|
@ -369,8 +369,6 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- react-native-turbo-mailer (0.2.0):
|
||||
- React-Core
|
||||
- react-native-user-agent (2.3.1):
|
||||
- React
|
||||
- react-native-video (5.2.1):
|
||||
- React-Core
|
||||
- react-native-video/Video (= 5.2.1)
|
||||
|
|
@ -618,7 +616,6 @@ DEPENDENCIES:
|
|||
- "react-native-paste-input (from `../node_modules/@mattermost/react-native-paste-input`)"
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- "react-native-turbo-mailer (from `../node_modules/@mattermost/react-native-turbo-mailer`)"
|
||||
- react-native-user-agent (from `../node_modules/react-native-user-agent`)
|
||||
- react-native-video (from `../node_modules/react-native-video`)
|
||||
- react-native-webrtc (from `../node_modules/react-native-webrtc`)
|
||||
- react-native-webview (from `../node_modules/react-native-webview`)
|
||||
|
|
@ -776,8 +773,6 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native-safe-area-context"
|
||||
react-native-turbo-mailer:
|
||||
:path: "../node_modules/@mattermost/react-native-turbo-mailer"
|
||||
react-native-user-agent:
|
||||
:path: "../node_modules/react-native-user-agent"
|
||||
react-native-video:
|
||||
:path: "../node_modules/react-native-video"
|
||||
react-native-webrtc:
|
||||
|
|
@ -928,7 +923,6 @@ SPEC CHECKSUMS:
|
|||
react-native-paste-input: 88709b4fd586ea8cc56ba5e2fc4cdfe90597730c
|
||||
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
|
||||
react-native-turbo-mailer: 226fc3533d16500fb4ad08cf8ab2cfc7bb1ef593
|
||||
react-native-user-agent: a90a1e839b99801baad67a73dd6f361a52aa3cf1
|
||||
react-native-video: c26780b224543c62d5e1b2a7244a5cd1b50e8253
|
||||
react-native-webrtc: 86d841823e66d68cc1f86712db1c2956056bf0c2
|
||||
react-native-webview: 91a6bd643c6c298f6dfac309231c15d60b8e9505
|
||||
|
|
|
|||
15
package-lock.json
generated
15
package-lock.json
generated
|
|
@ -88,7 +88,6 @@
|
|||
"react-native-shadow-2": "7.0.6",
|
||||
"react-native-share": "8.0.0",
|
||||
"react-native-svg": "13.6.0",
|
||||
"react-native-user-agent": "2.3.1",
|
||||
"react-native-vector-icons": "9.2.0",
|
||||
"react-native-video": "5.2.1",
|
||||
"react-native-webrtc": "github:mattermost/react-native-webrtc",
|
||||
|
|
@ -18733,14 +18732,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz",
|
||||
"integrity": "sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw=="
|
||||
},
|
||||
"node_modules/react-native-user-agent": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-user-agent/-/react-native-user-agent-2.3.1.tgz",
|
||||
"integrity": "sha512-AIFr1VgJHwgWmMwCOmIGxuBeAaADlouXKc10UyR4fzWneUbt5uIJIoRu2oExlfCtiT8IyCp106khDD5vx7RUjw==",
|
||||
"peerDependencies": {
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-vector-icons": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-9.2.0.tgz",
|
||||
|
|
@ -36079,12 +36070,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz",
|
||||
"integrity": "sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw=="
|
||||
},
|
||||
"react-native-user-agent": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-user-agent/-/react-native-user-agent-2.3.1.tgz",
|
||||
"integrity": "sha512-AIFr1VgJHwgWmMwCOmIGxuBeAaADlouXKc10UyR4fzWneUbt5uIJIoRu2oExlfCtiT8IyCp106khDD5vx7RUjw==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-native-vector-icons": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-9.2.0.tgz",
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@
|
|||
"react-native-shadow-2": "7.0.6",
|
||||
"react-native-share": "8.0.0",
|
||||
"react-native-svg": "13.6.0",
|
||||
"react-native-user-agent": "2.3.1",
|
||||
"react-native-vector-icons": "9.2.0",
|
||||
"react-native-video": "5.2.1",
|
||||
"react-native-webrtc": "github:mattermost/react-native-webrtc",
|
||||
|
|
@ -175,7 +174,6 @@
|
|||
},
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
"build-storybook": "build-storybook",
|
||||
"build:android": "./scripts/build.sh apk",
|
||||
"build:android-unsigned": "./scripts/build.sh apk unsigned",
|
||||
"build:ios": "./scripts/build.sh ipa",
|
||||
|
|
@ -196,9 +194,7 @@
|
|||
"pod-install": "cd ios && bundle exec pod install",
|
||||
"postinstall": "patch-package && ./scripts/postinstall.sh",
|
||||
"preinstall": "npx solidarity",
|
||||
"prestorybook": "rnstl",
|
||||
"start": "react-native start",
|
||||
"storybook": "start-storybook -p 7007",
|
||||
"test": "jest --forceExit --runInBand",
|
||||
"test:coverage": "jest --coverage",
|
||||
"test:watch": "npm test -- --watch",
|
||||
|
|
|
|||
|
|
@ -236,12 +236,6 @@ jest.mock('react-native-device-info', () => {
|
|||
};
|
||||
});
|
||||
|
||||
jest.mock('react-native-user-agent', () => {
|
||||
return {
|
||||
getUserAgent: () => 'user-agent',
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('react-native-localize', () => ({
|
||||
getTimeZone: () => 'World/Somewhere',
|
||||
getLocales: () => ([
|
||||
|
|
|
|||
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;
|
||||
};
|
||||
Loading…
Reference in a new issue