Merge branch 'develop' into new.delete-server
# Conflicts: # app/lib/rocketchat.js
This commit is contained in:
commit
533933373e
|
@ -0,0 +1,2 @@
|
||||||
|
export class Rocketchat {}
|
||||||
|
export const settings = {};
|
|
@ -0,0 +1,14 @@
|
||||||
|
export class Sound {
|
||||||
|
loadAsync = () => {};
|
||||||
|
|
||||||
|
playAsync = () => {};
|
||||||
|
|
||||||
|
pauseAsync = () => {};
|
||||||
|
|
||||||
|
stopAsync = () => {};
|
||||||
|
|
||||||
|
setOnPlaybackStatusUpdate = () => {};
|
||||||
|
|
||||||
|
setPositionAsync = () => {};
|
||||||
|
}
|
||||||
|
export const Audio = { Sound };
|
File diff suppressed because it is too large
Load Diff
|
@ -138,7 +138,7 @@ android {
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode VERSIONCODE as Integer
|
versionCode VERSIONCODE as Integer
|
||||||
versionName "4.6.0"
|
versionName "4.7.0"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const ROOMS = createRequestTypes('ROOMS', [
|
||||||
'OPEN_SEARCH_HEADER',
|
'OPEN_SEARCH_HEADER',
|
||||||
'CLOSE_SEARCH_HEADER'
|
'CLOSE_SEARCH_HEADER'
|
||||||
]);
|
]);
|
||||||
export const ROOM = createRequestTypes('ROOM', ['LEAVE', 'DELETE', 'REMOVED', 'USER_TYPING']);
|
export const ROOM = createRequestTypes('ROOM', ['SUBSCRIBE', 'UNSUBSCRIBE', 'LEAVE', 'DELETE', 'REMOVED', 'USER_TYPING']);
|
||||||
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS']);
|
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS']);
|
||||||
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
||||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
||||||
|
|
|
@ -1,5 +1,19 @@
|
||||||
import * as types from './actionsTypes';
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
|
export function subscribeRoom(rid) {
|
||||||
|
return {
|
||||||
|
type: types.ROOM.SUBSCRIBE,
|
||||||
|
rid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unsubscribeRoom(rid) {
|
||||||
|
return {
|
||||||
|
type: types.ROOM.UNSUBSCRIBE,
|
||||||
|
rid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function leaveRoom(rid, t) {
|
export function leaveRoom(rid, t) {
|
||||||
return {
|
return {
|
||||||
type: types.ROOM.LEAVE,
|
type: types.ROOM.LEAVE,
|
||||||
|
|
|
@ -100,7 +100,7 @@ export const themes = {
|
||||||
infoText: '#6d6d72',
|
infoText: '#6d6d72',
|
||||||
tintColor: '#1e9bfe',
|
tintColor: '#1e9bfe',
|
||||||
auxiliaryTintColor: '#cdcdcd',
|
auxiliaryTintColor: '#cdcdcd',
|
||||||
actionTintColor: '#1ea1fe',
|
actionTintColor: '#1e9bfe',
|
||||||
separatorColor: '#272728',
|
separatorColor: '#272728',
|
||||||
navbarBackground: '#0d0d0d',
|
navbarBackground: '#0d0d0d',
|
||||||
headerBorder: '#323232',
|
headerBorder: '#323232',
|
||||||
|
|
|
@ -125,6 +125,9 @@ export default {
|
||||||
uniqueID: {
|
uniqueID: {
|
||||||
type: 'valueAsString'
|
type: 'valueAsString'
|
||||||
},
|
},
|
||||||
|
UI_Allow_room_names_with_special_chars: {
|
||||||
|
type: 'valueAsBoolean'
|
||||||
|
},
|
||||||
UI_Use_Real_Name: {
|
UI_Use_Real_Name: {
|
||||||
type: 'valueAsBoolean'
|
type: 'valueAsBoolean'
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,12 +2,13 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
|
import Touchable from 'react-native-platform-touchable';
|
||||||
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||||
import Touch from '../utils/touch';
|
|
||||||
import { avatarURL } from '../utils/avatar';
|
import { avatarURL } from '../utils/avatar';
|
||||||
|
|
||||||
const Avatar = React.memo(({
|
const Avatar = React.memo(({
|
||||||
text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token, onPress, theme
|
text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token, onPress
|
||||||
}) => {
|
}) => {
|
||||||
const avatarStyle = {
|
const avatarStyle = {
|
||||||
width: size,
|
width: size,
|
||||||
|
@ -36,9 +37,9 @@ const Avatar = React.memo(({
|
||||||
|
|
||||||
if (onPress) {
|
if (onPress) {
|
||||||
image = (
|
image = (
|
||||||
<Touch onPress={onPress} theme={theme}>
|
<Touchable onPress={onPress}>
|
||||||
{image}
|
{image}
|
||||||
</Touch>
|
</Touchable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +62,6 @@ Avatar.propTypes = {
|
||||||
children: PropTypes.object,
|
children: PropTypes.object,
|
||||||
userId: PropTypes.string,
|
userId: PropTypes.string,
|
||||||
token: PropTypes.string,
|
token: PropTypes.string,
|
||||||
theme: PropTypes.string,
|
|
||||||
onPress: PropTypes.func
|
onPress: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,10 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const Check = React.memo(({ theme }) => <CustomIcon style={styles.icon} color={themes[theme].tintColor} size={22} name='check' />);
|
const Check = React.memo(({ theme, style }) => <CustomIcon style={[styles.icon, style]} color={themes[theme].tintColor} size={22} name='check' />);
|
||||||
|
|
||||||
Check.propTypes = {
|
Check.propTypes = {
|
||||||
|
style: PropTypes.object,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ const Button = React.memo(({
|
||||||
onPress, ...props
|
onPress, ...props
|
||||||
}) => (
|
}) => (
|
||||||
<Touch
|
<Touch
|
||||||
onPress={onPress}
|
onPress={() => onPress(props.title)}
|
||||||
style={{ backgroundColor: themes[props.theme].backgroundColor }}
|
style={{ backgroundColor: themes[props.theme].backgroundColor }}
|
||||||
enabled={!props.disabled}
|
enabled={!props.disabled}
|
||||||
theme={props.theme}
|
theme={props.theme}
|
||||||
|
@ -89,6 +89,7 @@ Content.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
Button.propTypes = {
|
Button.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
onPress: PropTypes.func,
|
onPress: PropTypes.func,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
|
|
|
@ -64,7 +64,7 @@ const MentionItem = ({
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
<Text style={[styles.slash, { backgroundColor: themes[theme].borderColor, color: themes[theme].tintColor }]}>/</Text>
|
<Text style={[styles.slash, { backgroundColor: themes[theme].borderColor, color: themes[theme].tintColor }]}>/</Text>
|
||||||
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{ item.command}</Text>
|
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.id}</Text>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,8 @@ export default class extends React.PureComponent {
|
||||||
SampleRate: 22050,
|
SampleRate: 22050,
|
||||||
Channels: 1,
|
Channels: 1,
|
||||||
AudioQuality: 'Low',
|
AudioQuality: 'Low',
|
||||||
AudioEncoding: 'aac'
|
AudioEncoding: 'aac',
|
||||||
|
OutputFormat: 'aac_adts'
|
||||||
});
|
});
|
||||||
|
|
||||||
AudioRecorder.onProgress = (data) => {
|
AudioRecorder.onProgress = (data) => {
|
||||||
|
|
|
@ -48,6 +48,7 @@ const TwoFactor = React.memo(({ theme, split }) => {
|
||||||
|
|
||||||
useDeepCompareEffect(() => {
|
useDeepCompareEffect(() => {
|
||||||
if (!_.isEmpty(data)) {
|
if (!_.isEmpty(data)) {
|
||||||
|
setCode('');
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
@ -94,7 +95,7 @@ const TwoFactor = React.memo(({ theme, split }) => {
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={[styles.content, split && [sharedStyles.modal, sharedStyles.modalFormSheet], { backgroundColor: themes[theme].backgroundColor }]}>
|
<View style={[styles.content, split && [sharedStyles.modal, sharedStyles.modalFormSheet], { backgroundColor: themes[theme].backgroundColor }]}>
|
||||||
<Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text>
|
<Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text>
|
||||||
<Text style={[styles.subtitle, { color }]}>{I18n.t(method?.text)}</Text>
|
{method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null}
|
||||||
<TextInput
|
<TextInput
|
||||||
value={code}
|
value={code}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
|
|
@ -15,11 +15,12 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
paddingBottom: 8,
|
||||||
...sharedStyles.textBold
|
...sharedStyles.textBold
|
||||||
},
|
},
|
||||||
subtitle: {
|
subtitle: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
paddingVertical: 8,
|
paddingBottom: 8,
|
||||||
...sharedStyles.textRegular,
|
...sharedStyles.textRegular,
|
||||||
...sharedStyles.textAlignCenter
|
...sharedStyles.textAlignCenter
|
||||||
},
|
},
|
||||||
|
|
|
@ -43,14 +43,14 @@ export const MultiSelect = React.memo(({
|
||||||
inputStyle,
|
inputStyle,
|
||||||
theme
|
theme
|
||||||
}) => {
|
}) => {
|
||||||
const [selected, select] = useState(values || []);
|
const [selected, select] = useState(Array.isArray(values) ? values : []);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [search, onSearchChange] = useState('');
|
const [search, onSearchChange] = useState('');
|
||||||
const [currentValue, setCurrentValue] = useState('');
|
const [currentValue, setCurrentValue] = useState('');
|
||||||
const [showContent, setShowContent] = useState(false);
|
const [showContent, setShowContent] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (values) {
|
if (Array.isArray(values)) {
|
||||||
select(values);
|
select(values);
|
||||||
}
|
}
|
||||||
}, [values]);
|
}, [values]);
|
||||||
|
|
|
@ -8,7 +8,7 @@ import Video from './Video';
|
||||||
import Reply from './Reply';
|
import Reply from './Reply';
|
||||||
|
|
||||||
const Attachments = React.memo(({
|
const Attachments = React.memo(({
|
||||||
attachments, timeFormat, user, baseUrl, showAttachment, getCustomEmoji, theme
|
attachments, timeFormat, showAttachment, getCustomEmoji, theme
|
||||||
}) => {
|
}) => {
|
||||||
if (!attachments || attachments.length === 0) {
|
if (!attachments || attachments.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -16,25 +16,23 @@ const Attachments = React.memo(({
|
||||||
|
|
||||||
return attachments.map((file, index) => {
|
return attachments.map((file, index) => {
|
||||||
if (file.image_url) {
|
if (file.image_url) {
|
||||||
return <Image key={file.image_url} file={file} user={user} baseUrl={baseUrl} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
return <Image key={file.image_url} file={file} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
||||||
}
|
}
|
||||||
if (file.audio_url) {
|
if (file.audio_url) {
|
||||||
return <Audio key={file.audio_url} file={file} user={user} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
return <Audio key={file.audio_url} file={file} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
||||||
}
|
}
|
||||||
if (file.video_url) {
|
if (file.video_url) {
|
||||||
return <Video key={file.video_url} file={file} user={user} baseUrl={baseUrl} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
return <Video key={file.video_url} file={file} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} user={user} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
||||||
});
|
});
|
||||||
}, (prevProps, nextProps) => isEqual(prevProps.attachments, nextProps.attachments) && prevProps.theme === nextProps.theme);
|
}, (prevProps, nextProps) => isEqual(prevProps.attachments, nextProps.attachments) && prevProps.theme === nextProps.theme);
|
||||||
|
|
||||||
Attachments.propTypes = {
|
Attachments.propTypes = {
|
||||||
attachments: PropTypes.array,
|
attachments: PropTypes.array,
|
||||||
timeFormat: PropTypes.string,
|
timeFormat: PropTypes.string,
|
||||||
user: PropTypes.object,
|
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
showAttachment: PropTypes.func,
|
showAttachment: PropTypes.func,
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
|
|
|
@ -3,18 +3,30 @@ import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
View, StyleSheet, Text, Easing, Dimensions
|
View, StyleSheet, Text, Easing, Dimensions
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import Video from 'react-native-video';
|
import { Audio } from 'expo-av';
|
||||||
import Slider from '@react-native-community/slider';
|
import Slider from '@react-native-community/slider';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
|
||||||
|
|
||||||
|
import Touchable from './Touchable';
|
||||||
import Markdown from '../markdown';
|
import Markdown from '../markdown';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { isAndroid, isIOS } from '../../utils/deviceInfo';
|
import { isAndroid, isIOS } from '../../utils/deviceInfo';
|
||||||
import { withSplit } from '../../split';
|
import { withSplit } from '../../split';
|
||||||
|
import MessageContext from './Context';
|
||||||
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
|
|
||||||
|
const mode = {
|
||||||
|
allowsRecordingIOS: false,
|
||||||
|
playsInSilentModeIOS: true,
|
||||||
|
staysActiveInBackground: false,
|
||||||
|
shouldDuckAndroid: true,
|
||||||
|
playThroughEarpieceAndroid: false,
|
||||||
|
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
|
||||||
|
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX
|
||||||
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
audioContainer: {
|
audioContainer: {
|
||||||
|
@ -31,6 +43,9 @@ const styles = StyleSheet.create({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'transparent'
|
backgroundColor: 'transparent'
|
||||||
},
|
},
|
||||||
|
audioLoading: {
|
||||||
|
marginHorizontal: 8
|
||||||
|
},
|
||||||
slider: {
|
slider: {
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
|
@ -51,29 +66,36 @@ const sliderAnimationConfig = {
|
||||||
delay: 0
|
delay: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
const Button = React.memo(({ paused, onPress, theme }) => (
|
const Button = React.memo(({
|
||||||
|
loading, paused, onPress, theme
|
||||||
|
}) => (
|
||||||
<Touchable
|
<Touchable
|
||||||
style={styles.playPauseButton}
|
style={styles.playPauseButton}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
hitSlop={BUTTON_HIT_SLOP}
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
background={Touchable.SelectableBackgroundBorderless()}
|
background={Touchable.SelectableBackgroundBorderless()}
|
||||||
>
|
>
|
||||||
<CustomIcon name={paused ? 'play' : 'pause'} size={36} color={themes[theme].tintColor} />
|
{
|
||||||
|
loading
|
||||||
|
? <ActivityIndicator style={[styles.playPauseButton, styles.audioLoading]} theme={theme} />
|
||||||
|
: <CustomIcon name={paused ? 'play' : 'pause'} size={36} color={themes[theme].tintColor} />
|
||||||
|
}
|
||||||
</Touchable>
|
</Touchable>
|
||||||
));
|
));
|
||||||
|
|
||||||
Button.propTypes = {
|
Button.propTypes = {
|
||||||
|
loading: PropTypes.bool,
|
||||||
paused: PropTypes.bool,
|
paused: PropTypes.bool,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
onPress: PropTypes.func
|
onPress: PropTypes.func
|
||||||
};
|
};
|
||||||
Button.displayName = 'MessageAudioButton';
|
Button.displayName = 'MessageAudioButton';
|
||||||
|
|
||||||
class Audio extends React.Component {
|
class MessageAudio extends React.Component {
|
||||||
|
static contextType = MessageContext;
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
file: PropTypes.object.isRequired,
|
file: PropTypes.object.isRequired,
|
||||||
baseUrl: PropTypes.string.isRequired,
|
|
||||||
user: PropTypes.object.isRequired,
|
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
split: PropTypes.bool,
|
split: PropTypes.bool,
|
||||||
getCustomEmoji: PropTypes.func
|
getCustomEmoji: PropTypes.func
|
||||||
|
@ -81,18 +103,34 @@ class Audio extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const { baseUrl, file, user } = props;
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
loading: false,
|
||||||
currentTime: 0,
|
currentTime: 0,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
paused: true,
|
paused: true
|
||||||
uri: `${ baseUrl }${ file.audio_url }?rc_uid=${ user.id }&rc_token=${ user.token }`
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.sound = new Audio.Sound();
|
||||||
|
this.sound.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
const { file } = this.props;
|
||||||
|
const { baseUrl, user } = this.context;
|
||||||
|
|
||||||
|
this.setState({ loading: true });
|
||||||
|
try {
|
||||||
|
await Audio.setAudioModeAsync(mode);
|
||||||
|
await this.sound.loadAsync({ uri: `${ baseUrl }${ file.audio_url }?rc_uid=${ user.id }&rc_token=${ user.token }` });
|
||||||
|
} catch {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
this.setState({ loading: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const {
|
const {
|
||||||
currentTime, duration, paused, uri
|
currentTime, duration, paused, loading
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { file, split, theme } = this.props;
|
const { file, split, theme } = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
|
@ -107,58 +145,99 @@ class Audio extends React.Component {
|
||||||
if (nextState.paused !== paused) {
|
if (nextState.paused !== paused) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (nextState.uri !== uri) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextProps.file, file)) {
|
if (!equal(nextProps.file, file)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (nextProps.split !== split) {
|
if (nextProps.split !== split) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (nextState.loading !== loading) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async componentWillUnmount() {
|
||||||
|
try {
|
||||||
|
await this.sound.stopAsync();
|
||||||
|
} catch {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPlaybackStatusUpdate = (status) => {
|
||||||
|
if (status) {
|
||||||
|
this.onLoad(status);
|
||||||
|
this.onProgress(status);
|
||||||
|
this.onEnd(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onLoad = (data) => {
|
onLoad = (data) => {
|
||||||
this.setState({ duration: data.duration > 0 ? data.duration : 0 });
|
const duration = data.durationMillis / 1000;
|
||||||
|
this.setState({ duration: duration > 0 ? duration : 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgress = (data) => {
|
onProgress = (data) => {
|
||||||
const { duration } = this.state;
|
const { duration } = this.state;
|
||||||
if (data.currentTime <= duration) {
|
const currentTime = data.positionMillis / 1000;
|
||||||
this.setState({ currentTime: data.currentTime });
|
if (currentTime <= duration) {
|
||||||
|
this.setState({ currentTime });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnd = () => {
|
onEnd = async(data) => {
|
||||||
this.setState({ paused: true, currentTime: 0 });
|
if (data.didJustFinish) {
|
||||||
requestAnimationFrame(() => {
|
try {
|
||||||
this.player.seek(0);
|
await this.sound.stopAsync();
|
||||||
});
|
this.setState({ paused: true, currentTime: 0 });
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get duration() {
|
get duration() {
|
||||||
const { duration } = this.state;
|
const { currentTime, duration } = this.state;
|
||||||
return formatTime(duration);
|
return formatTime(currentTime || duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
setRef = ref => this.player = ref;
|
|
||||||
|
|
||||||
togglePlayPause = () => {
|
togglePlayPause = () => {
|
||||||
const { paused } = this.state;
|
const { paused } = this.state;
|
||||||
this.setState({ paused: !paused });
|
this.setState({ paused: !paused }, this.playPause);
|
||||||
}
|
}
|
||||||
|
|
||||||
onValueChange = value => this.setState({ currentTime: value });
|
playPause = async() => {
|
||||||
|
const { paused } = this.state;
|
||||||
|
try {
|
||||||
|
if (paused) {
|
||||||
|
await this.sound.pauseAsync();
|
||||||
|
} else {
|
||||||
|
await this.sound.playAsync();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onValueChange = async(value) => {
|
||||||
|
try {
|
||||||
|
this.setState({ currentTime: value });
|
||||||
|
await this.sound.setPositionAsync(value * 1000);
|
||||||
|
} catch {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
uri, paused, currentTime, duration
|
loading, paused, currentTime, duration
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
user, baseUrl, file, getCustomEmoji, split, theme
|
file, getCustomEmoji, split, theme
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { description } = file;
|
const { description } = file;
|
||||||
|
const { baseUrl, user } = this.context;
|
||||||
|
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -173,17 +252,7 @@ class Audio extends React.Component {
|
||||||
split && sharedStyles.tabletContent
|
split && sharedStyles.tabletContent
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Video
|
<Button loading={loading} paused={paused} onPress={this.togglePlayPause} theme={theme} />
|
||||||
ref={this.setRef}
|
|
||||||
source={{ uri }}
|
|
||||||
onLoad={this.onLoad}
|
|
||||||
onProgress={this.onProgress}
|
|
||||||
onEnd={this.onEnd}
|
|
||||||
paused={paused}
|
|
||||||
repeat={false}
|
|
||||||
ignoreSilentSwitch='ignore'
|
|
||||||
/>
|
|
||||||
<Button paused={paused} onPress={this.togglePlayPause} theme={theme} />
|
|
||||||
<Slider
|
<Slider
|
||||||
style={styles.slider}
|
style={styles.slider}
|
||||||
value={currentTime}
|
value={currentTime}
|
||||||
|
@ -205,4 +274,4 @@ class Audio extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withSplit(Audio);
|
export default withSplit(MessageAudio);
|
||||||
|
|
|
@ -6,8 +6,7 @@ const Blocks = React.memo(({
|
||||||
blocks, id: mid, rid, blockAction
|
blocks, id: mid, rid, blockAction
|
||||||
}) => {
|
}) => {
|
||||||
if (blocks && blocks.length > 0) {
|
if (blocks && blocks.length > 0) {
|
||||||
const [, secondBlock] = blocks;
|
const appId = blocks[0]?.appId || '';
|
||||||
const { appId = '' } = secondBlock;
|
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
messageBlockWithContext({
|
messageBlockWithContext({
|
||||||
action: async({ actionId, value, blockId }) => {
|
action: async({ actionId, value, blockId }) => {
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { View, Text } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import Touchable from './Touchable';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { BUTTON_HIT_SLOP } from './utils';
|
import { BUTTON_HIT_SLOP } from './utils';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
const Broadcast = React.memo(({
|
const Broadcast = React.memo(({
|
||||||
author, user, broadcast, replyBroadcast, theme
|
author, broadcast, theme
|
||||||
}) => {
|
}) => {
|
||||||
|
const { user, replyBroadcast } = useContext(MessageContext);
|
||||||
const isOwn = author._id === user.id;
|
const isOwn = author._id === user.id;
|
||||||
if (broadcast && !isOwn) {
|
if (broadcast && !isOwn) {
|
||||||
return (
|
return (
|
||||||
|
@ -36,10 +38,8 @@ const Broadcast = React.memo(({
|
||||||
|
|
||||||
Broadcast.propTypes = {
|
Broadcast.propTypes = {
|
||||||
author: PropTypes.object,
|
author: PropTypes.object,
|
||||||
user: PropTypes.object,
|
|
||||||
broadcast: PropTypes.bool,
|
broadcast: PropTypes.bool,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string
|
||||||
replyBroadcast: PropTypes.func
|
|
||||||
};
|
};
|
||||||
Broadcast.displayName = 'MessageBroadcast';
|
Broadcast.displayName = 'MessageBroadcast';
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import Touchable from './Touchable';
|
||||||
import { formatLastMessage, BUTTON_HIT_SLOP } from './utils';
|
import { formatLastMessage, BUTTON_HIT_SLOP } from './utils';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
|
@ -8,6 +8,7 @@ import styles from './styles';
|
||||||
import Markdown from '../markdown';
|
import Markdown from '../markdown';
|
||||||
import { getInfoMessage } from './utils';
|
import { getInfoMessage } from './utils';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
const Content = React.memo((props) => {
|
const Content = React.memo((props) => {
|
||||||
if (props.isInfo) {
|
if (props.isInfo) {
|
||||||
|
@ -26,12 +27,13 @@ const Content = React.memo((props) => {
|
||||||
if (props.tmid && !props.msg) {
|
if (props.tmid && !props.msg) {
|
||||||
content = <Text style={[styles.text, { color: themes[props.theme].bodyText }]}>{I18n.t('Sent_an_attachment')}</Text>;
|
content = <Text style={[styles.text, { color: themes[props.theme].bodyText }]}>{I18n.t('Sent_an_attachment')}</Text>;
|
||||||
} else {
|
} else {
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
content = (
|
content = (
|
||||||
<Markdown
|
<Markdown
|
||||||
msg={props.msg}
|
msg={props.msg}
|
||||||
baseUrl={props.baseUrl}
|
baseUrl={baseUrl}
|
||||||
getCustomEmoji={props.getCustomEmoji}
|
getCustomEmoji={props.getCustomEmoji}
|
||||||
username={props.user.username}
|
username={user.username}
|
||||||
isEdited={props.isEdited}
|
isEdited={props.isEdited}
|
||||||
numberOfLines={(props.tmid && !props.isThreadRoom) ? 1 : 0}
|
numberOfLines={(props.tmid && !props.isThreadRoom) ? 1 : 0}
|
||||||
preview={props.tmid && !props.isThreadRoom}
|
preview={props.tmid && !props.isThreadRoom}
|
||||||
|
@ -77,8 +79,6 @@ Content.propTypes = {
|
||||||
msg: PropTypes.string,
|
msg: PropTypes.string,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
isEdited: PropTypes.bool,
|
isEdited: PropTypes.bool,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
user: PropTypes.object,
|
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||||
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const MessageContext = React.createContext();
|
||||||
|
export default MessageContext;
|
|
@ -1,20 +1,22 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { View, Text } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import Touchable from './Touchable';
|
||||||
import { formatLastMessage, formatMessageCount, BUTTON_HIT_SLOP } from './utils';
|
import { formatLastMessage, formatMessageCount, BUTTON_HIT_SLOP } from './utils';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import { DISCUSSION } from './constants';
|
import { DISCUSSION } from './constants';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
const Discussion = React.memo(({
|
const Discussion = React.memo(({
|
||||||
msg, dcount, dlm, onDiscussionPress, theme
|
msg, dcount, dlm, theme
|
||||||
}) => {
|
}) => {
|
||||||
const time = formatLastMessage(dlm);
|
const time = formatLastMessage(dlm);
|
||||||
const buttonText = formatMessageCount(dcount, DISCUSSION);
|
const buttonText = formatMessageCount(dcount, DISCUSSION);
|
||||||
|
const { onDiscussionPress } = useContext(MessageContext);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Text style={[styles.startedDiscussion, { color: themes[theme].auxiliaryText }]}>{I18n.t('Started_discussion')}</Text>
|
<Text style={[styles.startedDiscussion, { color: themes[theme].auxiliaryText }]}>{I18n.t('Started_discussion')}</Text>
|
||||||
|
@ -55,8 +57,7 @@ Discussion.propTypes = {
|
||||||
msg: PropTypes.string,
|
msg: PropTypes.string,
|
||||||
dcount: PropTypes.number,
|
dcount: PropTypes.number,
|
||||||
dlm: PropTypes.string,
|
dlm: PropTypes.string,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string
|
||||||
onDiscussionPress: PropTypes.func
|
|
||||||
};
|
};
|
||||||
Discussion.displayName = 'MessageDiscussion';
|
Discussion.displayName = 'MessageDiscussion';
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
|
|
||||||
const Emoji = React.memo(({
|
const Emoji = React.memo(({
|
||||||
content, standardEmojiStyle, customEmojiStyle, baseUrl, getCustomEmoji
|
content, baseUrl, standardEmojiStyle, customEmojiStyle, getCustomEmoji
|
||||||
}) => {
|
}) => {
|
||||||
const parsedContent = content.replace(/^:|:$/g, '');
|
const parsedContent = content.replace(/^:|:$/g, '');
|
||||||
const emoji = getCustomEmoji(parsedContent);
|
const emoji = getCustomEmoji(parsedContent);
|
||||||
|
@ -18,9 +18,9 @@ const Emoji = React.memo(({
|
||||||
|
|
||||||
Emoji.propTypes = {
|
Emoji.propTypes = {
|
||||||
content: PropTypes.string,
|
content: PropTypes.string,
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
standardEmojiStyle: PropTypes.object,
|
standardEmojiStyle: PropTypes.object,
|
||||||
customEmojiStyle: PropTypes.object,
|
customEmojiStyle: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
getCustomEmoji: PropTypes.func
|
getCustomEmoji: PropTypes.func
|
||||||
};
|
};
|
||||||
Emoji.displayName = 'MessageEmoji';
|
Emoji.displayName = 'MessageEmoji';
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
|
||||||
import { createImageProgress } from 'react-native-image-progress';
|
import { createImageProgress } from 'react-native-image-progress';
|
||||||
import * as Progress from 'react-native-progress';
|
import * as Progress from 'react-native-progress';
|
||||||
|
|
||||||
|
import Touchable from './Touchable';
|
||||||
import Markdown from '../markdown';
|
import Markdown from '../markdown';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { formatAttachmentUrl } from '../../lib/utils';
|
import { formatAttachmentUrl } from '../../lib/utils';
|
||||||
import { withSplit } from '../../split';
|
import { withSplit } from '../../split';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
const ImageProgress = createImageProgress(FastImage);
|
const ImageProgress = createImageProgress(FastImage);
|
||||||
|
|
||||||
|
@ -41,8 +42,9 @@ export const MessageImage = React.memo(({ img, theme }) => (
|
||||||
));
|
));
|
||||||
|
|
||||||
const ImageContainer = React.memo(({
|
const ImageContainer = React.memo(({
|
||||||
file, imageUrl, baseUrl, user, showAttachment, getCustomEmoji, split, theme
|
file, imageUrl, showAttachment, getCustomEmoji, split, theme
|
||||||
}) => {
|
}) => {
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
|
const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
|
||||||
if (!img) {
|
if (!img) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -71,8 +73,6 @@ const ImageContainer = React.memo(({
|
||||||
ImageContainer.propTypes = {
|
ImageContainer.propTypes = {
|
||||||
file: PropTypes.object,
|
file: PropTypes.object,
|
||||||
imageUrl: PropTypes.string,
|
imageUrl: PropTypes.string,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
user: PropTypes.object,
|
|
||||||
showAttachment: PropTypes.func,
|
showAttachment: PropTypes.func,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
import User from './User';
|
import User from './User';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import RepliedThread from './RepliedThread';
|
import RepliedThread from './RepliedThread';
|
||||||
|
@ -111,10 +113,11 @@ const MessageTouchable = React.memo((props) => {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const { onPress, onLongPress } = useContext(MessageContext);
|
||||||
return (
|
return (
|
||||||
<Touchable
|
<Touchable
|
||||||
onLongPress={props.onLongPress}
|
onLongPress={onLongPress}
|
||||||
onPress={props.onPress}
|
onPress={onPress}
|
||||||
disabled={props.isInfo || props.archived || props.isTemp}
|
disabled={props.isInfo || props.archived || props.isTemp}
|
||||||
>
|
>
|
||||||
<View>
|
<View>
|
||||||
|
@ -129,9 +132,7 @@ MessageTouchable.propTypes = {
|
||||||
hasError: PropTypes.bool,
|
hasError: PropTypes.bool,
|
||||||
isInfo: PropTypes.bool,
|
isInfo: PropTypes.bool,
|
||||||
isTemp: PropTypes.bool,
|
isTemp: PropTypes.bool,
|
||||||
archived: PropTypes.bool,
|
archived: PropTypes.bool
|
||||||
onLongPress: PropTypes.func,
|
|
||||||
onPress: PropTypes.func
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Message.propTypes = {
|
Message.propTypes = {
|
||||||
|
@ -143,7 +144,6 @@ Message.propTypes = {
|
||||||
hasError: PropTypes.bool,
|
hasError: PropTypes.bool,
|
||||||
style: PropTypes.any,
|
style: PropTypes.any,
|
||||||
onLongPress: PropTypes.func,
|
onLongPress: PropTypes.func,
|
||||||
onPress: PropTypes.func,
|
|
||||||
isReadReceiptEnabled: PropTypes.bool,
|
isReadReceiptEnabled: PropTypes.bool,
|
||||||
unread: PropTypes.bool,
|
unread: PropTypes.bool,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
|
|
|
@ -1,34 +1,31 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { TouchableOpacity } from 'react-native';
|
|
||||||
|
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
const MessageAvatar = React.memo(({
|
const MessageAvatar = React.memo(({
|
||||||
isHeader, avatar, author, baseUrl, user, small, navToRoomInfo
|
isHeader, avatar, author, small, navToRoomInfo
|
||||||
}) => {
|
}) => {
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
if (isHeader && author) {
|
if (isHeader && author) {
|
||||||
const navParam = {
|
const navParam = {
|
||||||
t: 'd',
|
t: 'd',
|
||||||
rid: author._id
|
rid: author._id
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<Avatar
|
||||||
onPress={() => navToRoomInfo(navParam)}
|
style={small ? styles.avatarSmall : styles.avatar}
|
||||||
disabled={author._id === user.id}
|
text={avatar ? '' : author.username}
|
||||||
>
|
size={small ? 20 : 36}
|
||||||
<Avatar
|
borderRadius={small ? 2 : 4}
|
||||||
style={small ? styles.avatarSmall : styles.avatar}
|
onPress={author._id === user.id ? undefined : () => navToRoomInfo(navParam)}
|
||||||
text={avatar ? '' : author.username}
|
avatar={avatar}
|
||||||
size={small ? 20 : 36}
|
baseUrl={baseUrl}
|
||||||
borderRadius={small ? 2 : 4}
|
userId={user.id}
|
||||||
avatar={avatar}
|
token={user.token}
|
||||||
baseUrl={baseUrl}
|
/>
|
||||||
userId={user.id}
|
|
||||||
token={user.token}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -38,8 +35,6 @@ MessageAvatar.propTypes = {
|
||||||
isHeader: PropTypes.bool,
|
isHeader: PropTypes.bool,
|
||||||
avatar: PropTypes.string,
|
avatar: PropTypes.string,
|
||||||
author: PropTypes.obj,
|
author: PropTypes.obj,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
user: PropTypes.obj,
|
|
||||||
small: PropTypes.bool,
|
small: PropTypes.bool,
|
||||||
navToRoomInfo: PropTypes.func
|
navToRoomInfo: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import Touchable from './Touchable';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { BUTTON_HIT_SLOP } from './utils';
|
import { BUTTON_HIT_SLOP } from './utils';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
const MessageError = React.memo(({ hasError, onErrorPress, theme }) => {
|
const MessageError = React.memo(({ hasError, theme }) => {
|
||||||
if (!hasError) {
|
if (!hasError) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const { onErrorPress } = useContext(MessageContext);
|
||||||
return (
|
return (
|
||||||
<Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}>
|
<Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}>
|
||||||
<CustomIcon name='warning' color={themes[theme].dangerColor} size={18} />
|
<CustomIcon name='warning' color={themes[theme].dangerColor} size={18} />
|
||||||
|
@ -20,7 +22,6 @@ const MessageError = React.memo(({ hasError, onErrorPress, theme }) => {
|
||||||
|
|
||||||
MessageError.propTypes = {
|
MessageError.propTypes = {
|
||||||
hasError: PropTypes.bool,
|
hasError: PropTypes.bool,
|
||||||
onErrorPress: PropTypes.func,
|
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
MessageError.displayName = 'MessageError';
|
MessageError.displayName = 'MessageError';
|
||||||
|
|
|
@ -1,33 +1,40 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { View, Text } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import Touchable from './Touchable';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Emoji from './Emoji';
|
import Emoji from './Emoji';
|
||||||
import { BUTTON_HIT_SLOP } from './utils';
|
import { BUTTON_HIT_SLOP } from './utils';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
const AddReaction = React.memo(({ reactionInit, theme }) => (
|
const AddReaction = React.memo(({ theme }) => {
|
||||||
<Touchable
|
const { reactionInit } = useContext(MessageContext);
|
||||||
onPress={reactionInit}
|
return (
|
||||||
key='message-add-reaction'
|
<Touchable
|
||||||
testID='message-add-reaction'
|
onPress={reactionInit}
|
||||||
style={[styles.reactionButton, { backgroundColor: themes[theme].backgroundColor }]}
|
key='message-add-reaction'
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
testID='message-add-reaction'
|
||||||
hitSlop={BUTTON_HIT_SLOP}
|
style={[styles.reactionButton, { backgroundColor: themes[theme].backgroundColor }]}
|
||||||
>
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
<View style={[styles.reactionContainer, { borderColor: themes[theme].borderColor }]}>
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
<CustomIcon name='add-reaction' size={21} color={themes[theme].tintColor} />
|
>
|
||||||
</View>
|
<View style={[styles.reactionContainer, { borderColor: themes[theme].borderColor }]}>
|
||||||
</Touchable>
|
<CustomIcon name='add-reaction' size={21} color={themes[theme].tintColor} />
|
||||||
));
|
</View>
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const Reaction = React.memo(({
|
const Reaction = React.memo(({
|
||||||
reaction, user, onReactionLongPress, onReactionPress, baseUrl, getCustomEmoji, theme
|
reaction, getCustomEmoji, theme
|
||||||
}) => {
|
}) => {
|
||||||
|
const {
|
||||||
|
onReactionPress, onReactionLongPress, baseUrl, user
|
||||||
|
} = useContext(MessageContext);
|
||||||
const reacted = reaction.usernames.findIndex(item => item === user.username) !== -1;
|
const reacted = reaction.usernames.findIndex(item => item === user.username) !== -1;
|
||||||
return (
|
return (
|
||||||
<Touchable
|
<Touchable
|
||||||
|
@ -54,7 +61,7 @@ const Reaction = React.memo(({
|
||||||
});
|
});
|
||||||
|
|
||||||
const Reactions = React.memo(({
|
const Reactions = React.memo(({
|
||||||
reactions, user, baseUrl, onReactionPress, reactionInit, onReactionLongPress, getCustomEmoji, theme
|
reactions, getCustomEmoji, theme
|
||||||
}) => {
|
}) => {
|
||||||
if (!Array.isArray(reactions) || reactions.length === 0) {
|
if (!Array.isArray(reactions) || reactions.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -65,25 +72,17 @@ const Reactions = React.memo(({
|
||||||
<Reaction
|
<Reaction
|
||||||
key={reaction.emoji}
|
key={reaction.emoji}
|
||||||
reaction={reaction}
|
reaction={reaction}
|
||||||
user={user}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
onReactionLongPress={onReactionLongPress}
|
|
||||||
onReactionPress={onReactionPress}
|
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<AddReaction reactionInit={reactionInit} theme={theme} />
|
<AddReaction theme={theme} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Reaction.propTypes = {
|
Reaction.propTypes = {
|
||||||
reaction: PropTypes.object,
|
reaction: PropTypes.object,
|
||||||
user: PropTypes.object,
|
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
onReactionPress: PropTypes.func,
|
|
||||||
onReactionLongPress: PropTypes.func,
|
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
@ -91,18 +90,12 @@ Reaction.displayName = 'MessageReaction';
|
||||||
|
|
||||||
Reactions.propTypes = {
|
Reactions.propTypes = {
|
||||||
reactions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
reactions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||||
user: PropTypes.object,
|
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
onReactionPress: PropTypes.func,
|
|
||||||
reactionInit: PropTypes.func,
|
|
||||||
onReactionLongPress: PropTypes.func,
|
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
Reactions.displayName = 'MessageReactions';
|
Reactions.displayName = 'MessageReactions';
|
||||||
|
|
||||||
AddReaction.propTypes = {
|
AddReaction.propTypes = {
|
||||||
reactionInit: PropTypes.func,
|
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
AddReaction.displayName = 'MessageAddReaction';
|
AddReaction.displayName = 'MessageAddReaction';
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, StyleSheet } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
|
||||||
import isEqual from 'deep-equal';
|
import isEqual from 'deep-equal';
|
||||||
|
|
||||||
|
import Touchable from './Touchable';
|
||||||
import Markdown from '../markdown';
|
import Markdown from '../markdown';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { withSplit } from '../../split';
|
import { withSplit } from '../../split';
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
|
@ -79,12 +80,13 @@ const Title = React.memo(({ attachment, timeFormat, theme }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const Description = React.memo(({
|
const Description = React.memo(({
|
||||||
attachment, baseUrl, user, getCustomEmoji, theme
|
attachment, getCustomEmoji, theme
|
||||||
}) => {
|
}) => {
|
||||||
const text = attachment.text || attachment.title;
|
const text = attachment.text || attachment.title;
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
return (
|
return (
|
||||||
<Markdown
|
<Markdown
|
||||||
msg={text}
|
msg={text}
|
||||||
|
@ -124,11 +126,12 @@ const Fields = React.memo(({ attachment, theme }) => {
|
||||||
}, (prevProps, nextProps) => isEqual(prevProps.attachment.fields, nextProps.attachment.fields) && prevProps.theme === nextProps.theme);
|
}, (prevProps, nextProps) => isEqual(prevProps.attachment.fields, nextProps.attachment.fields) && prevProps.theme === nextProps.theme);
|
||||||
|
|
||||||
const Reply = React.memo(({
|
const Reply = React.memo(({
|
||||||
attachment, timeFormat, baseUrl, user, index, getCustomEmoji, split, theme
|
attachment, timeFormat, index, getCustomEmoji, split, theme
|
||||||
}) => {
|
}) => {
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
let url = attachment.title_link || attachment.author_link;
|
let url = attachment.title_link || attachment.author_link;
|
||||||
|
@ -160,8 +163,6 @@ const Reply = React.memo(({
|
||||||
<Description
|
<Description
|
||||||
attachment={attachment}
|
attachment={attachment}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
baseUrl={baseUrl}
|
|
||||||
user={user}
|
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
|
@ -174,8 +175,6 @@ const Reply = React.memo(({
|
||||||
Reply.propTypes = {
|
Reply.propTypes = {
|
||||||
attachment: PropTypes.object,
|
attachment: PropTypes.object,
|
||||||
timeFormat: PropTypes.string,
|
timeFormat: PropTypes.string,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
user: PropTypes.object,
|
|
||||||
index: PropTypes.number,
|
index: PropTypes.number,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
|
@ -192,8 +191,6 @@ Title.displayName = 'MessageReplyTitle';
|
||||||
|
|
||||||
Description.propTypes = {
|
Description.propTypes = {
|
||||||
attachment: PropTypes.object,
|
attachment: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
user: PropTypes.object,
|
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
|
const RCTouchable = React.memo(({ children, ...props }) => {
|
||||||
|
const { onLongPress } = useContext(MessageContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Touchable
|
||||||
|
onLongPress={onLongPress}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
RCTouchable.propTypes = {
|
||||||
|
children: PropTypes.node
|
||||||
|
};
|
||||||
|
RCTouchable.Ripple = (...args) => Touchable.Ripple(...args);
|
||||||
|
RCTouchable.SelectableBackgroundBorderless = () => Touchable.SelectableBackgroundBorderless();
|
||||||
|
|
||||||
|
export default RCTouchable;
|
|
@ -1,12 +1,12 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import {
|
import {
|
||||||
View, Text, StyleSheet, Clipboard
|
View, Text, StyleSheet, Clipboard
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
|
|
||||||
|
import Touchable from './Touchable';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
@ -15,6 +15,7 @@ import { withSplit } from '../../split';
|
||||||
import { LISTENER } from '../Toast';
|
import { LISTENER } from '../Toast';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../utils/events';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
|
@ -52,10 +53,11 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const UrlImage = React.memo(({ image, user, baseUrl }) => {
|
const UrlImage = React.memo(({ image }) => {
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
image = image.includes('http') ? image : `${ baseUrl }/${ image }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
image = image.includes('http') ? image : `${ baseUrl }/${ image }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
||||||
return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />;
|
return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />;
|
||||||
}, (prevProps, nextProps) => prevProps.image === nextProps.image);
|
}, (prevProps, nextProps) => prevProps.image === nextProps.image);
|
||||||
|
@ -79,7 +81,7 @@ const UrlContent = React.memo(({ title, description, theme }) => (
|
||||||
});
|
});
|
||||||
|
|
||||||
const Url = React.memo(({
|
const Url = React.memo(({
|
||||||
url, index, user, baseUrl, split, theme
|
url, index, split, theme
|
||||||
}) => {
|
}) => {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -109,7 +111,7 @@ const Url = React.memo(({
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<UrlImage image={url.image} user={user} baseUrl={baseUrl} />
|
<UrlImage image={url.image} />
|
||||||
<UrlContent title={url.title} description={url.description} theme={theme} />
|
<UrlContent title={url.title} description={url.description} theme={theme} />
|
||||||
</>
|
</>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
|
@ -117,21 +119,19 @@ const Url = React.memo(({
|
||||||
}, (oldProps, newProps) => isEqual(oldProps.url, newProps.url) && oldProps.split === newProps.split && oldProps.theme === newProps.theme);
|
}, (oldProps, newProps) => isEqual(oldProps.url, newProps.url) && oldProps.split === newProps.split && oldProps.theme === newProps.theme);
|
||||||
|
|
||||||
const Urls = React.memo(({
|
const Urls = React.memo(({
|
||||||
urls, user, baseUrl, split, theme
|
urls, split, theme
|
||||||
}) => {
|
}) => {
|
||||||
if (!urls || urls.length === 0) {
|
if (!urls || urls.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return urls.map((url, index) => (
|
return urls.map((url, index) => (
|
||||||
<Url url={url} key={url.url} index={index} user={user} baseUrl={baseUrl} split={split} theme={theme} />
|
<Url url={url} key={url.url} index={index} split={split} theme={theme} />
|
||||||
));
|
));
|
||||||
}, (oldProps, newProps) => isEqual(oldProps.urls, newProps.urls) && oldProps.split === newProps.split && oldProps.theme === newProps.theme);
|
}, (oldProps, newProps) => isEqual(oldProps.urls, newProps.urls) && oldProps.split === newProps.split && oldProps.theme === newProps.theme);
|
||||||
|
|
||||||
UrlImage.propTypes = {
|
UrlImage.propTypes = {
|
||||||
image: PropTypes.string,
|
image: PropTypes.string
|
||||||
user: PropTypes.object,
|
|
||||||
baseUrl: PropTypes.string
|
|
||||||
};
|
};
|
||||||
UrlImage.displayName = 'MessageUrlImage';
|
UrlImage.displayName = 'MessageUrlImage';
|
||||||
|
|
||||||
|
@ -145,8 +145,6 @@ UrlContent.displayName = 'MessageUrlContent';
|
||||||
Url.propTypes = {
|
Url.propTypes = {
|
||||||
url: PropTypes.object.isRequired,
|
url: PropTypes.object.isRequired,
|
||||||
index: PropTypes.number,
|
index: PropTypes.number,
|
||||||
user: PropTypes.object,
|
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
split: PropTypes.bool
|
split: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
@ -154,8 +152,6 @@ Url.displayName = 'MessageUrl';
|
||||||
|
|
||||||
Urls.propTypes = {
|
Urls.propTypes = {
|
||||||
urls: PropTypes.array,
|
urls: PropTypes.array,
|
||||||
user: PropTypes.object,
|
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
split: PropTypes.bool
|
split: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
View, Text, StyleSheet, TouchableOpacity
|
View, Text, StyleSheet, TouchableOpacity
|
||||||
|
@ -11,6 +11,7 @@ import { withTheme } from '../../theme';
|
||||||
import MessageError from './MessageError';
|
import MessageError from './MessageError';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import messageStyles from './styles';
|
import messageStyles from './styles';
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -35,13 +36,14 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
const User = React.memo(({
|
const User = React.memo(({
|
||||||
isHeader, useRealName, author, alias, ts, timeFormat, hasError, theme, navToRoomInfo, user, ...props
|
isHeader, useRealName, author, alias, ts, timeFormat, hasError, theme, navToRoomInfo, ...props
|
||||||
}) => {
|
}) => {
|
||||||
if (isHeader || hasError) {
|
if (isHeader || hasError) {
|
||||||
const navParam = {
|
const navParam = {
|
||||||
t: 'd',
|
t: 'd',
|
||||||
rid: author._id
|
rid: author._id
|
||||||
};
|
};
|
||||||
|
const { user } = useContext(MessageContext);
|
||||||
const username = (useRealName && author.name) || author.username;
|
const username = (useRealName && author.name) || author.username;
|
||||||
const aliasUsername = alias ? (<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>) : null;
|
const aliasUsername = alias ? (<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>) : null;
|
||||||
const time = moment(ts).format(timeFormat);
|
const time = moment(ts).format(timeFormat);
|
||||||
|
@ -49,15 +51,14 @@ const User = React.memo(({
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
style={styles.titleContainer}
|
||||||
onPress={() => navToRoomInfo(navParam)}
|
onPress={() => navToRoomInfo(navParam)}
|
||||||
disabled={author._id === user.id}
|
disabled={author._id === user.id}
|
||||||
>
|
>
|
||||||
<View style={styles.titleContainer}>
|
<Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}>
|
||||||
<Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}>
|
{alias || username}
|
||||||
{alias || username}
|
{aliasUsername}
|
||||||
{aliasUsername}
|
</Text>
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
|
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
|
||||||
{ hasError && <MessageError hasError={hasError} theme={theme} {...props} /> }
|
{ hasError && <MessageError hasError={hasError} theme={theme} {...props} /> }
|
||||||
|
@ -76,7 +77,6 @@ User.propTypes = {
|
||||||
ts: PropTypes.instanceOf(Date),
|
ts: PropTypes.instanceOf(Date),
|
||||||
timeFormat: PropTypes.string,
|
timeFormat: PropTypes.string,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
user: PropTypes.obj,
|
|
||||||
navToRoomInfo: PropTypes.func
|
navToRoomInfo: PropTypes.func
|
||||||
};
|
};
|
||||||
User.displayName = 'MessageUser';
|
User.displayName = 'MessageUser';
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
|
||||||
import isEqual from 'deep-equal';
|
import isEqual from 'deep-equal';
|
||||||
|
|
||||||
|
import Touchable from './Touchable';
|
||||||
import Markdown from '../markdown';
|
import Markdown from '../markdown';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import { isIOS, isTablet } from '../../utils/deviceInfo';
|
import { isIOS, isTablet } from '../../utils/deviceInfo';
|
||||||
|
@ -11,6 +11,7 @@ import { CustomIcon } from '../../lib/Icons';
|
||||||
import { formatAttachmentUrl } from '../../lib/utils';
|
import { formatAttachmentUrl } from '../../lib/utils';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import MessageContext from './Context';
|
||||||
|
|
||||||
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
|
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
|
||||||
const isTypeSupported = type => SUPPORTED_TYPES.indexOf(type) !== -1;
|
const isTypeSupported = type => SUPPORTED_TYPES.indexOf(type) !== -1;
|
||||||
|
@ -27,12 +28,12 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
const Video = React.memo(({
|
const Video = React.memo(({
|
||||||
file, baseUrl, user, showAttachment, getCustomEmoji, theme
|
file, showAttachment, getCustomEmoji, theme
|
||||||
}) => {
|
}) => {
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
if (isTypeSupported(file.video_type)) {
|
if (isTypeSupported(file.video_type)) {
|
||||||
return showAttachment(file);
|
return showAttachment(file);
|
||||||
|
@ -61,8 +62,6 @@ const Video = React.memo(({
|
||||||
|
|
||||||
Video.propTypes = {
|
Video.propTypes = {
|
||||||
file: PropTypes.object,
|
file: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
user: PropTypes.object,
|
|
||||||
showAttachment: PropTypes.func,
|
showAttachment: PropTypes.func,
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
import { KeyboardUtils } from 'react-native-keyboard-input';
|
import { KeyboardUtils } from 'react-native-keyboard-input';
|
||||||
|
|
||||||
import Message from './Message';
|
import Message from './Message';
|
||||||
|
import MessageContext from './Context';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
|
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
|
||||||
import messagesStatus from '../../constants/messagesStatus';
|
import messagesStatus from '../../constants/messagesStatus';
|
||||||
|
@ -240,63 +241,68 @@ class MessageContainer extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Message
|
<MessageContext.Provider
|
||||||
id={id}
|
value={{
|
||||||
msg={message}
|
user,
|
||||||
rid={rid}
|
baseUrl,
|
||||||
author={u}
|
onPress: this.onPress,
|
||||||
ts={ts}
|
onLongPress: this.onLongPress,
|
||||||
type={t}
|
reactionInit: this.reactionInit,
|
||||||
attachments={attachments}
|
onErrorPress: this.onErrorPress,
|
||||||
blocks={blocks}
|
replyBroadcast: this.replyBroadcast,
|
||||||
urls={urls}
|
onReactionPress: this.onReactionPress,
|
||||||
reactions={reactions}
|
onDiscussionPress: this.onDiscussionPress,
|
||||||
alias={alias}
|
onReactionLongPress: this.onReactionLongPress
|
||||||
avatar={avatar}
|
}}
|
||||||
user={user}
|
>
|
||||||
timeFormat={timeFormat}
|
<Message
|
||||||
customThreadTimeFormat={customThreadTimeFormat}
|
id={id}
|
||||||
style={style}
|
msg={message}
|
||||||
archived={archived}
|
rid={rid}
|
||||||
broadcast={broadcast}
|
author={u}
|
||||||
baseUrl={baseUrl}
|
ts={ts}
|
||||||
useRealName={useRealName}
|
type={t}
|
||||||
isReadReceiptEnabled={isReadReceiptEnabled}
|
attachments={attachments}
|
||||||
unread={unread}
|
blocks={blocks}
|
||||||
role={role}
|
urls={urls}
|
||||||
drid={drid}
|
reactions={reactions}
|
||||||
dcount={dcount}
|
alias={alias}
|
||||||
dlm={dlm}
|
avatar={avatar}
|
||||||
tmid={tmid}
|
timeFormat={timeFormat}
|
||||||
tcount={tcount}
|
customThreadTimeFormat={customThreadTimeFormat}
|
||||||
tlm={tlm}
|
style={style}
|
||||||
tmsg={tmsg}
|
archived={archived}
|
||||||
fetchThreadName={fetchThreadName}
|
broadcast={broadcast}
|
||||||
mentions={mentions}
|
useRealName={useRealName}
|
||||||
channels={channels}
|
isReadReceiptEnabled={isReadReceiptEnabled}
|
||||||
isEdited={editedBy && !!editedBy.username}
|
unread={unread}
|
||||||
isHeader={this.isHeader}
|
role={role}
|
||||||
isThreadReply={this.isThreadReply}
|
drid={drid}
|
||||||
isThreadSequential={this.isThreadSequential}
|
dcount={dcount}
|
||||||
isThreadRoom={isThreadRoom}
|
dlm={dlm}
|
||||||
isInfo={this.isInfo}
|
tmid={tmid}
|
||||||
isTemp={this.isTemp}
|
tcount={tcount}
|
||||||
hasError={this.hasError}
|
tlm={tlm}
|
||||||
onErrorPress={this.onErrorPress}
|
tmsg={tmsg}
|
||||||
onPress={this.onPress}
|
fetchThreadName={fetchThreadName}
|
||||||
onLongPress={this.onLongPress}
|
mentions={mentions}
|
||||||
onReactionLongPress={this.onReactionLongPress}
|
channels={channels}
|
||||||
onReactionPress={this.onReactionPress}
|
isEdited={editedBy && !!editedBy.username}
|
||||||
replyBroadcast={this.replyBroadcast}
|
isHeader={this.isHeader}
|
||||||
reactionInit={this.reactionInit}
|
isThreadReply={this.isThreadReply}
|
||||||
onDiscussionPress={this.onDiscussionPress}
|
isThreadSequential={this.isThreadSequential}
|
||||||
showAttachment={showAttachment}
|
isThreadRoom={isThreadRoom}
|
||||||
getCustomEmoji={getCustomEmoji}
|
isInfo={this.isInfo}
|
||||||
navToRoomInfo={navToRoomInfo}
|
isTemp={this.isTemp}
|
||||||
callJitsi={callJitsi}
|
hasError={this.hasError}
|
||||||
blockAction={blockAction}
|
showAttachment={showAttachment}
|
||||||
theme={theme}
|
getCustomEmoji={getCustomEmoji}
|
||||||
/>
|
navToRoomInfo={navToRoomInfo}
|
||||||
|
callJitsi={callJitsi}
|
||||||
|
blockAction={blockAction}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
</MessageContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ export default {
|
||||||
'error-email-domain-blacklisted': 'The email domain is blacklisted',
|
'error-email-domain-blacklisted': 'The email domain is blacklisted',
|
||||||
'error-email-send-failed': 'Error trying to send email: {{message}}',
|
'error-email-send-failed': 'Error trying to send email: {{message}}',
|
||||||
'error-save-image': 'Error while saving image',
|
'error-save-image': 'Error while saving image',
|
||||||
|
'error-save-video': 'Error while saving video',
|
||||||
'error-field-unavailable': '{{field}} is already in use :(',
|
'error-field-unavailable': '{{field}} is already in use :(',
|
||||||
'error-file-too-large': 'File is too large',
|
'error-file-too-large': 'File is too large',
|
||||||
'error-importer-not-defined': 'The importer was not defined correctly, it is missing the Import class.',
|
'error-importer-not-defined': 'The importer was not defined correctly, it is missing the Import class.',
|
||||||
|
@ -414,6 +415,7 @@ export default {
|
||||||
Select_Server: 'Select Server',
|
Select_Server: 'Select Server',
|
||||||
Select_Users: 'Select Users',
|
Select_Users: 'Select Users',
|
||||||
Select_a_Channel: 'Select a Channel',
|
Select_a_Channel: 'Select a Channel',
|
||||||
|
Select_an_option: 'Select an option',
|
||||||
Send: 'Send',
|
Send: 'Send',
|
||||||
Send_audio_message: 'Send audio message',
|
Send_audio_message: 'Send audio message',
|
||||||
Send_crash_report: 'Send crash report',
|
Send_crash_report: 'Send crash report',
|
||||||
|
|
|
@ -377,6 +377,7 @@ export default {
|
||||||
Select_Server: 'Selecionar Servidor',
|
Select_Server: 'Selecionar Servidor',
|
||||||
Select_Users: 'Selecionar Usuários',
|
Select_Users: 'Selecionar Usuários',
|
||||||
Select_a_Channel: 'Selecione um canal',
|
Select_a_Channel: 'Selecione um canal',
|
||||||
|
Select_an_option: 'Selecione uma opção',
|
||||||
Send: 'Enviar',
|
Send: 'Enviar',
|
||||||
Send_audio_message: 'Enviar mensagem de áudio',
|
Send_audio_message: 'Enviar mensagem de áudio',
|
||||||
Send_message: 'Enviar mensagem',
|
Send_message: 'Enviar mensagem',
|
||||||
|
|
10
app/index.js
10
app/index.js
|
@ -166,6 +166,9 @@ const ChatsStack = createStackNavigator({
|
||||||
NotificationPrefView: {
|
NotificationPrefView: {
|
||||||
getScreen: () => require('./views/NotificationPreferencesView').default
|
getScreen: () => require('./views/NotificationPreferencesView').default
|
||||||
},
|
},
|
||||||
|
PickerView: {
|
||||||
|
getScreen: () => require('./views/PickerView').default
|
||||||
|
},
|
||||||
...RoomRoutes
|
...RoomRoutes
|
||||||
}, {
|
}, {
|
||||||
defaultNavigationOptions: defaultHeader,
|
defaultNavigationOptions: defaultHeader,
|
||||||
|
@ -448,6 +451,9 @@ const RoomActionsStack = createStackNavigator({
|
||||||
},
|
},
|
||||||
AttachmentView: {
|
AttachmentView: {
|
||||||
getScreen: () => require('./views/AttachmentView').default
|
getScreen: () => require('./views/AttachmentView').default
|
||||||
|
},
|
||||||
|
PickerView: {
|
||||||
|
getScreen: () => require('./views/PickerView').default
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
defaultNavigationOptions: defaultHeader,
|
defaultNavigationOptions: defaultHeader,
|
||||||
|
@ -508,7 +514,7 @@ class CustomModalStack extends React.Component {
|
||||||
const pageSheetViews = ['AttachmentView'];
|
const pageSheetViews = ['AttachmentView'];
|
||||||
const pageSheet = pageSheetViews.includes(getActiveRouteName(navigation.state));
|
const pageSheet = pageSheetViews.includes(getActiveRouteName(navigation.state));
|
||||||
|
|
||||||
const androidProps = isAndroid && {
|
const androidProps = isAndroid && !pageSheet && {
|
||||||
style: { marginBottom: 0 }
|
style: { marginBottom: 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -518,7 +524,7 @@ class CustomModalStack extends React.Component {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isAndroid) {
|
if (isAndroid && !pageSheet) {
|
||||||
content = (
|
content = (
|
||||||
<ScrollView overScrollMode='never'>
|
<ScrollView overScrollMode='never'>
|
||||||
{content}
|
{content}
|
||||||
|
|
|
@ -109,7 +109,8 @@ class DB {
|
||||||
Message,
|
Message,
|
||||||
Thread,
|
Thread,
|
||||||
ThreadMessage,
|
ThreadMessage,
|
||||||
Upload
|
Upload,
|
||||||
|
Permission
|
||||||
],
|
],
|
||||||
actionsEnabled: true
|
actionsEnabled: true
|
||||||
});
|
});
|
||||||
|
|
|
@ -61,8 +61,15 @@ export default async function canOpenRoom({ rid, path }) {
|
||||||
|
|
||||||
if (rid) {
|
if (rid) {
|
||||||
try {
|
try {
|
||||||
await subsCollection.find(rid);
|
const room = await subsCollection.find(rid);
|
||||||
return { rid };
|
return {
|
||||||
|
rid,
|
||||||
|
t: room.t,
|
||||||
|
name: room.name,
|
||||||
|
fname: room.fname,
|
||||||
|
prid: room.prid,
|
||||||
|
uids: room.uids
|
||||||
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,22 @@ import fetch from '../../utils/fetch';
|
||||||
|
|
||||||
const serverInfoKeys = ['Site_Name', 'UI_Use_Real_Name', 'FileUpload_MediaTypeWhiteList', 'FileUpload_MaxFileSize'];
|
const serverInfoKeys = ['Site_Name', 'UI_Use_Real_Name', 'FileUpload_MediaTypeWhiteList', 'FileUpload_MaxFileSize'];
|
||||||
|
|
||||||
|
// these settings are used only on onboarding process
|
||||||
|
const loginSettings = [
|
||||||
|
'API_Gitlab_URL',
|
||||||
|
'CAS_enabled',
|
||||||
|
'CAS_login_url',
|
||||||
|
'Accounts_EmailVerification',
|
||||||
|
'Accounts_ManuallyApproveNewUsers',
|
||||||
|
'Accounts_ShowFormLogin',
|
||||||
|
'Site_Url',
|
||||||
|
'Accounts_RegistrationForm',
|
||||||
|
'Accounts_RegistrationForm_LinkReplacementText',
|
||||||
|
'Accounts_EmailOrUsernamePlaceholder',
|
||||||
|
'Accounts_PasswordPlaceholder',
|
||||||
|
'Accounts_PasswordReset'
|
||||||
|
];
|
||||||
|
|
||||||
const serverInfoUpdate = async(serverInfo, iconSetting) => {
|
const serverInfoUpdate = async(serverInfo, iconSetting) => {
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
const serverId = reduxStore.getState().server.server;
|
const serverId = reduxStore.getState().server.server;
|
||||||
|
@ -54,7 +70,7 @@ const serverInfoUpdate = async(serverInfo, iconSetting) => {
|
||||||
|
|
||||||
export async function getLoginSettings({ server }) {
|
export async function getLoginSettings({ server }) {
|
||||||
try {
|
try {
|
||||||
const settingsParams = JSON.stringify(['Accounts_ShowFormLogin', 'Accounts_RegistrationForm']);
|
const settingsParams = JSON.stringify(loginSettings);
|
||||||
const result = await fetch(`${ server }/api/v1/settings.public?query={"_id":{"$in":${ settingsParams }}}`).then(response => response.json());
|
const result = await fetch(`${ server }/api/v1/settings.public?query={"_id":{"$in":${ settingsParams }}}`).then(response => response.json());
|
||||||
|
|
||||||
if (result.success && result.settings.length) {
|
if (result.success && result.settings.length) {
|
||||||
|
@ -84,7 +100,7 @@ export async function setSettings() {
|
||||||
export default async function() {
|
export default async function() {
|
||||||
try {
|
try {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const settingsParams = JSON.stringify(Object.keys(settings));
|
const settingsParams = JSON.stringify(Object.keys(settings).filter(key => !loginSettings.includes(key)));
|
||||||
// RC 0.60.0
|
// RC 0.60.0
|
||||||
const result = await fetch(`${ this.sdk.client.host }/api/v1/settings.public?query={"_id":{"$in":${ settingsParams }}}`).then(response => response.json());
|
const result = await fetch(`${ this.sdk.client.host }/api/v1/settings.public?query={"_id":{"$in":${ settingsParams }}}`).then(response => response.json());
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,13 @@ import semver from 'semver';
|
||||||
|
|
||||||
import reduxStore from '../createStore';
|
import reduxStore from '../createStore';
|
||||||
import { setActiveUsers } from '../../actions/activeUsers';
|
import { setActiveUsers } from '../../actions/activeUsers';
|
||||||
|
import { setUser } from '../../actions/login';
|
||||||
|
|
||||||
export function subscribeUsersPresence() {
|
export function subscribeUsersPresence() {
|
||||||
const serverVersion = reduxStore.getState().server.version;
|
const serverVersion = reduxStore.getState().server.version;
|
||||||
|
|
||||||
// if server is lower than 1.1.0
|
// if server is lower than 1.1.0
|
||||||
if (serverVersion && semver.lt(semver.coerce(serverVersion), '1.1.0')) {
|
if (serverVersion && semver.lt(serverVersion, '1.1.0')) {
|
||||||
if (this.activeUsersSubTimeout) {
|
if (this.activeUsersSubTimeout) {
|
||||||
clearTimeout(this.activeUsersSubTimeout);
|
clearTimeout(this.activeUsersSubTimeout);
|
||||||
this.activeUsersSubTimeout = false;
|
this.activeUsersSubTimeout = false;
|
||||||
|
@ -25,35 +26,43 @@ let ids = [];
|
||||||
|
|
||||||
export default async function getUsersPresence() {
|
export default async function getUsersPresence() {
|
||||||
const serverVersion = reduxStore.getState().server.version;
|
const serverVersion = reduxStore.getState().server.version;
|
||||||
|
const { user: loggedUser } = reduxStore.getState().login;
|
||||||
|
|
||||||
// if server is greather than or equal 1.1.0
|
// if server is greather than or equal 1.1.0
|
||||||
if (serverVersion && !semver.lt(semver.coerce(serverVersion), '1.1.0')) {
|
if (serverVersion && semver.gte(serverVersion, '1.1.0')) {
|
||||||
let params = {};
|
let params = {};
|
||||||
|
|
||||||
// if server is greather than or equal 3.0.0
|
// if server is greather than or equal 3.0.0
|
||||||
if (serverVersion && !semver.lt(semver.coerce(serverVersion), '3.0.0')) {
|
if (serverVersion && semver.gte(serverVersion, '3.0.0')) {
|
||||||
// if not have any id
|
// if not have any id
|
||||||
if (!ids.length) {
|
if (!ids.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Request userPresence on demand
|
// Request userPresence on demand
|
||||||
params = { ids: ids.join(',') };
|
params = { ids: ids.join(',') };
|
||||||
ids = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RC 1.1.0
|
try {
|
||||||
const result = await this.sdk.get('users.presence', params);
|
// RC 1.1.0
|
||||||
if (result.success) {
|
const result = await this.sdk.get('users.presence', params);
|
||||||
const activeUsers = result.users.reduce((ret, item) => {
|
if (result.success) {
|
||||||
ret[item._id] = {
|
const activeUsers = result.users.reduce((ret, item) => {
|
||||||
status: item.status,
|
const { _id, status, statusText } = item;
|
||||||
statusText: item.statusText
|
|
||||||
};
|
if (loggedUser && loggedUser.id === _id) {
|
||||||
return ret;
|
reduxStore.dispatch(setUser({ status, statusText }));
|
||||||
}, {});
|
}
|
||||||
InteractionManager.runAfterInteractions(() => {
|
|
||||||
reduxStore.dispatch(setActiveUsers(activeUsers));
|
ret[_id] = { status, statusText };
|
||||||
});
|
return ret;
|
||||||
|
}, {});
|
||||||
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
reduxStore.dispatch(setActiveUsers(activeUsers));
|
||||||
|
});
|
||||||
|
ids = [];
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
|
import database from '../../database';
|
||||||
|
|
||||||
|
export default async(subscriptions = [], rooms = []) => {
|
||||||
|
try {
|
||||||
|
const db = database.active;
|
||||||
|
const subCollection = db.collections.get('subscriptions');
|
||||||
|
|
||||||
|
const roomIds = rooms.filter(r => !subscriptions.find(s => s.rid === r._id)).map(r => r._id);
|
||||||
|
let existingSubs = await subCollection.query(Q.where('rid', Q.oneOf(roomIds))).fetch();
|
||||||
|
existingSubs = existingSubs.map(s => ({
|
||||||
|
_id: s._id,
|
||||||
|
f: s.f,
|
||||||
|
t: s.t,
|
||||||
|
ts: s.ts,
|
||||||
|
ls: s.ls,
|
||||||
|
name: s.name,
|
||||||
|
fname: s.fname,
|
||||||
|
rid: s.rid,
|
||||||
|
open: s.open,
|
||||||
|
alert: s.alert,
|
||||||
|
unread: s.unread,
|
||||||
|
userMentions: s.userMentions,
|
||||||
|
roomUpdatedAt: s.roomUpdatedAt,
|
||||||
|
ro: s.ro,
|
||||||
|
lastOpen: s.lastOpen,
|
||||||
|
description: s.description,
|
||||||
|
announcement: s.announcement,
|
||||||
|
topic: s.topic,
|
||||||
|
blocked: s.blocked,
|
||||||
|
blocker: s.blocker,
|
||||||
|
reactWhenReadOnly: s.reactWhenReadOnly,
|
||||||
|
archived: s.archived,
|
||||||
|
joinCodeRequired: s.joinCodeRequired,
|
||||||
|
muted: s.muted,
|
||||||
|
broadcast: s.broadcast,
|
||||||
|
prid: s.prid,
|
||||||
|
draftMessage: s.draftMessage,
|
||||||
|
lastThreadSync: s.lastThreadSync,
|
||||||
|
jitsiTimeout: s.jitsiTimeout,
|
||||||
|
autoTranslate: s.autoTranslate,
|
||||||
|
autoTranslateLanguage: s.autoTranslateLanguage,
|
||||||
|
lastMessage: s.lastMessage,
|
||||||
|
usernames: s.usernames,
|
||||||
|
uids: s.uids
|
||||||
|
}));
|
||||||
|
subscriptions = subscriptions.concat(existingSubs);
|
||||||
|
|
||||||
|
const subsIds = subscriptions.filter(s => !rooms.find(r => s.rid === r._id)).map(s => s._id);
|
||||||
|
let existingRooms = await subCollection.query(Q.where('id', Q.oneOf(subsIds))).fetch();
|
||||||
|
existingRooms = existingRooms.map(r => ({
|
||||||
|
_updatedAt: r._updatedAt,
|
||||||
|
lastMessage: r.lastMessage,
|
||||||
|
description: r.description,
|
||||||
|
topic: r.topic,
|
||||||
|
announcement: r.announcement,
|
||||||
|
reactWhenReadOnly: r.reactWhenReadOnly,
|
||||||
|
archived: r.archived,
|
||||||
|
joinCodeRequired: r.joinCodeRequired,
|
||||||
|
jitsiTimeout: r.jitsiTimeout,
|
||||||
|
usernames: r.usernames,
|
||||||
|
uids: r.uids,
|
||||||
|
ro: r.ro,
|
||||||
|
broadcast: r.broadcast,
|
||||||
|
muted: r.muted,
|
||||||
|
sysMes: r.sysMes
|
||||||
|
}));
|
||||||
|
rooms = rooms.concat(existingRooms);
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscriptions,
|
||||||
|
rooms
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
import EJSON from 'ejson';
|
import EJSON from 'ejson';
|
||||||
|
|
||||||
import normalizeMessage from './normalizeMessage';
|
import normalizeMessage from './normalizeMessage';
|
||||||
|
import findSubscriptionsRooms from './findSubscriptionsRooms';
|
||||||
// TODO: delete and update
|
// TODO: delete and update
|
||||||
|
|
||||||
export const merge = (subscription, room) => {
|
export const merge = (subscription, room) => {
|
||||||
|
@ -46,11 +47,14 @@ export const merge = (subscription, room) => {
|
||||||
return subscription;
|
return subscription;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (subscriptions = [], rooms = []) => {
|
export default async(subscriptions = [], rooms = []) => {
|
||||||
if (subscriptions.update) {
|
if (subscriptions.update) {
|
||||||
subscriptions = subscriptions.update;
|
subscriptions = subscriptions.update;
|
||||||
rooms = rooms.update;
|
rooms = rooms.update;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
({ subscriptions, rooms } = await findSubscriptionsRooms(subscriptions, rooms));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscriptions: subscriptions.map((s) => {
|
subscriptions: subscriptions.map((s) => {
|
||||||
const index = rooms.findIndex(({ _id }) => _id === s.rid);
|
const index = rooms.findIndex(({ _id }) => _id === s.rid);
|
||||||
|
|
|
@ -96,6 +96,7 @@ export async function removeServer({ server }) {
|
||||||
export default async function logout({ server }) {
|
export default async function logout({ server }) {
|
||||||
if (this.roomsSub) {
|
if (this.roomsSub) {
|
||||||
this.roomsSub.stop();
|
this.roomsSub.stop();
|
||||||
|
this.roomsSub = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.activeUsersSubTimeout) {
|
if (this.activeUsersSubTimeout) {
|
||||||
|
|
|
@ -62,7 +62,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
|
||||||
formData.append('file', {
|
formData.append('file', {
|
||||||
uri: fileInfo.path,
|
uri: fileInfo.path,
|
||||||
type: fileInfo.type,
|
type: fileInfo.type,
|
||||||
name: fileInfo.name || 'fileMessage'
|
name: encodeURI(fileInfo.name) || 'fileMessage'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (fileInfo.description) {
|
if (fileInfo.description) {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import reduxStore from '../../createStore';
|
||||||
import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actions/usersTyping';
|
import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actions/usersTyping';
|
||||||
import debounce from '../../../utils/debounce';
|
import debounce from '../../../utils/debounce';
|
||||||
import RocketChat from '../../rocketchat';
|
import RocketChat from '../../rocketchat';
|
||||||
|
import { subscribeRoom, unsubscribeRoom } from '../../../actions/room';
|
||||||
|
|
||||||
const WINDOW_TIME = 1000;
|
const WINDOW_TIME = 1000;
|
||||||
|
|
||||||
|
@ -38,6 +39,8 @@ export default class RoomSubscription {
|
||||||
if (!this.isAlive) {
|
if (!this.isAlive) {
|
||||||
this.unsubscribe();
|
this.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reduxStore.dispatch(subscribeRoom(this.rid));
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribe = async() => {
|
unsubscribe = async() => {
|
||||||
|
@ -51,14 +54,16 @@ export default class RoomSubscription {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
reduxStore.dispatch(clearUserTyping());
|
||||||
this.removeListener(this.connectedListener);
|
this.removeListener(this.connectedListener);
|
||||||
this.removeListener(this.disconnectedListener);
|
this.removeListener(this.disconnectedListener);
|
||||||
this.removeListener(this.notifyRoomListener);
|
this.removeListener(this.notifyRoomListener);
|
||||||
this.removeListener(this.messageReceivedListener);
|
this.removeListener(this.messageReceivedListener);
|
||||||
reduxStore.dispatch(clearUserTyping());
|
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
clearTimeout(this.timer);
|
clearTimeout(this.timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reduxStore.dispatch(unsubscribeRoom(this.rid));
|
||||||
}
|
}
|
||||||
|
|
||||||
removeListener = async(promise) => {
|
removeListener = async(promise) => {
|
||||||
|
@ -73,6 +78,7 @@ export default class RoomSubscription {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleConnection = () => {
|
handleConnection = () => {
|
||||||
|
reduxStore.dispatch(clearUserTyping());
|
||||||
RocketChat.loadMissedMessages({ rid: this.rid }).catch(e => console.log(e));
|
RocketChat.loadMissedMessages({ rid: this.rid }).catch(e => console.log(e));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -154,22 +160,17 @@ export default class RoomSubscription {
|
||||||
const msgCollection = db.collections.get('messages');
|
const msgCollection = db.collections.get('messages');
|
||||||
const threadsCollection = db.collections.get('threads');
|
const threadsCollection = db.collections.get('threads');
|
||||||
const threadMessagesCollection = db.collections.get('thread_messages');
|
const threadMessagesCollection = db.collections.get('thread_messages');
|
||||||
let messageRecord;
|
|
||||||
let threadRecord;
|
|
||||||
let threadMessageRecord;
|
|
||||||
|
|
||||||
// Create or update message
|
// Create or update message
|
||||||
try {
|
try {
|
||||||
messageRecord = await msgCollection.find(message._id);
|
const messageRecord = await msgCollection.find(message._id);
|
||||||
} catch (error) {
|
if (!messageRecord._hasPendingUpdate) {
|
||||||
// Do nothing
|
const update = messageRecord.prepareUpdate(protectedFunction((m) => {
|
||||||
}
|
Object.assign(m, message);
|
||||||
if (messageRecord) {
|
}));
|
||||||
const update = messageRecord.prepareUpdate((m) => {
|
this._messagesBatch[message._id] = update;
|
||||||
Object.assign(m, message);
|
}
|
||||||
});
|
} catch {
|
||||||
this._messagesBatch[message._id] = update;
|
|
||||||
} else {
|
|
||||||
const create = msgCollection.prepareCreate(protectedFunction((m) => {
|
const create = msgCollection.prepareCreate(protectedFunction((m) => {
|
||||||
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
|
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
|
||||||
m.subscription.id = this.rid;
|
m.subscription.id = this.rid;
|
||||||
|
@ -181,17 +182,14 @@ export default class RoomSubscription {
|
||||||
// Create or update thread
|
// Create or update thread
|
||||||
if (message.tlm) {
|
if (message.tlm) {
|
||||||
try {
|
try {
|
||||||
threadRecord = await threadsCollection.find(message._id);
|
const threadRecord = await threadsCollection.find(message._id);
|
||||||
} catch (error) {
|
if (!threadRecord._hasPendingUpdate) {
|
||||||
// Do nothing
|
const updateThread = threadRecord.prepareUpdate(protectedFunction((t) => {
|
||||||
}
|
Object.assign(t, message);
|
||||||
|
}));
|
||||||
if (threadRecord) {
|
this._threadsBatch[message._id] = updateThread;
|
||||||
const updateThread = threadRecord.prepareUpdate(protectedFunction((t) => {
|
}
|
||||||
Object.assign(t, message);
|
} catch {
|
||||||
}));
|
|
||||||
this._threadsBatch[message._id] = updateThread;
|
|
||||||
} else {
|
|
||||||
const createThread = threadsCollection.prepareCreate(protectedFunction((t) => {
|
const createThread = threadsCollection.prepareCreate(protectedFunction((t) => {
|
||||||
t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
|
t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
|
||||||
t.subscription.id = this.rid;
|
t.subscription.id = this.rid;
|
||||||
|
@ -204,19 +202,16 @@ export default class RoomSubscription {
|
||||||
// Create or update thread message
|
// Create or update thread message
|
||||||
if (message.tmid) {
|
if (message.tmid) {
|
||||||
try {
|
try {
|
||||||
threadMessageRecord = await threadMessagesCollection.find(message._id);
|
const threadMessageRecord = await threadMessagesCollection.find(message._id);
|
||||||
} catch (error) {
|
if (!threadMessageRecord._hasPendingUpdate) {
|
||||||
// Do nothing
|
const updateThreadMessage = threadMessageRecord.prepareUpdate(protectedFunction((tm) => {
|
||||||
}
|
Object.assign(tm, message);
|
||||||
|
tm.rid = message.tmid;
|
||||||
if (threadMessageRecord) {
|
delete tm.tmid;
|
||||||
const updateThreadMessage = threadMessageRecord.prepareUpdate(protectedFunction((tm) => {
|
}));
|
||||||
Object.assign(tm, message);
|
this._threadMessagesBatch[message._id] = updateThreadMessage;
|
||||||
tm.rid = message.tmid;
|
}
|
||||||
delete tm.tmid;
|
} catch {
|
||||||
}));
|
|
||||||
this._threadMessagesBatch[message._id] = updateThreadMessage;
|
|
||||||
} else {
|
|
||||||
const createThreadMessage = threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
|
const createThreadMessage = threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
|
||||||
tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
|
tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
|
||||||
Object.assign(tm, message);
|
Object.assign(tm, message);
|
||||||
|
|
|
@ -72,6 +72,7 @@ const createOrUpdateSubscription = async(subscription, room) => {
|
||||||
autoTranslate: s.autoTranslate,
|
autoTranslate: s.autoTranslate,
|
||||||
autoTranslateLanguage: s.autoTranslateLanguage,
|
autoTranslateLanguage: s.autoTranslateLanguage,
|
||||||
lastMessage: s.lastMessage,
|
lastMessage: s.lastMessage,
|
||||||
|
roles: s.roles,
|
||||||
usernames: s.usernames,
|
usernames: s.usernames,
|
||||||
uids: s.uids
|
uids: s.uids
|
||||||
};
|
};
|
||||||
|
@ -140,7 +141,8 @@ const createOrUpdateSubscription = async(subscription, room) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tmp.lastMessage) {
|
const { rooms } = store.getState().room;
|
||||||
|
if (tmp.lastMessage && !rooms.includes(tmp.rid)) {
|
||||||
const lastMessage = buildMessage(tmp.lastMessage);
|
const lastMessage = buildMessage(tmp.lastMessage);
|
||||||
const messagesCollection = db.collections.get('messages');
|
const messagesCollection = db.collections.get('messages');
|
||||||
let messageRecord;
|
let messageRecord;
|
||||||
|
|
|
@ -62,13 +62,12 @@ const RocketChat = {
|
||||||
TOKEN_KEY,
|
TOKEN_KEY,
|
||||||
callJitsi,
|
callJitsi,
|
||||||
async subscribeRooms() {
|
async subscribeRooms() {
|
||||||
if (this.roomsSub) {
|
if (!this.roomsSub) {
|
||||||
this.roomsSub.stop();
|
try {
|
||||||
}
|
this.roomsSub = await subscribeRooms.call(this);
|
||||||
try {
|
} catch (e) {
|
||||||
this.roomsSub = await subscribeRooms.call(this);
|
log(e);
|
||||||
} catch (e) {
|
}
|
||||||
log(e);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
canOpenRoom,
|
canOpenRoom,
|
||||||
|
@ -185,6 +184,7 @@ const RocketChat = {
|
||||||
|
|
||||||
if (this.roomsSub) {
|
if (this.roomsSub) {
|
||||||
this.roomsSub.stop();
|
this.roomsSub.stop();
|
||||||
|
this.roomsSub = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.sdk) {
|
if (this.sdk) {
|
||||||
|
@ -199,7 +199,7 @@ const RocketChat = {
|
||||||
this.sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) });
|
this.sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) });
|
||||||
this.getSettings();
|
this.getSettings();
|
||||||
|
|
||||||
this.sdk.connect()
|
const sdkConnect = () => this.sdk.connect()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (user && user.token) {
|
if (user && user.token) {
|
||||||
reduxStore.dispatch(loginRequest({ resume: user.token }, logoutOnError));
|
reduxStore.dispatch(loginRequest({ resume: user.token }, logoutOnError));
|
||||||
|
@ -210,10 +210,12 @@ const RocketChat = {
|
||||||
|
|
||||||
// when `connect` raises an error, we try again in 10 seconds
|
// when `connect` raises an error, we try again in 10 seconds
|
||||||
this.connectTimeout = setTimeout(() => {
|
this.connectTimeout = setTimeout(() => {
|
||||||
this.connect({ server, user });
|
sdkConnect();
|
||||||
}, 10000);
|
}, 10000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sdkConnect();
|
||||||
|
|
||||||
this.connectedListener = this.sdk.onStreamData('connected', () => {
|
this.connectedListener = this.sdk.onStreamData('connected', () => {
|
||||||
reduxStore.dispatch(connectSuccess());
|
reduxStore.dispatch(connectSuccess());
|
||||||
});
|
});
|
||||||
|
@ -277,14 +279,11 @@ const RocketChat = {
|
||||||
user = {
|
user = {
|
||||||
id: userRecord.id,
|
id: userRecord.id,
|
||||||
token: userRecord.token,
|
token: userRecord.token,
|
||||||
username: userRecord.username
|
username: userRecord.username,
|
||||||
|
roles: userRecord.roles
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
reduxStore.dispatch(shareSetUser({
|
reduxStore.dispatch(shareSetUser(user));
|
||||||
id: user.id,
|
|
||||||
token: user.token,
|
|
||||||
username: user.username
|
|
||||||
}));
|
|
||||||
await RocketChat.login({ resume: user.token });
|
await RocketChat.login({ resume: user.token });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
|
@ -296,6 +295,8 @@ const RocketChat = {
|
||||||
this.shareSDK = null;
|
this.shareSDK = null;
|
||||||
}
|
}
|
||||||
database.share = null;
|
database.share = null;
|
||||||
|
|
||||||
|
reduxStore.dispatch(shareSetUser(null));
|
||||||
},
|
},
|
||||||
|
|
||||||
updateJitsiTimeout(rid) {
|
updateJitsiTimeout(rid) {
|
||||||
|
@ -326,8 +327,8 @@ const RocketChat = {
|
||||||
if (e.data?.error && (e.data.error === 'totp-required' || e.data.error === 'totp-invalid')) {
|
if (e.data?.error && (e.data.error === 'totp-required' || e.data.error === 'totp-invalid')) {
|
||||||
const { details } = e.data;
|
const { details } = e.data;
|
||||||
try {
|
try {
|
||||||
await twoFactor({ method: details?.method, invalid: e.data.error === 'totp-invalid' });
|
const code = await twoFactor({ method: details?.method || 'totp', invalid: e.data.error === 'totp-invalid' });
|
||||||
return resolve(this.loginTOTP(params));
|
return resolve(this.loginTOTP({ ...params, code: code?.twoFactorCode }));
|
||||||
} catch {
|
} catch {
|
||||||
// twoFactor was canceled
|
// twoFactor was canceled
|
||||||
return reject();
|
return reject();
|
||||||
|
@ -497,12 +498,25 @@ const RocketChat = {
|
||||||
).fetch();
|
).fetch();
|
||||||
|
|
||||||
if (filterUsers && !filterRooms) {
|
if (filterUsers && !filterRooms) {
|
||||||
data = data.filter(item => item.t === 'd');
|
data = data.filter(item => item.t === 'd' && !RocketChat.isGroupChat(item));
|
||||||
} else if (!filterUsers && filterRooms) {
|
} else if (!filterUsers && filterRooms) {
|
||||||
data = data.filter(item => item.t !== 'd');
|
data = data.filter(item => item.t !== 'd' || RocketChat.isGroupChat(item));
|
||||||
}
|
}
|
||||||
data = data.slice(0, 7);
|
data = data.slice(0, 7);
|
||||||
|
|
||||||
|
data = data.map((sub) => {
|
||||||
|
if (sub.t !== 'd') {
|
||||||
|
return ({
|
||||||
|
rid: sub.rid,
|
||||||
|
name: sub.name,
|
||||||
|
fname: sub.fname,
|
||||||
|
t: sub.t,
|
||||||
|
search: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sub;
|
||||||
|
});
|
||||||
|
|
||||||
const usernames = data.map(sub => sub.name);
|
const usernames = data.map(sub => sub.name);
|
||||||
try {
|
try {
|
||||||
if (data.length < 7) {
|
if (data.length < 7) {
|
||||||
|
@ -553,11 +567,11 @@ const RocketChat = {
|
||||||
},
|
},
|
||||||
|
|
||||||
createGroupChat() {
|
createGroupChat() {
|
||||||
let { users } = reduxStore.getState().selectedUsers;
|
const { users } = reduxStore.getState().selectedUsers;
|
||||||
users = users.map(u => u.name);
|
const usernames = users.map(u => u.name).join(',');
|
||||||
|
|
||||||
// RC 3.1.0
|
// RC 3.1.0
|
||||||
return this.methodCall('createDirectMessage', ...users);
|
return this.post('im.create', { usernames });
|
||||||
},
|
},
|
||||||
|
|
||||||
createDiscussion({
|
createDiscussion({
|
||||||
|
@ -692,12 +706,9 @@ const RocketChat = {
|
||||||
setUserPresenceOnline() {
|
setUserPresenceOnline() {
|
||||||
return this.methodCall('UserPresence:online');
|
return this.methodCall('UserPresence:online');
|
||||||
},
|
},
|
||||||
setUserPresenceDefaultStatus(status) {
|
setUserStatus(status, message) {
|
||||||
return this.methodCall('UserPresence:setDefaultStatus', status);
|
|
||||||
},
|
|
||||||
setUserStatus(message) {
|
|
||||||
// RC 1.2.0
|
// RC 1.2.0
|
||||||
return this.post('users.setStatus', { message });
|
return this.post('users.setStatus', { status, message });
|
||||||
},
|
},
|
||||||
setReaction(emoji, messageId) {
|
setReaction(emoji, messageId) {
|
||||||
// RC 0.62.2
|
// RC 0.62.2
|
||||||
|
@ -738,7 +749,9 @@ const RocketChat = {
|
||||||
return this.sdk.get('rooms.info', { roomId });
|
return this.sdk.get('rooms.info', { roomId });
|
||||||
},
|
},
|
||||||
|
|
||||||
getUidDirectMessage(room, userId) {
|
getUidDirectMessage(room) {
|
||||||
|
const { id: userId } = reduxStore.getState().login.user;
|
||||||
|
|
||||||
// legacy method
|
// legacy method
|
||||||
if (!room.uids && room.rid && room.t === 'd') {
|
if (!room.uids && room.rid && room.t === 'd') {
|
||||||
return room.rid.replace(userId, '').trim();
|
return room.rid.replace(userId, '').trim();
|
||||||
|
@ -821,7 +834,7 @@ const RocketChat = {
|
||||||
methodCall(...args) {
|
methodCall(...args) {
|
||||||
return new Promise(async(resolve, reject) => {
|
return new Promise(async(resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const result = await this.sdk.methodCall(...args, this.code);
|
const result = await this.sdk.methodCall(...args, this.code || '');
|
||||||
return resolve(result);
|
return resolve(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) {
|
if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) {
|
||||||
|
@ -875,7 +888,7 @@ const RocketChat = {
|
||||||
// get the room from database
|
// get the room from database
|
||||||
const room = await subsCollection.find(rid);
|
const room = await subsCollection.find(rid);
|
||||||
// get room roles
|
// get room roles
|
||||||
roomRoles = room.roles;
|
roomRoles = room.roles || [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('hasPermission -> Room not found');
|
console.log('hasPermission -> Room not found');
|
||||||
return permissions.reduce((result, permission) => {
|
return permissions.reduce((result, permission) => {
|
||||||
|
@ -886,8 +899,10 @@ const RocketChat = {
|
||||||
// get permissions from database
|
// get permissions from database
|
||||||
try {
|
try {
|
||||||
const permissionsFiltered = await permissionsCollection.query(Q.where('id', Q.oneOf(permissions))).fetch();
|
const permissionsFiltered = await permissionsCollection.query(Q.where('id', Q.oneOf(permissions))).fetch();
|
||||||
|
const shareUser = reduxStore.getState().share.user;
|
||||||
|
const loginUser = reduxStore.getState().login.user;
|
||||||
// get user roles on the server from redux
|
// get user roles on the server from redux
|
||||||
const userRoles = (reduxStore.getState().login.user && reduxStore.getState().login.user.roles) || [];
|
const userRoles = (shareUser.roles || loginUser.roles) || [];
|
||||||
// merge both roles
|
// merge both roles
|
||||||
const mergedRoles = [...new Set([...roomRoles, ...userRoles])];
|
const mergedRoles = [...new Set([...roomRoles, ...userRoles])];
|
||||||
|
|
||||||
|
@ -1136,16 +1151,19 @@ const RocketChat = {
|
||||||
return this.methodCall('autoTranslate.translateMessage', message, targetLanguage);
|
return this.methodCall('autoTranslate.translateMessage', message, targetLanguage);
|
||||||
},
|
},
|
||||||
getRoomTitle(room) {
|
getRoomTitle(room) {
|
||||||
const { UI_Use_Real_Name: useRealName } = reduxStore.getState().settings;
|
const { UI_Use_Real_Name: useRealName, UI_Allow_room_names_with_special_chars: allowSpecialChars } = reduxStore.getState().settings;
|
||||||
const { username } = reduxStore.getState().login.user;
|
const { username } = reduxStore.getState().login.user;
|
||||||
if (RocketChat.isGroupChat(room) && !(room.name && room.name.length)) {
|
if (RocketChat.isGroupChat(room) && !(room.name && room.name.length)) {
|
||||||
return room.usernames.filter(u => u !== username).sort((u1, u2) => u1.localeCompare(u2)).join(', ');
|
return room.usernames.filter(u => u !== username).sort((u1, u2) => u1.localeCompare(u2)).join(', ');
|
||||||
}
|
}
|
||||||
|
if (allowSpecialChars && room.t !== 'd') {
|
||||||
|
return room.fname || room.name;
|
||||||
|
}
|
||||||
return ((room.prid || useRealName) && room.fname) || room.name;
|
return ((room.prid || useRealName) && room.fname) || room.name;
|
||||||
},
|
},
|
||||||
getRoomAvatar(room) {
|
getRoomAvatar(room) {
|
||||||
if (RocketChat.isGroupChat(room)) {
|
if (RocketChat.isGroupChat(room)) {
|
||||||
return room.uids.length + room.usernames.join();
|
return room.uids?.length + room.usernames?.join();
|
||||||
}
|
}
|
||||||
return room.prid ? room.fname : room.name;
|
return room.prid ? room.fname : room.name;
|
||||||
},
|
},
|
||||||
|
|
|
@ -27,6 +27,7 @@ const attrs = [
|
||||||
'isRead',
|
'isRead',
|
||||||
'favorite',
|
'favorite',
|
||||||
'status',
|
'status',
|
||||||
|
'connected',
|
||||||
'theme'
|
'theme'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -40,15 +41,15 @@ const arePropsEqual = (oldProps, newProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoomItem = React.memo(({
|
const RoomItem = React.memo(({
|
||||||
onPress, width, favorite, toggleFav, isRead, rid, toggleRead, hideChannel, testID, unread, userMentions, name, _updatedAt, alert, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, hideUnreadStatus, lastMessage, status, avatar, useRealName, getUserPresence, isGroupChat, theme
|
onPress, width, favorite, toggleFav, isRead, rid, toggleRead, hideChannel, testID, unread, userMentions, name, _updatedAt, alert, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, hideUnreadStatus, lastMessage, status, avatar, useRealName, getUserPresence, isGroupChat, connected, theme
|
||||||
}) => {
|
}) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (type === 'd') {
|
if (connected && type === 'd' && id) {
|
||||||
getUserPresence(id);
|
getUserPresence(id);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [connected]);
|
||||||
|
|
||||||
const date = formatDate(_updatedAt);
|
const date = lastMessage && formatDate(lastMessage.ts);
|
||||||
|
|
||||||
let accessibilityLabel = name;
|
let accessibilityLabel = name;
|
||||||
if (unread === 1) {
|
if (unread === 1) {
|
||||||
|
@ -197,6 +198,7 @@ RoomItem.propTypes = {
|
||||||
hideUnreadStatus: PropTypes.bool,
|
hideUnreadStatus: PropTypes.bool,
|
||||||
useRealName: PropTypes.bool,
|
useRealName: PropTypes.bool,
|
||||||
getUserPresence: PropTypes.func,
|
getUserPresence: PropTypes.func,
|
||||||
|
connected: PropTypes.bool,
|
||||||
isGroupChat: PropTypes.bool,
|
isGroupChat: PropTypes.bool,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
@ -208,6 +210,7 @@ RoomItem.defaultProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => ({
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
|
connected: state.meteor.connected,
|
||||||
status:
|
status:
|
||||||
state.meteor.connected && ownProps.type === 'd'
|
state.meteor.connected && ownProps.type === 'd'
|
||||||
? state.activeUsers[ownProps.id] && state.activeUsers[ownProps.id].status
|
? state.activeUsers[ownProps.id] && state.activeUsers[ownProps.id].status
|
||||||
|
|
|
@ -2,11 +2,23 @@ import { ROOM } from '../actions/actionsTypes';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
rid: null,
|
rid: null,
|
||||||
isDeleting: false
|
isDeleting: false,
|
||||||
|
rooms: []
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function(state = initialState, action) {
|
export default function(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case ROOM.SUBSCRIBE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
rooms: [action.rid, ...state.rooms]
|
||||||
|
};
|
||||||
|
case ROOM.UNSUBSCRIBE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
rooms: state.rooms
|
||||||
|
.filter(room => room.rid === action.rid)
|
||||||
|
};
|
||||||
case ROOM.LEAVE:
|
case ROOM.LEAVE:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -28,7 +28,10 @@ const handleRequest = function* handleRequest({ data }) {
|
||||||
|
|
||||||
let sub;
|
let sub;
|
||||||
if (data.group) {
|
if (data.group) {
|
||||||
sub = yield call(createGroupChat);
|
const result = yield call(createGroupChat);
|
||||||
|
if (result.success) {
|
||||||
|
({ room: sub } = result);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
sub = yield call(createChannel, data);
|
sub = yield call(createChannel, data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,14 +30,21 @@ const handleInviteLink = function* handleInviteLink({ params, requireLogin = fal
|
||||||
const navigate = function* navigate({ params }) {
|
const navigate = function* navigate({ params }) {
|
||||||
yield put(appStart('inside'));
|
yield put(appStart('inside'));
|
||||||
if (params.path) {
|
if (params.path) {
|
||||||
const room = yield RocketChat.canOpenRoom(params);
|
|
||||||
const [type, name] = params.path.split('/');
|
const [type, name] = params.path.split('/');
|
||||||
if (room) {
|
if (type !== 'invite') {
|
||||||
yield Navigation.navigate('RoomsListView');
|
const room = yield RocketChat.canOpenRoom(params);
|
||||||
Navigation.navigate('RoomView', { name, t: roomTypes[type], ...room });
|
if (room) {
|
||||||
|
yield Navigation.navigate('RoomsListView');
|
||||||
|
Navigation.navigate('RoomView', {
|
||||||
|
name,
|
||||||
|
t: roomTypes[type],
|
||||||
|
roomUserId: RocketChat.getUidDirectMessage(room),
|
||||||
|
...room
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yield handleInviteLink({ params });
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
yield handleInviteLink({ params });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import database from '../lib/database';
|
||||||
import EventEmitter from '../utils/events';
|
import EventEmitter from '../utils/events';
|
||||||
import { inviteLinksRequest } from '../actions/inviteLinks';
|
import { inviteLinksRequest } from '../actions/inviteLinks';
|
||||||
import { showErrorAlert } from '../utils/info';
|
import { showErrorAlert } from '../utils/info';
|
||||||
|
import { setActiveUsers } from '../actions/activeUsers';
|
||||||
|
|
||||||
const getServer = state => state.server.server;
|
const getServer = state => state.server.server;
|
||||||
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
|
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
|
||||||
|
@ -73,7 +74,7 @@ const registerPushToken = function* registerPushToken() {
|
||||||
|
|
||||||
const fetchUsersPresence = function* fetchUserPresence() {
|
const fetchUsersPresence = function* fetchUserPresence() {
|
||||||
yield RocketChat.getUsersPresence();
|
yield RocketChat.getUsersPresence();
|
||||||
yield RocketChat.subscribeUsersPresence();
|
RocketChat.subscribeUsersPresence();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
|
@ -81,6 +82,8 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
const adding = yield select(state => state.server.adding);
|
const adding = yield select(state => state.server.adding);
|
||||||
yield RNUserDefaults.set(RocketChat.TOKEN_KEY, user.token);
|
yield RNUserDefaults.set(RocketChat.TOKEN_KEY, user.token);
|
||||||
|
|
||||||
|
RocketChat.getUserPresence(user.id);
|
||||||
|
|
||||||
const server = yield select(getServer);
|
const server = yield select(getServer);
|
||||||
yield put(roomsRequest());
|
yield put(roomsRequest());
|
||||||
yield fork(fetchPermissions);
|
yield fork(fetchPermissions);
|
||||||
|
@ -186,11 +189,16 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetUser = function handleSetUser({ user }) {
|
const handleSetUser = function* handleSetUser({ user }) {
|
||||||
if (user && user.language) {
|
if (user && user.language) {
|
||||||
I18n.locale = user.language;
|
I18n.locale = user.language;
|
||||||
moment.locale(toMomentLocale(user.language));
|
moment.locale(toMomentLocale(user.language));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user && user.status) {
|
||||||
|
const userId = yield select(state => state.login.user.id);
|
||||||
|
yield put(setActiveUsers({ [userId]: user }));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
|
|
|
@ -41,7 +41,7 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) {
|
||||||
({ roomsUpdatedAt } = serverRecord);
|
({ roomsUpdatedAt } = serverRecord);
|
||||||
}
|
}
|
||||||
const [subscriptionsResult, roomsResult] = yield RocketChat.getRooms(roomsUpdatedAt);
|
const [subscriptionsResult, roomsResult] = yield RocketChat.getRooms(roomsUpdatedAt);
|
||||||
const { subscriptions } = mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
|
const { subscriptions } = yield mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
|
||||||
|
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const subCollection = db.collections.get('subscriptions');
|
const subCollection = db.collections.get('subscriptions');
|
||||||
|
|
|
@ -12,8 +12,8 @@ import * as actions from '../actions';
|
||||||
import {
|
import {
|
||||||
serverFailure, selectServerRequest, selectServerSuccess, selectServerFailure
|
serverFailure, selectServerRequest, selectServerSuccess, selectServerFailure
|
||||||
} from '../actions/server';
|
} from '../actions/server';
|
||||||
import { setUser } from '../actions/login';
|
|
||||||
import { clearSettings } from '../actions/settings';
|
import { clearSettings } from '../actions/settings';
|
||||||
|
import { setUser } from '../actions/login';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/database';
|
import database from '../lib/database';
|
||||||
import log, { logServerVersion } from '../utils/log';
|
import log, { logServerVersion } from '../utils/log';
|
||||||
|
@ -38,7 +38,10 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validVersion = semver.coerce(serverInfo.version);
|
let serverVersion = semver.valid(serverInfo.version);
|
||||||
|
if (!serverVersion) {
|
||||||
|
({ version: serverVersion } = semver.coerce(serverInfo.version));
|
||||||
|
}
|
||||||
|
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
const serversCollection = serversDB.collections.get('servers');
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
|
@ -46,12 +49,12 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
|
||||||
try {
|
try {
|
||||||
const serverRecord = await serversCollection.find(server);
|
const serverRecord = await serversCollection.find(server);
|
||||||
await serverRecord.update((record) => {
|
await serverRecord.update((record) => {
|
||||||
record.version = validVersion;
|
record.version = serverVersion;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await serversCollection.create((record) => {
|
await serversCollection.create((record) => {
|
||||||
record._raw = sanitizedRaw({ id: server }, serversCollection.schema);
|
record._raw = sanitizedRaw({ id: server }, serversCollection.schema);
|
||||||
record.version = validVersion;
|
record.version = serverVersion;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -95,9 +98,8 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
||||||
const basicAuth = yield RNUserDefaults.get(`${ BASIC_AUTH_KEY }-${ server }`);
|
const basicAuth = yield RNUserDefaults.get(`${ BASIC_AUTH_KEY }-${ server }`);
|
||||||
setBasicAuth(basicAuth);
|
setBasicAuth(basicAuth);
|
||||||
|
|
||||||
yield put(clearSettings());
|
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
|
yield put(clearSettings());
|
||||||
yield RocketChat.connect({ server, user, logoutOnError: true });
|
yield RocketChat.connect({ server, user, logoutOnError: true });
|
||||||
yield put(setUser(user));
|
yield put(setUser(user));
|
||||||
yield put(actions.appStart('inside'));
|
yield put(actions.appStart('inside'));
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
|
||||||
|
const canPost = async({ rid }) => {
|
||||||
|
try {
|
||||||
|
const permission = await RocketChat.hasPermission(['post-readonly'], rid);
|
||||||
|
return permission && permission['post-readonly'];
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isMuted = (room, user) => room && room.muted && room.muted.find && !!room.muted.find(m => m === user.username);
|
||||||
|
|
||||||
|
export const isReadOnly = async(room, user) => {
|
||||||
|
if (room.archived) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const allowPost = await canPost(room);
|
||||||
|
if (allowPost) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (room && room.ro) || isMuted(room, user);
|
||||||
|
};
|
|
@ -2,17 +2,6 @@ import moment from 'moment';
|
||||||
|
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
|
|
||||||
export const isOwner = room => room && room.roles && room.roles.length && !!room.roles.find(role => role === 'owner');
|
|
||||||
|
|
||||||
export const isMuted = (room, user) => room && room.muted && room.muted.find && !!room.muted.find(m => m === user.username);
|
|
||||||
|
|
||||||
export const isReadOnly = (room, user) => {
|
|
||||||
if (isOwner(room)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (room && room.ro) || isMuted(room, user);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isBlocked = (room) => {
|
export const isBlocked = (room) => {
|
||||||
if (room) {
|
if (room) {
|
||||||
const { t, blocked, blocker } = room;
|
const { t, blocked, blocker } = room;
|
||||||
|
|
|
@ -33,11 +33,11 @@ class AttachmentView extends React.Component {
|
||||||
const attachment = navigation.getParam('attachment');
|
const attachment = navigation.getParam('attachment');
|
||||||
const from = navigation.getParam('from');
|
const from = navigation.getParam('from');
|
||||||
const handleSave = navigation.getParam('handleSave', () => {});
|
const handleSave = navigation.getParam('handleSave', () => {});
|
||||||
const { title, video_url } = attachment;
|
const { title } = attachment;
|
||||||
const options = {
|
const options = {
|
||||||
title,
|
title: decodeURI(title),
|
||||||
...themedHeader(theme),
|
...themedHeader(theme),
|
||||||
headerRight: !video_url ? <SaveButton testID='save-image' onPress={handleSave} /> : null
|
headerRight: <SaveButton testID='save-image' onPress={handleSave} />
|
||||||
};
|
};
|
||||||
if (from !== 'MessagesView') {
|
if (from !== 'MessagesView') {
|
||||||
options.gesturesEnabled = false;
|
options.gesturesEnabled = false;
|
||||||
|
@ -84,8 +84,11 @@ class AttachmentView extends React.Component {
|
||||||
handleSave = async() => {
|
handleSave = async() => {
|
||||||
const { attachment } = this.state;
|
const { attachment } = this.state;
|
||||||
const { user, baseUrl } = this.props;
|
const { user, baseUrl } = this.props;
|
||||||
const { image_url, image_type } = attachment;
|
const {
|
||||||
const img = formatAttachmentUrl(image_url, user.id, user.token, baseUrl);
|
image_url, image_type, video_url, video_type
|
||||||
|
} = attachment;
|
||||||
|
const url = image_url || video_url;
|
||||||
|
const mediaAttachment = formatAttachmentUrl(url, user.id, user.token, baseUrl);
|
||||||
|
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
const rationale = {
|
const rationale = {
|
||||||
|
@ -100,13 +103,13 @@ class AttachmentView extends React.Component {
|
||||||
|
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
try {
|
try {
|
||||||
const extension = `.${ mime.extension(image_type) || 'jpg' }`;
|
const extension = image_url ? `.${ mime.extension(image_type) || 'jpg' }` : `.${ mime.extension(video_type) || 'mp4' }`;
|
||||||
const file = `${ FileSystem.documentDirectory + SHA256(image_url) + extension }`;
|
const file = `${ FileSystem.documentDirectory + SHA256(url) + extension }`;
|
||||||
const { uri } = await FileSystem.downloadAsync(img, file);
|
const { uri } = await FileSystem.downloadAsync(mediaAttachment, file);
|
||||||
await CameraRoll.save(uri, { album: 'Rocket.Chat' });
|
await CameraRoll.save(uri, { album: 'Rocket.Chat' });
|
||||||
EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') });
|
EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
EventEmitter.emit(LISTENER, { message: I18n.t('error-save-image') });
|
EventEmitter.emit(LISTENER, { message: I18n.t(image_url ? 'error-save-image' : 'error-save-video') });
|
||||||
}
|
}
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
};
|
};
|
||||||
|
|
|
@ -79,11 +79,12 @@ class AuthenticationWebView extends React.PureComponent {
|
||||||
if (this.authType === 'saml' || this.authType === 'cas') {
|
if (this.authType === 'saml' || this.authType === 'cas') {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
const ssoToken = navigation.getParam('ssoToken');
|
const ssoToken = navigation.getParam('ssoToken');
|
||||||
if (url.includes('ticket') || url.includes('validate') || url.includes('saml_idp_credentialToken')) {
|
const parsedUrl = parse(url, true);
|
||||||
|
// ticket -> cas / validate & saml_idp_credentialToken -> saml
|
||||||
|
if (parsedUrl.pathname?.includes('validate') || parsedUrl.query?.ticket || parsedUrl.query?.saml_idp_credentialToken) {
|
||||||
let payload;
|
let payload;
|
||||||
if (this.authType === 'saml') {
|
if (this.authType === 'saml') {
|
||||||
const parsedUrl = parse(url, true);
|
const token = parsedUrl.query?.saml_idp_credentialToken || ssoToken;
|
||||||
const token = (parsedUrl.query && parsedUrl.query.saml_idp_credentialToken) || ssoToken;
|
|
||||||
const credentialToken = { credentialToken: token };
|
const credentialToken = { credentialToken: token };
|
||||||
payload = { ...credentialToken, saml: true };
|
payload = { ...credentialToken, saml: true };
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -125,10 +125,14 @@ class DirectoryView extends React.Component {
|
||||||
this.setState(({ showOptionsDropdown }) => ({ showOptionsDropdown: !showOptionsDropdown }));
|
this.setState(({ showOptionsDropdown }) => ({ showOptionsDropdown: !showOptionsDropdown }));
|
||||||
}
|
}
|
||||||
|
|
||||||
goRoom = async({ rid, name, t }) => {
|
goRoom = async({
|
||||||
|
rid, name, t, search
|
||||||
|
}) => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
await navigation.navigate('RoomsListView');
|
await navigation.navigate('RoomsListView');
|
||||||
navigation.navigate('RoomView', { rid, name, t });
|
navigation.navigate('RoomView', {
|
||||||
|
rid, name, t, search
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressItem = async(item) => {
|
onPressItem = async(item) => {
|
||||||
|
@ -139,7 +143,9 @@ class DirectoryView extends React.Component {
|
||||||
this.goRoom({ rid: result.room._id, name: item.username, t: 'd' });
|
this.goRoom({ rid: result.room._id, name: item.username, t: 'd' });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.goRoom({ rid: item._id, name: item.name, t: 'c' });
|
this.goRoom({
|
||||||
|
rid: item._id, name: item.name, t: 'c', search: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ class ModalBlockView extends React.Component {
|
||||||
return {
|
return {
|
||||||
title: textParser([title]),
|
title: textParser([title]),
|
||||||
...themedHeader(theme),
|
...themedHeader(theme),
|
||||||
headerLeft: (
|
headerLeft: close ? (
|
||||||
<CustomHeaderButtons>
|
<CustomHeaderButtons>
|
||||||
<Item
|
<Item
|
||||||
title={textParser([close.text])}
|
title={textParser([close.text])}
|
||||||
|
@ -76,8 +76,8 @@ class ModalBlockView extends React.Component {
|
||||||
testID='close-modal-uikit'
|
testID='close-modal-uikit'
|
||||||
/>
|
/>
|
||||||
</CustomHeaderButtons>
|
</CustomHeaderButtons>
|
||||||
),
|
) : null,
|
||||||
headerRight: (
|
headerRight: submit ? (
|
||||||
<CustomHeaderButtons>
|
<CustomHeaderButtons>
|
||||||
<Item
|
<Item
|
||||||
title={textParser([submit.text])}
|
title={textParser([submit.text])}
|
||||||
|
@ -86,7 +86,7 @@ class ModalBlockView extends React.Component {
|
||||||
testID='submit-modal-uikit'
|
testID='submit-modal-uikit'
|
||||||
/>
|
/>
|
||||||
</CustomHeaderButtons>
|
</CustomHeaderButtons>
|
||||||
)
|
) : null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ class ModalBlockView extends React.Component {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
const oldData = prevProps.navigation.getParam('data', {});
|
const oldData = prevProps.navigation.getParam('data', {});
|
||||||
const newData = navigation.getParam('data', {});
|
const newData = navigation.getParam('data', {});
|
||||||
if (!isEqual(oldData, newData)) {
|
if (oldData.viewId !== newData.viewId) {
|
||||||
navigation.push('ModalBlockView', { data: newData });
|
navigation.push('ModalBlockView', { data: newData });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,12 +148,14 @@ class ModalBlockView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdate = ({ type, ...data }) => {
|
handleUpdate = ({ type, ...data }) => {
|
||||||
|
const { navigation } = this.props;
|
||||||
if ([MODAL_ACTIONS.ERRORS].includes(type)) {
|
if ([MODAL_ACTIONS.ERRORS].includes(type)) {
|
||||||
const { errors } = data;
|
const { errors } = data;
|
||||||
this.setState({ errors });
|
this.setState({ errors });
|
||||||
} else {
|
} else {
|
||||||
this.setState({ data });
|
this.setState({ data });
|
||||||
}
|
}
|
||||||
|
navigation.setParams({ data });
|
||||||
};
|
};
|
||||||
|
|
||||||
cancel = async({ closeModal }) => {
|
cancel = async({ closeModal }) => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
Text, Keyboard, StyleSheet, TouchableOpacity, View, Alert
|
Text, Keyboard, StyleSheet, TouchableOpacity, View, Alert, BackHandler
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import * as FileSystem from 'expo-file-system';
|
import * as FileSystem from 'expo-file-system';
|
||||||
|
@ -105,6 +105,7 @@ class NewServerView extends React.Component {
|
||||||
certificate: null
|
certificate: null
|
||||||
};
|
};
|
||||||
EventEmitter.addEventListener('NewServer', this.handleNewServerEvent);
|
EventEmitter.addEventListener('NewServer', this.handleNewServerEvent);
|
||||||
|
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -116,6 +117,16 @@ class NewServerView extends React.Component {
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
EventEmitter.removeListener('NewServer', this.handleNewServerEvent);
|
EventEmitter.removeListener('NewServer', this.handleNewServerEvent);
|
||||||
|
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBackPress = () => {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
if (navigation.isFocused() && this.previousServer) {
|
||||||
|
this.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeText = (text) => {
|
onChangeText = (text) => {
|
||||||
|
|
|
@ -3,9 +3,9 @@ import {
|
||||||
View, ScrollView, Switch, Text
|
View, ScrollView, Switch, Text
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import RNPickerSelect from 'react-native-picker-select';
|
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
|
|
||||||
|
import database from '../../lib/database';
|
||||||
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
|
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import ListItem from '../../containers/ListItem';
|
import ListItem from '../../containers/ListItem';
|
||||||
|
@ -15,9 +15,9 @@ import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import log from '../../utils/log';
|
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import { themedHeader } from '../../utils/navigation';
|
import { themedHeader } from '../../utils/navigation';
|
||||||
|
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
||||||
|
|
||||||
const SectionTitle = React.memo(({ title, theme }) => (
|
const SectionTitle = React.memo(({ title, theme }) => (
|
||||||
<Text
|
<Text
|
||||||
|
@ -181,43 +181,52 @@ class NotificationPreferencesView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onValueChangeSwitch = async(key, value) => {
|
saveNotificationSettings = async(key, value, params) => {
|
||||||
const params = {
|
const { room } = this.state;
|
||||||
[key]: value ? '1' : '0'
|
const db = database.active;
|
||||||
};
|
|
||||||
|
await db.action(async() => {
|
||||||
|
await room.update(protectedFunction((r) => {
|
||||||
|
r[key] = value;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await RocketChat.saveNotificationSettings(this.rid, params);
|
const result = await RocketChat.saveNotificationSettings(this.rid, params);
|
||||||
} catch (e) {
|
if (result.success) {
|
||||||
log(e);
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await db.action(async() => {
|
||||||
|
await room.update(protectedFunction((r) => {
|
||||||
|
r[key] = room[key];
|
||||||
|
}));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onValueChangePicker = async(key, value) => {
|
onValueChangeSwitch = (key, value) => this.saveNotificationSettings(key, value, { [key]: value ? '1' : '0' });
|
||||||
const params = {
|
|
||||||
[key]: value.toString()
|
onValueChangePicker = (key, value) => this.saveNotificationSettings(key, value, { [key]: value.toString() });
|
||||||
};
|
|
||||||
try {
|
pickerSelection = (title, key) => {
|
||||||
await RocketChat.saveNotificationSettings(this.rid, params);
|
const { room } = this.state;
|
||||||
} catch (e) {
|
const { navigation } = this.props;
|
||||||
log(e);
|
navigation.navigate('PickerView', {
|
||||||
}
|
title,
|
||||||
|
data: OPTIONS[key],
|
||||||
|
value: room[key],
|
||||||
|
onChangeValue: value => this.onValueChangePicker(key, value)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPicker = (key) => {
|
renderPickerOption = (key) => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
return (
|
const text = room[key] ? OPTIONS[key].find(option => option.value === room[key]) : OPTIONS[key][0];
|
||||||
<RNPickerSelect
|
return <Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>{text?.label}</Text>;
|
||||||
testID={key}
|
|
||||||
style={{ viewContainer: styles.viewContainer }}
|
|
||||||
value={room[key]}
|
|
||||||
textInputProps={{ style: { ...styles.pickerText, color: themes[theme].actionTintColor } }}
|
|
||||||
useNativeAndroidPickerStyle={false}
|
|
||||||
placeholder={{}}
|
|
||||||
onValueChange={value => this.onValueChangePicker(key, value)}
|
|
||||||
items={OPTIONS[key]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSwitch = (key) => {
|
renderSwitch = (key) => {
|
||||||
|
@ -283,7 +292,8 @@ class NotificationPreferencesView extends React.Component {
|
||||||
<ListItem
|
<ListItem
|
||||||
title={I18n.t('Alert')}
|
title={I18n.t('Alert')}
|
||||||
testID='notification-preference-view-alert'
|
testID='notification-preference-view-alert'
|
||||||
right={() => this.renderPicker('desktopNotifications')}
|
onPress={title => this.pickerSelection(title, 'desktopNotifications')}
|
||||||
|
right={() => this.renderPickerOption('desktopNotifications')}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
<Separator theme={theme} />
|
<Separator theme={theme} />
|
||||||
|
@ -296,7 +306,8 @@ class NotificationPreferencesView extends React.Component {
|
||||||
<ListItem
|
<ListItem
|
||||||
title={I18n.t('Alert')}
|
title={I18n.t('Alert')}
|
||||||
testID='notification-preference-view-push-notification'
|
testID='notification-preference-view-push-notification'
|
||||||
right={() => this.renderPicker('mobilePushNotifications')}
|
onPress={title => this.pickerSelection(title, 'mobilePushNotifications')}
|
||||||
|
right={() => this.renderPickerOption('mobilePushNotifications')}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
<Separator theme={theme} />
|
<Separator theme={theme} />
|
||||||
|
@ -309,21 +320,24 @@ class NotificationPreferencesView extends React.Component {
|
||||||
<ListItem
|
<ListItem
|
||||||
title={I18n.t('Audio')}
|
title={I18n.t('Audio')}
|
||||||
testID='notification-preference-view-audio'
|
testID='notification-preference-view-audio'
|
||||||
right={() => this.renderPicker('audioNotifications')}
|
onPress={title => this.pickerSelection(title, 'audioNotifications')}
|
||||||
|
right={() => this.renderPickerOption('audioNotifications')}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
<Separator theme={theme} />
|
<Separator theme={theme} />
|
||||||
<ListItem
|
<ListItem
|
||||||
title={I18n.t('Sound')}
|
title={I18n.t('Sound')}
|
||||||
testID='notification-preference-view-sound'
|
testID='notification-preference-view-sound'
|
||||||
right={() => this.renderPicker('audioNotificationValue')}
|
onPress={title => this.pickerSelection(title, 'audioNotificationValue')}
|
||||||
|
right={() => this.renderPickerOption('audioNotificationValue')}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
<Separator theme={theme} />
|
<Separator theme={theme} />
|
||||||
<ListItem
|
<ListItem
|
||||||
title={I18n.t('Notification_Duration')}
|
title={I18n.t('Notification_Duration')}
|
||||||
testID='notification-preference-view-notification-duration'
|
testID='notification-preference-view-notification-duration'
|
||||||
right={() => this.renderPicker('desktopNotificationDuration')}
|
onPress={title => this.pickerSelection(title, 'desktopNotificationDuration')}
|
||||||
|
right={() => this.renderPickerOption('desktopNotificationDuration')}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
<Separator theme={theme} />
|
<Separator theme={theme} />
|
||||||
|
@ -335,7 +349,8 @@ class NotificationPreferencesView extends React.Component {
|
||||||
<ListItem
|
<ListItem
|
||||||
title={I18n.t('Alert')}
|
title={I18n.t('Alert')}
|
||||||
testID='notification-preference-view-email-alert'
|
testID='notification-preference-view-email-alert'
|
||||||
right={() => this.renderPicker('emailNotifications')}
|
onPress={title => this.pickerSelection(title, 'emailNotifications')}
|
||||||
|
right={() => this.renderPickerOption('emailNotifications')}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
<Separator theme={theme} />
|
<Separator theme={theme} />
|
||||||
|
|
|
@ -24,9 +24,6 @@ export default StyleSheet.create({
|
||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
fontSize: 14
|
fontSize: 14
|
||||||
},
|
},
|
||||||
viewContainer: {
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
|
||||||
pickerText: {
|
pickerText: {
|
||||||
...sharedStyles.textRegular,
|
...sharedStyles.textRegular,
|
||||||
fontSize: 16
|
fontSize: 16
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { FlatList, StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
import I18n from '../i18n';
|
||||||
|
import { themedHeader } from '../utils/navigation';
|
||||||
|
import { withTheme } from '../theme';
|
||||||
|
import { themes } from '../constants/colors';
|
||||||
|
import sharedStyles from './Styles';
|
||||||
|
|
||||||
|
import ListItem from '../containers/ListItem';
|
||||||
|
import Check from '../containers/Check';
|
||||||
|
import Separator from '../containers/Separator';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
check: {
|
||||||
|
marginHorizontal: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Item = React.memo(({
|
||||||
|
item,
|
||||||
|
selected,
|
||||||
|
onItemPress,
|
||||||
|
theme
|
||||||
|
}) => (
|
||||||
|
<ListItem
|
||||||
|
title={item.label}
|
||||||
|
right={selected && (() => <Check theme={theme} style={styles.check} />)}
|
||||||
|
onPress={onItemPress}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
Item.propTypes = {
|
||||||
|
item: PropTypes.object,
|
||||||
|
selected: PropTypes.bool,
|
||||||
|
onItemPress: PropTypes.func,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
class PickerView extends React.PureComponent {
|
||||||
|
static navigationOptions = ({ navigation, screenProps }) => ({
|
||||||
|
title: navigation.getParam('title', I18n.t('Select_an_option')),
|
||||||
|
...themedHeader(screenProps.theme)
|
||||||
|
})
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
navigation: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const data = props.navigation.getParam('data', []);
|
||||||
|
const value = props.navigation.getParam('value');
|
||||||
|
this.state = { data, value };
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeValue = (value) => {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
const onChange = navigation.getParam('onChangeValue', () => {});
|
||||||
|
onChange(value);
|
||||||
|
navigation.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { data, value } = this.state;
|
||||||
|
const { theme } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlatList
|
||||||
|
data={data}
|
||||||
|
keyExtractor={item => item.value}
|
||||||
|
renderItem={({ item }) => (
|
||||||
|
<Item
|
||||||
|
item={item}
|
||||||
|
theme={theme}
|
||||||
|
selected={(value || data[0]?.value) === item.value}
|
||||||
|
onItemPress={() => this.onChangeValue(item.value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
ItemSeparatorComponent={() => <Separator theme={theme} />}
|
||||||
|
contentContainerStyle={[
|
||||||
|
sharedStyles.listContentContainer,
|
||||||
|
{
|
||||||
|
backgroundColor: themes[theme].auxiliaryBackground,
|
||||||
|
borderColor: themes[theme].separatorColor
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withTheme(PickerView);
|
|
@ -87,35 +87,37 @@ class RoomActionsView extends React.Component {
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
this.mounted = true;
|
this.mounted = true;
|
||||||
const { room, member } = this.state;
|
const { room, member } = this.state;
|
||||||
if (!room.id) {
|
if (room.rid) {
|
||||||
try {
|
if (!room.id) {
|
||||||
const result = await RocketChat.getChannelInfo(room.rid);
|
try {
|
||||||
if (result.success) {
|
const result = await RocketChat.getChannelInfo(room.rid);
|
||||||
this.setState({ room: { ...result.channel, rid: result.channel._id } });
|
if (result.success) {
|
||||||
|
this.setState({ room: { ...result.channel, rid: result.channel._id } });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (room && room.t !== 'd' && this.canViewMembers()) {
|
if (room && room.t !== 'd' && this.canViewMembers()) {
|
||||||
try {
|
try {
|
||||||
const counters = await RocketChat.getRoomCounters(room.rid, room.t);
|
const counters = await RocketChat.getRoomCounters(room.rid, room.t);
|
||||||
if (counters.success) {
|
if (counters.success) {
|
||||||
this.setState({ membersCount: counters.members, joined: counters.joined });
|
this.setState({ membersCount: counters.members, joined: counters.joined });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} else if (room.t === 'd' && _.isEmpty(member)) {
|
||||||
log(e);
|
this.updateRoomMember();
|
||||||
}
|
}
|
||||||
} else if (room.t === 'd' && _.isEmpty(member)) {
|
|
||||||
this.updateRoomMember();
|
const canAutoTranslate = await RocketChat.canAutoTranslate();
|
||||||
|
this.setState({ canAutoTranslate });
|
||||||
|
|
||||||
|
this.canAddUser();
|
||||||
|
this.canInviteUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
const canAutoTranslate = await RocketChat.canAutoTranslate();
|
|
||||||
this.setState({ canAutoTranslate });
|
|
||||||
|
|
||||||
this.canAddUser();
|
|
||||||
this.canInviteUser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -384,11 +386,10 @@ class RoomActionsView extends React.Component {
|
||||||
|
|
||||||
updateRoomMember = async() => {
|
updateRoomMember = async() => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { user } = this.props;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!RocketChat.isGroupChat(room)) {
|
if (!RocketChat.isGroupChat(room)) {
|
||||||
const roomUserId = RocketChat.getUidDirectMessage(room, user.id);
|
const roomUserId = RocketChat.getUidDirectMessage(room);
|
||||||
const result = await RocketChat.getUserInfo(roomUserId);
|
const result = await RocketChat.getUserInfo(roomUserId);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.setState({ member: result.user });
|
this.setState({ member: result.user });
|
||||||
|
@ -443,7 +444,7 @@ class RoomActionsView extends React.Component {
|
||||||
|
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
I18n.t('Are_you_sure_question_mark'),
|
I18n.t('Are_you_sure_question_mark'),
|
||||||
I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: room.t === 'd' ? room.fname : room.name }),
|
I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: RocketChat.getRoomTitle(room) }),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
text: I18n.t('Cancel'),
|
text: I18n.t('Cancel'),
|
||||||
|
@ -485,7 +486,7 @@ class RoomActionsView extends React.Component {
|
||||||
: (
|
: (
|
||||||
<View style={styles.roomTitleRow}>
|
<View style={styles.roomTitleRow}>
|
||||||
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} theme={theme} />
|
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} theme={theme} />
|
||||||
<Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}>{room.prid ? room.fname : room.name}</Text>
|
<Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}>{RocketChat.getRoomTitle(room)}</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,13 +125,13 @@ class RoomInfoEditView extends React.Component {
|
||||||
|
|
||||||
init = (room) => {
|
init = (room) => {
|
||||||
const {
|
const {
|
||||||
name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired, sysMes
|
description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired, sysMes
|
||||||
} = room;
|
} = room;
|
||||||
// fake password just to user knows about it
|
// fake password just to user knows about it
|
||||||
this.randomValue = random(15);
|
this.randomValue = random(15);
|
||||||
this.setState({
|
this.setState({
|
||||||
room,
|
room,
|
||||||
name,
|
name: RocketChat.getRoomTitle(room),
|
||||||
description,
|
description,
|
||||||
topic,
|
topic,
|
||||||
announcement,
|
announcement,
|
||||||
|
|
|
@ -36,7 +36,7 @@ const getRoomTitle = (room, type, name, username, statusText, theme) => (type ==
|
||||||
: (
|
: (
|
||||||
<View style={styles.roomTitleRow}>
|
<View style={styles.roomTitleRow}>
|
||||||
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} key='room-info-type' theme={theme} />
|
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} key='room-info-type' theme={theme} />
|
||||||
<Text testID='room-info-view-name' style={[styles.roomTitle, { color: themes[theme].titleText }]} key='room-info-name'>{room.prid ? room.fname : room.name}</Text>
|
<Text testID='room-info-view-name' style={[styles.roomTitle, { color: themes[theme].titleText }]} key='room-info-name'>{RocketChat.getRoomTitle(room)}</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -37,7 +37,6 @@ const RoomHeaderLeft = ({
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
token={token}
|
token={token}
|
||||||
theme={theme}
|
|
||||||
onPress={goRoomActionsView}
|
onPress={goRoomActionsView}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -101,8 +101,10 @@ const mapStateToProps = (state, ownProps) => {
|
||||||
if (type === 'd') {
|
if (type === 'd') {
|
||||||
const user = getUserSelector(state);
|
const user = getUserSelector(state);
|
||||||
if (user.id) {
|
if (user.id) {
|
||||||
if (state.activeUsers[roomUserId]) {
|
if (state.activeUsers[roomUserId] && state.meteor.connected) {
|
||||||
({ status, statusText } = state.activeUsers[roomUserId]);
|
({ status, statusText } = state.activeUsers[roomUserId]);
|
||||||
|
} else {
|
||||||
|
status = 'offline';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ class List extends React.Component {
|
||||||
this.messagesSubscription = this.messagesObservable
|
this.messagesSubscription = this.messagesObservable
|
||||||
.subscribe((data) => {
|
.subscribe((data) => {
|
||||||
this.interaction = InteractionManager.runAfterInteractions(() => {
|
this.interaction = InteractionManager.runAfterInteractions(() => {
|
||||||
if (tmid) {
|
if (tmid && this.thread) {
|
||||||
data = [this.thread, ...data];
|
data = [this.thread, ...data];
|
||||||
}
|
}
|
||||||
const messages = orderBy(data, ['ts'], ['desc']);
|
const messages = orderBy(data, ['ts'], ['desc']);
|
||||||
|
|
|
@ -34,7 +34,8 @@ import { themes } from '../../constants/colors';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import ReactionsModal from '../../containers/ReactionsModal';
|
import ReactionsModal from '../../containers/ReactionsModal';
|
||||||
import { LISTENER } from '../../containers/Toast';
|
import { LISTENER } from '../../containers/Toast';
|
||||||
import { isReadOnly, isBlocked } from '../../utils/room';
|
import { isBlocked } from '../../utils/room';
|
||||||
|
import { isReadOnly } from '../../utils/isReadOnly';
|
||||||
import { isIOS, isTablet } from '../../utils/deviceInfo';
|
import { isIOS, isTablet } from '../../utils/deviceInfo';
|
||||||
import { showErrorAlert } from '../../utils/info';
|
import { showErrorAlert } from '../../utils/info';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
|
@ -65,9 +66,10 @@ const stateAttrsUpdate = [
|
||||||
'editing',
|
'editing',
|
||||||
'replying',
|
'replying',
|
||||||
'reacting',
|
'reacting',
|
||||||
|
'readOnly',
|
||||||
'member'
|
'member'
|
||||||
];
|
];
|
||||||
const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'muted', 'jitsiTimeout', 'announcement', 'sysMes', 'topic', 'name', 'fname'];
|
const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'muted', 'jitsiTimeout', 'announcement', 'sysMes', 'topic', 'name', 'fname', 'roles'];
|
||||||
|
|
||||||
class RoomView extends React.Component {
|
class RoomView extends React.Component {
|
||||||
static navigationOptions = ({ navigation, screenProps }) => {
|
static navigationOptions = ({ navigation, screenProps }) => {
|
||||||
|
@ -164,6 +166,7 @@ class RoomView extends React.Component {
|
||||||
const selectedMessage = props.navigation.getParam('message');
|
const selectedMessage = props.navigation.getParam('message');
|
||||||
const name = props.navigation.getParam('name');
|
const name = props.navigation.getParam('name');
|
||||||
const fname = props.navigation.getParam('fname');
|
const fname = props.navigation.getParam('fname');
|
||||||
|
const search = props.navigation.getParam('search');
|
||||||
const prid = props.navigation.getParam('prid');
|
const prid = props.navigation.getParam('prid');
|
||||||
this.state = {
|
this.state = {
|
||||||
joined: true,
|
joined: true,
|
||||||
|
@ -183,7 +186,7 @@ class RoomView extends React.Component {
|
||||||
replying: !!selectedMessage,
|
replying: !!selectedMessage,
|
||||||
replyWithMention: false,
|
replyWithMention: false,
|
||||||
reacting: false,
|
reacting: false,
|
||||||
announcement: null
|
readOnly: false
|
||||||
};
|
};
|
||||||
|
|
||||||
if (room && room.observe) {
|
if (room && room.observe) {
|
||||||
|
@ -192,6 +195,12 @@ class RoomView extends React.Component {
|
||||||
this.findAndObserveRoom(this.rid);
|
this.findAndObserveRoom(this.rid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setReadOnly();
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
this.updateRoom();
|
||||||
|
}
|
||||||
|
|
||||||
this.messagebox = React.createRef();
|
this.messagebox = React.createRef();
|
||||||
this.list = React.createRef();
|
this.list = React.createRef();
|
||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
|
@ -209,7 +218,7 @@ class RoomView extends React.Component {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if ((room.id || room.rid) && !this.tmid) {
|
if ((room.id || room.rid) && !this.tmid) {
|
||||||
navigation.setParams({
|
navigation.setParams({
|
||||||
name: this.getRoomTitle(room),
|
name: RocketChat.getRoomTitle(room),
|
||||||
subtitle: room.topic,
|
subtitle: room.topic,
|
||||||
avatar: room.name,
|
avatar: room.name,
|
||||||
t: room.t,
|
t: room.t,
|
||||||
|
@ -222,10 +231,13 @@ class RoomView extends React.Component {
|
||||||
if (this.tmid) {
|
if (this.tmid) {
|
||||||
navigation.setParams({ toggleFollowThread: this.toggleFollowThread, goRoomActionsView: this.goRoomActionsView });
|
navigation.setParams({ toggleFollowThread: this.toggleFollowThread, goRoomActionsView: this.goRoomActionsView });
|
||||||
}
|
}
|
||||||
if (isAuthenticated && this.rid) {
|
if (this.rid) {
|
||||||
this.init();
|
this.sub.subscribe();
|
||||||
} else if (this.rid) {
|
if (isAuthenticated) {
|
||||||
EventEmitter.addEventListener('connected', this.handleConnected);
|
this.init();
|
||||||
|
} else {
|
||||||
|
EventEmitter.addEventListener('connected', this.handleConnected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isIOS && this.rid) {
|
if (isIOS && this.rid) {
|
||||||
this.updateUnreadCount();
|
this.updateUnreadCount();
|
||||||
|
@ -275,9 +287,12 @@ class RoomView extends React.Component {
|
||||||
if (roomUpdate.topic !== prevState.roomUpdate.topic) {
|
if (roomUpdate.topic !== prevState.roomUpdate.topic) {
|
||||||
navigation.setParams({ subtitle: roomUpdate.topic });
|
navigation.setParams({ subtitle: roomUpdate.topic });
|
||||||
}
|
}
|
||||||
|
if (!isEqual(prevState.roomUpdate.roles, roomUpdate.roles)) {
|
||||||
|
this.setReadOnly();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (((roomUpdate.fname !== prevState.roomUpdate.fname) || (roomUpdate.name !== prevState.roomUpdate.name)) && !this.tmid) {
|
if (((roomUpdate.fname !== prevState.roomUpdate.fname) || (roomUpdate.name !== prevState.roomUpdate.name)) && !this.tmid) {
|
||||||
navigation.setParams({ name: this.getRoomTitle(room) });
|
navigation.setParams({ name: RocketChat.getRoomTitle(room) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,6 +358,32 @@ class RoomView extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setReadOnly = async() => {
|
||||||
|
const { room } = this.state;
|
||||||
|
const { user } = this.props;
|
||||||
|
const readOnly = await isReadOnly(room, user);
|
||||||
|
this.setState({ readOnly });
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRoom = async() => {
|
||||||
|
const db = database.active;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const subCollection = db.collections.get('subscriptions');
|
||||||
|
const sub = await subCollection.find(this.rid);
|
||||||
|
|
||||||
|
const { room } = await RocketChat.getRoomInfo(this.rid);
|
||||||
|
|
||||||
|
await db.action(async() => {
|
||||||
|
await sub.update((s) => {
|
||||||
|
Object.assign(s, room);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init = async() => {
|
init = async() => {
|
||||||
try {
|
try {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
@ -361,7 +402,6 @@ class RoomView extends React.Component {
|
||||||
this.setLastOpen(null);
|
this.setLastOpen(null);
|
||||||
}
|
}
|
||||||
RocketChat.readMessages(room.rid, newLastOpen, true).catch(e => console.log(e));
|
RocketChat.readMessages(room.rid, newLastOpen, true).catch(e => console.log(e));
|
||||||
this.sub.subscribe();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,10 +428,10 @@ class RoomView extends React.Component {
|
||||||
const { t } = room;
|
const { t } = room;
|
||||||
|
|
||||||
if (t === 'd' && !RocketChat.isGroupChat(room)) {
|
if (t === 'd' && !RocketChat.isGroupChat(room)) {
|
||||||
const { user, navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const roomUserId = RocketChat.getUidDirectMessage(room, user.id);
|
const roomUserId = RocketChat.getUidDirectMessage(room);
|
||||||
|
|
||||||
navigation.setParams({ roomUserId });
|
navigation.setParams({ roomUserId });
|
||||||
|
|
||||||
|
@ -416,7 +456,7 @@ class RoomView extends React.Component {
|
||||||
this.setState({ room });
|
this.setState({ room });
|
||||||
if (!this.tmid) {
|
if (!this.tmid) {
|
||||||
navigation.setParams({
|
navigation.setParams({
|
||||||
name: this.getRoomTitle(room),
|
name: RocketChat.getRoomTitle(room),
|
||||||
subtitle: room.topic,
|
subtitle: room.topic,
|
||||||
avatar: room.name,
|
avatar: room.name,
|
||||||
t: room.t
|
t: room.t
|
||||||
|
@ -605,7 +645,7 @@ class RoomView extends React.Component {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
if (rid === this.rid) {
|
if (rid === this.rid) {
|
||||||
Navigation.navigate('RoomsListView');
|
Navigation.navigate('RoomsListView');
|
||||||
showErrorAlert(I18n.t('You_were_removed_from_channel', { channel: this.getRoomTitle(room) }), I18n.t('Oops'));
|
showErrorAlert(I18n.t('You_were_removed_from_channel', { channel: RocketChat.getRoomTitle(room) }), I18n.t('Oops'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,11 +667,6 @@ class RoomView extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
getRoomTitle = (room) => {
|
|
||||||
const { useRealName } = this.props;
|
|
||||||
return ((room.prid || useRealName) && room.fname) || room.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMessages = () => {
|
getMessages = () => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
if (room.lastOpen) {
|
if (room.lastOpen) {
|
||||||
|
@ -668,7 +703,6 @@ class RoomView extends React.Component {
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
fetchThreadName = async(tmid, messageId) => {
|
fetchThreadName = async(tmid, messageId) => {
|
||||||
try {
|
try {
|
||||||
const { room } = this.state;
|
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const threadCollection = db.collections.get('threads');
|
const threadCollection = db.collections.get('threads');
|
||||||
const messageCollection = db.collections.get('messages');
|
const messageCollection = db.collections.get('messages');
|
||||||
|
@ -691,7 +725,7 @@ class RoomView extends React.Component {
|
||||||
await db.batch(
|
await db.batch(
|
||||||
threadCollection.prepareCreate((t) => {
|
threadCollection.prepareCreate((t) => {
|
||||||
t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema);
|
t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema);
|
||||||
t.subscription.set(room);
|
t.subscription.id = this.rid;
|
||||||
Object.assign(t, thread);
|
Object.assign(t, thread);
|
||||||
}),
|
}),
|
||||||
messageRecord.prepareUpdate((m) => {
|
messageRecord.prepareUpdate((m) => {
|
||||||
|
@ -701,7 +735,7 @@ class RoomView extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
// log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -761,12 +795,6 @@ class RoomView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isReadOnly() {
|
|
||||||
const { room } = this.state;
|
|
||||||
const { user } = this.props;
|
|
||||||
return isReadOnly(room, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
blockAction = ({
|
blockAction = ({
|
||||||
actionId, appId, value, blockId, rid, mid
|
actionId, appId, value, blockId, rid, mid
|
||||||
}) => RocketChat.triggerBlockAction({
|
}) => RocketChat.triggerBlockAction({
|
||||||
|
@ -854,7 +882,7 @@ class RoomView extends React.Component {
|
||||||
|
|
||||||
renderFooter = () => {
|
renderFooter = () => {
|
||||||
const {
|
const {
|
||||||
joined, room, selectedMessage, editing, replying, replyWithMention
|
joined, room, selectedMessage, editing, replying, replyWithMention, readOnly
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { navigation, theme } = this.props;
|
const { navigation, theme } = this.props;
|
||||||
|
|
||||||
|
@ -875,7 +903,7 @@ class RoomView extends React.Component {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.isReadOnly || room.archived) {
|
if (readOnly) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.readOnly}>
|
<View style={styles.readOnly}>
|
||||||
<Text style={[styles.previewMode, { color: themes[theme].titleText }]} accessibilityLabel={I18n.t('This_room_is_read_only')}>{I18n.t('This_room_is_read_only')}</Text>
|
<Text style={[styles.previewMode, { color: themes[theme].titleText }]} accessibilityLabel={I18n.t('This_room_is_read_only')}>{I18n.t('This_room_is_read_only')}</Text>
|
||||||
|
@ -913,7 +941,7 @@ class RoomView extends React.Component {
|
||||||
|
|
||||||
renderActions = () => {
|
renderActions = () => {
|
||||||
const {
|
const {
|
||||||
room, selectedMessage, showActions, showErrorActions, joined
|
room, selectedMessage, showActions, showErrorActions, joined, readOnly
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
user, navigation
|
user, navigation
|
||||||
|
@ -934,7 +962,7 @@ class RoomView extends React.Component {
|
||||||
editInit={this.onEditInit}
|
editInit={this.onEditInit}
|
||||||
replyInit={this.onReplyInit}
|
replyInit={this.onReplyInit}
|
||||||
reactionInit={this.onReactionInit}
|
reactionInit={this.onReactionInit}
|
||||||
isReadOnly={this.isReadOnly}
|
isReadOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
|
|
|
@ -174,6 +174,7 @@ class RoomsListView extends React.Component {
|
||||||
roomsRequest: PropTypes.func,
|
roomsRequest: PropTypes.func,
|
||||||
closeServerDropdown: PropTypes.func,
|
closeServerDropdown: PropTypes.func,
|
||||||
useRealName: PropTypes.bool,
|
useRealName: PropTypes.bool,
|
||||||
|
connected: PropTypes.bool,
|
||||||
split: PropTypes.bool
|
split: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -211,13 +212,17 @@ class RoomsListView extends React.Component {
|
||||||
this.willFocusListener = navigation.addListener('willFocus', () => {
|
this.willFocusListener = navigation.addListener('willFocus', () => {
|
||||||
// Check if there were changes while not focused (it's set on sCU)
|
// Check if there were changes while not focused (it's set on sCU)
|
||||||
if (this.shouldUpdate) {
|
if (this.shouldUpdate) {
|
||||||
// animateNextTransition();
|
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
this.shouldUpdate = false;
|
this.shouldUpdate = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.didFocusListener = navigation.addListener('didFocus', () => {
|
this.didFocusListener = navigation.addListener('didFocus', () => {
|
||||||
this.animated = true;
|
this.animated = true;
|
||||||
|
// Check if there were changes while not focused (it's set on sCU)
|
||||||
|
if (this.shouldUpdate) {
|
||||||
|
this.forceUpdate();
|
||||||
|
this.shouldUpdate = false;
|
||||||
|
}
|
||||||
this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
||||||
});
|
});
|
||||||
this.willBlurListener = navigation.addListener('willBlur', () => {
|
this.willBlurListener = navigation.addListener('willBlur', () => {
|
||||||
|
@ -302,6 +307,7 @@ class RoomsListView extends React.Component {
|
||||||
showFavorites,
|
showFavorites,
|
||||||
showUnread,
|
showUnread,
|
||||||
appState,
|
appState,
|
||||||
|
connected,
|
||||||
roomsRequest
|
roomsRequest
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -317,6 +323,7 @@ class RoomsListView extends React.Component {
|
||||||
} else if (
|
} else if (
|
||||||
appState === 'foreground'
|
appState === 'foreground'
|
||||||
&& appState !== prevProps.appState
|
&& appState !== prevProps.appState
|
||||||
|
&& connected
|
||||||
) {
|
) {
|
||||||
roomsRequest();
|
roomsRequest();
|
||||||
}
|
}
|
||||||
|
@ -528,10 +535,7 @@ class RoomsListView extends React.Component {
|
||||||
|
|
||||||
getUserPresence = uid => RocketChat.getUserPresence(uid)
|
getUserPresence = uid => RocketChat.getUserPresence(uid)
|
||||||
|
|
||||||
getUidDirectMessage = (room) => {
|
getUidDirectMessage = room => RocketChat.getUidDirectMessage(room);
|
||||||
const { user: { id } } = this.props;
|
|
||||||
return RocketChat.getUidDirectMessage(room, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
goRoom = (item) => {
|
goRoom = (item) => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
|
@ -542,12 +546,17 @@ class RoomsListView extends React.Component {
|
||||||
name: this.getRoomTitle(item),
|
name: this.getRoomTitle(item),
|
||||||
t: item.t,
|
t: item.t,
|
||||||
prid: item.prid,
|
prid: item.prid,
|
||||||
roomUserId: this.getUidDirectMessage(item),
|
room: item,
|
||||||
room: item
|
search: item.search,
|
||||||
|
roomUserId: this.getUidDirectMessage(item)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPressItem = async(item = {}) => {
|
_onPressItem = async(item = {}) => {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
if (!navigation.isFocused()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!item.search) {
|
if (!item.search) {
|
||||||
return this.goRoom(item);
|
return this.goRoom(item);
|
||||||
}
|
}
|
||||||
|
@ -894,6 +903,7 @@ class RoomsListView extends React.Component {
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
||||||
server: state.server.server,
|
server: state.server.server,
|
||||||
|
connected: state.server.connected,
|
||||||
searchText: state.rooms.searchText,
|
searchText: state.rooms.searchText,
|
||||||
loadingServer: state.server.loading,
|
loadingServer: state.server.loading,
|
||||||
showServerDropdown: state.rooms.showServerDropdown,
|
showServerDropdown: state.rooms.showServerDropdown,
|
||||||
|
|
|
@ -13,7 +13,8 @@ import styles from './styles';
|
||||||
import TextInput from '../../containers/TextInput';
|
import TextInput from '../../containers/TextInput';
|
||||||
import ActivityIndicator from '../../containers/ActivityIndicator';
|
import ActivityIndicator from '../../containers/ActivityIndicator';
|
||||||
import { CustomHeaderButtons, Item } from '../../containers/HeaderButton';
|
import { CustomHeaderButtons, Item } from '../../containers/HeaderButton';
|
||||||
import { isReadOnly, isBlocked } from '../../utils/room';
|
import { isBlocked } from '../../utils/room';
|
||||||
|
import { isReadOnly } from '../../utils/isReadOnly';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import { themedHeader } from '../../utils/navigation';
|
import { themedHeader } from '../../utils/navigation';
|
||||||
|
|
||||||
|
@ -69,18 +70,29 @@ class ShareView extends React.Component {
|
||||||
fileInfo,
|
fileInfo,
|
||||||
room,
|
room,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
readOnly: false,
|
||||||
file: {
|
file: {
|
||||||
name: fileInfo ? fileInfo.name : '',
|
name: fileInfo ? fileInfo.name : '',
|
||||||
description: ''
|
description: ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.setReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
navigation.setParams({ sendMessage: this._sendMessage });
|
||||||
|
}
|
||||||
|
|
||||||
|
setReadOnly = async() => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { navigation, user } = this.props;
|
const { navigation, user } = this.props;
|
||||||
const { username } = user;
|
const { username } = user;
|
||||||
navigation.setParams({ sendMessage: this._sendMessage, canSend: !(isReadOnly(room, { username }) || isBlocked(room)) });
|
const readOnly = await isReadOnly(room, { username });
|
||||||
|
|
||||||
|
this.setState({ readOnly });
|
||||||
|
navigation.setParams({ canSend: !(readOnly || isBlocked(room)) });
|
||||||
}
|
}
|
||||||
|
|
||||||
bytesToSize = bytes => `${ (bytes / 1048576).toFixed(2) }MB`;
|
bytesToSize = bytes => `${ (bytes / 1048576).toFixed(2) }MB`;
|
||||||
|
@ -237,8 +249,9 @@ class ShareView extends React.Component {
|
||||||
|
|
||||||
renderError = () => {
|
renderError = () => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
|
const { theme } = this.props;
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, styles.centered]}>
|
<View style={[styles.container, styles.centered, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||||
<Text style={styles.title}>
|
<Text style={styles.title}>
|
||||||
{
|
{
|
||||||
isBlocked(room) ? I18n.t('This_room_is_blocked') : I18n.t('This_room_is_read_only')
|
isBlocked(room) ? I18n.t('This_room_is_blocked') : I18n.t('This_room_is_read_only')
|
||||||
|
@ -249,13 +262,12 @@ class ShareView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { user, theme } = this.props;
|
const { theme } = this.props;
|
||||||
const { username } = user;
|
|
||||||
const {
|
const {
|
||||||
name, loading, isMedia, room
|
name, loading, isMedia, room, readOnly
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
if (isReadOnly(room, { username }) || isBlocked(room)) {
|
if (readOnly || isBlocked(room)) {
|
||||||
return this.renderError();
|
return this.renderError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ import { withSplit } from '../split';
|
||||||
import { themedHeader } from '../utils/navigation';
|
import { themedHeader } from '../utils/navigation';
|
||||||
import { getUserSelector } from '../selectors/login';
|
import { getUserSelector } from '../selectors/login';
|
||||||
import { CustomHeaderButtons, Item, CancelModalButton } from '../containers/HeaderButton';
|
import { CustomHeaderButtons, Item, CancelModalButton } from '../containers/HeaderButton';
|
||||||
|
import store from '../lib/createStore';
|
||||||
|
import { setUser } from '../actions/login';
|
||||||
|
|
||||||
const STATUS = [{
|
const STATUS = [{
|
||||||
id: 'online',
|
id: 'online',
|
||||||
|
@ -75,6 +77,7 @@ class StatusView extends React.Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
|
id: PropTypes.string,
|
||||||
status: PropTypes.string,
|
status: PropTypes.string,
|
||||||
statusText: PropTypes.string
|
statusText: PropTypes.string
|
||||||
}),
|
}),
|
||||||
|
@ -112,11 +115,12 @@ class StatusView extends React.Component {
|
||||||
|
|
||||||
setCustomStatus = async() => {
|
setCustomStatus = async() => {
|
||||||
const { statusText } = this.state;
|
const { statusText } = this.state;
|
||||||
|
const { user } = this.props;
|
||||||
|
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await RocketChat.setUserStatus(statusText);
|
const result = await RocketChat.setUserStatus(user.status, statusText);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
EventEmitter.emit(LISTENER, { message: I18n.t('Status_saved_successfully') });
|
EventEmitter.emit(LISTENER, { message: I18n.t('Status_saved_successfully') });
|
||||||
} else {
|
} else {
|
||||||
|
@ -163,6 +167,7 @@ class StatusView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem = ({ item }) => {
|
renderItem = ({ item }) => {
|
||||||
|
const { statusText } = this.state;
|
||||||
const { theme, user } = this.props;
|
const { theme, user } = this.props;
|
||||||
const { id, name } = item;
|
const { id, name } = item;
|
||||||
return (
|
return (
|
||||||
|
@ -171,7 +176,10 @@ class StatusView extends React.Component {
|
||||||
onPress={async() => {
|
onPress={async() => {
|
||||||
if (user.status !== item.id) {
|
if (user.status !== item.id) {
|
||||||
try {
|
try {
|
||||||
await RocketChat.setUserPresenceDefaultStatus(item.id);
|
const result = await RocketChat.setUserStatus(item.id, statusText);
|
||||||
|
if (result.success) {
|
||||||
|
store.dispatch(setUser({ status: item.id }));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,12 +71,10 @@ class ThreadMessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
console.countReset(`${ this.constructor.name }.render calls`);
|
||||||
if (this.mountInteraction && this.mountInteraction.cancel) {
|
if (this.mountInteraction && this.mountInteraction.cancel) {
|
||||||
this.mountInteraction.cancel();
|
this.mountInteraction.cancel();
|
||||||
}
|
}
|
||||||
if (this.loadInteraction && this.loadInteraction.cancel) {
|
|
||||||
this.loadInteraction.cancel();
|
|
||||||
}
|
|
||||||
if (this.syncInteraction && this.syncInteraction.cancel) {
|
if (this.syncInteraction && this.syncInteraction.cancel) {
|
||||||
this.syncInteraction.cancel();
|
this.syncInteraction.cancel();
|
||||||
}
|
}
|
||||||
|
@ -89,13 +87,14 @@ class ThreadMessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
subscribeData = () => {
|
subscribeData = async() => {
|
||||||
try {
|
try {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
this.subObservable = db.collections
|
const subscription = await db.collections
|
||||||
.get('subscriptions')
|
.get('subscriptions')
|
||||||
.findAndObserve(this.rid);
|
.find(this.rid);
|
||||||
this.subSubscription = this.subObservable
|
const observable = subscription.observe();
|
||||||
|
this.subSubscription = observable
|
||||||
.subscribe((data) => {
|
.subscribe((data) => {
|
||||||
this.subscription = data;
|
this.subscription = data;
|
||||||
});
|
});
|
||||||
|
@ -116,14 +115,14 @@ class ThreadMessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
// Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
init = () => {
|
init = () => {
|
||||||
if (!this.subscription) {
|
if (!this.subscription) {
|
||||||
return;
|
this.load();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const lastThreadSync = new Date();
|
const lastThreadSync = new Date();
|
||||||
|
@ -138,6 +137,13 @@ class ThreadMessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateThreads = async({ update, remove, lastThreadSync }) => {
|
updateThreads = async({ update, remove, lastThreadSync }) => {
|
||||||
|
// if there's no subscription, manage data on this.state.messages
|
||||||
|
// note: sync will never be called without subscription
|
||||||
|
if (!this.subscription) {
|
||||||
|
this.setState(({ messages }) => ({ messages: [...messages, ...update] }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const threadsCollection = db.collections.get('threads');
|
const threadsCollection = db.collections.get('threads');
|
||||||
|
@ -198,13 +204,10 @@ class ThreadMessagesView extends React.Component {
|
||||||
rid: this.rid, count: API_FETCH_COUNT, offset: messages.length
|
rid: this.rid, count: API_FETCH_COUNT, offset: messages.length
|
||||||
});
|
});
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.loadInteraction = InteractionManager.runAfterInteractions(() => {
|
this.updateThreads({ update: result.threads, lastThreadSync });
|
||||||
this.updateThreads({ update: result.threads, lastThreadSync });
|
this.setState({
|
||||||
|
loading: false,
|
||||||
this.setState({
|
end: result.count < API_FETCH_COUNT
|
||||||
loading: false,
|
|
||||||
end: result.count < API_FETCH_COUNT
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -319,6 +322,7 @@ class ThreadMessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
console.count(`${ this.constructor.name }.render calls`);
|
||||||
const { loading, messages } = this.state;
|
const { loading, messages } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
|
|
||||||
|
@ -335,7 +339,7 @@ class ThreadMessagesView extends React.Component {
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]}
|
style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]}
|
||||||
contentContainerStyle={styles.contentContainer}
|
contentContainerStyle={styles.contentContainer}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={item => item._id}
|
||||||
onEndReached={this.load}
|
onEndReached={this.load}
|
||||||
onEndReachedThreshold={0.5}
|
onEndReachedThreshold={0.5}
|
||||||
maxToRenderPerBatch={5}
|
maxToRenderPerBatch={5}
|
||||||
|
|
|
@ -309,11 +309,6 @@ PODS:
|
||||||
- React
|
- React
|
||||||
- react-native-slider (2.0.5):
|
- react-native-slider (2.0.5):
|
||||||
- React
|
- React
|
||||||
- react-native-video (5.0.2):
|
|
||||||
- React
|
|
||||||
- react-native-video/Video (= 5.0.2)
|
|
||||||
- react-native-video/Video (5.0.2):
|
|
||||||
- React
|
|
||||||
- react-native-webview (7.5.1):
|
- react-native-webview (7.5.1):
|
||||||
- React
|
- React
|
||||||
- React-RCTActionSheet (0.61.5):
|
- React-RCTActionSheet (0.61.5):
|
||||||
|
@ -408,6 +403,7 @@ PODS:
|
||||||
- SDWebImageWebPCoder (0.2.5):
|
- SDWebImageWebPCoder (0.2.5):
|
||||||
- libwebp (~> 1.0)
|
- libwebp (~> 1.0)
|
||||||
- SDWebImage/Core (~> 5.0)
|
- SDWebImage/Core (~> 5.0)
|
||||||
|
- SocketRocket (0.5.1)
|
||||||
- UMBarCodeScannerInterface (3.0.0)
|
- UMBarCodeScannerInterface (3.0.0)
|
||||||
- UMCameraInterface (3.0.0)
|
- UMCameraInterface (3.0.0)
|
||||||
- UMConstantsInterface (3.0.0)
|
- UMConstantsInterface (3.0.0)
|
||||||
|
@ -462,7 +458,6 @@ DEPENDENCIES:
|
||||||
- react-native-notifications (from `../node_modules/react-native-notifications`)
|
- react-native-notifications (from `../node_modules/react-native-notifications`)
|
||||||
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
|
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
|
||||||
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
|
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
|
||||||
- react-native-video (from `../node_modules/react-native-video`)
|
|
||||||
- react-native-webview (from `../node_modules/react-native-webview`)
|
- react-native-webview (from `../node_modules/react-native-webview`)
|
||||||
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
|
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
|
||||||
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
|
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
|
||||||
|
@ -492,6 +487,7 @@ DEPENDENCIES:
|
||||||
- RNScreens (from `../node_modules/react-native-screens`)
|
- RNScreens (from `../node_modules/react-native-screens`)
|
||||||
- RNUserDefaults (from `../node_modules/rn-user-defaults`)
|
- RNUserDefaults (from `../node_modules/rn-user-defaults`)
|
||||||
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
|
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
|
||||||
|
- SocketRocket (from `../node_modules/detox/ios_src/SocketRocket`)
|
||||||
- UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`)
|
- UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`)
|
||||||
- UMCameraInterface (from `../node_modules/unimodules-camera-interface/ios`)
|
- UMCameraInterface (from `../node_modules/unimodules-camera-interface/ios`)
|
||||||
- UMConstantsInterface (from `../node_modules/unimodules-constants-interface/ios`)
|
- UMConstantsInterface (from `../node_modules/unimodules-constants-interface/ios`)
|
||||||
|
@ -605,8 +601,6 @@ EXTERNAL SOURCES:
|
||||||
:path: "../node_modules/react-native-orientation-locker"
|
:path: "../node_modules/react-native-orientation-locker"
|
||||||
react-native-slider:
|
react-native-slider:
|
||||||
:path: "../node_modules/@react-native-community/slider"
|
:path: "../node_modules/@react-native-community/slider"
|
||||||
react-native-video:
|
|
||||||
:path: "../node_modules/react-native-video"
|
|
||||||
react-native-webview:
|
react-native-webview:
|
||||||
:path: "../node_modules/react-native-webview"
|
:path: "../node_modules/react-native-webview"
|
||||||
React-RCTActionSheet:
|
React-RCTActionSheet:
|
||||||
|
@ -663,6 +657,8 @@ EXTERNAL SOURCES:
|
||||||
:path: "../node_modules/rn-user-defaults"
|
:path: "../node_modules/rn-user-defaults"
|
||||||
RNVectorIcons:
|
RNVectorIcons:
|
||||||
:path: "../node_modules/react-native-vector-icons"
|
:path: "../node_modules/react-native-vector-icons"
|
||||||
|
SocketRocket:
|
||||||
|
:path: "../node_modules/detox/ios_src/SocketRocket"
|
||||||
UMBarCodeScannerInterface:
|
UMBarCodeScannerInterface:
|
||||||
:path: !ruby/object:Pathname
|
:path: !ruby/object:Pathname
|
||||||
path: "../node_modules/unimodules-barcode-scanner-interface/ios"
|
path: "../node_modules/unimodules-barcode-scanner-interface/ios"
|
||||||
|
@ -759,7 +755,6 @@ SPEC CHECKSUMS:
|
||||||
react-native-notifications: 163ddedac6fcc8d850ea15b06abdadcacdff00f1
|
react-native-notifications: 163ddedac6fcc8d850ea15b06abdadcacdff00f1
|
||||||
react-native-orientation-locker: 23918c400376a7043e752c639c122fcf6bce8f1c
|
react-native-orientation-locker: 23918c400376a7043e752c639c122fcf6bce8f1c
|
||||||
react-native-slider: 39208600e44f885e2d2c0510b5c6435a0f62d087
|
react-native-slider: 39208600e44f885e2d2c0510b5c6435a0f62d087
|
||||||
react-native-video: d01ed7ff1e38fa7dcc6c15c94cf505e661b7bfd0
|
|
||||||
react-native-webview: 2aadbfef6b9eaa9e89b306ae3e31e6e870a6306d
|
react-native-webview: 2aadbfef6b9eaa9e89b306ae3e31e6e870a6306d
|
||||||
React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76
|
React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76
|
||||||
React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360
|
React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360
|
||||||
|
@ -791,6 +786,7 @@ SPEC CHECKSUMS:
|
||||||
RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a
|
RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a
|
||||||
SDWebImage: 4d5c027c935438f341ed33dbac53ff9f479922ca
|
SDWebImage: 4d5c027c935438f341ed33dbac53ff9f479922ca
|
||||||
SDWebImageWebPCoder: 947093edd1349d820c40afbd9f42acb6cdecd987
|
SDWebImageWebPCoder: 947093edd1349d820c40afbd9f42acb6cdecd987
|
||||||
|
SocketRocket: dbb1554b8fc288ef8ef370d6285aeca7361be31e
|
||||||
UMBarCodeScannerInterface: 84ea2d6b58ff0dc27ef9b68bab71286be18ee020
|
UMBarCodeScannerInterface: 84ea2d6b58ff0dc27ef9b68bab71286be18ee020
|
||||||
UMCameraInterface: 26b26005d1756a0d5f4f04f1e168e39ea9154535
|
UMCameraInterface: 26b26005d1756a0d5f4f04f1e168e39ea9154535
|
||||||
UMConstantsInterface: 038bacb19de12b6fd328c589122c8dc977cccf61
|
UMConstantsInterface: 038bacb19de12b6fd328c589122c8dc977cccf61
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/NSRunLoop+SRWebSocket.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/NSRunLoop+SRWebSocketPrivate.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/NSURLRequest+SRWebSocket.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/NSURLRequest+SRWebSocketPrivate.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/SRConstants.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/Delegate/SRDelegateController.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/Utilities/SRError.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/Utilities/SRHTTPConnectMessage.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/Utilities/SRHash.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/IOConsumer/SRIOConsumer.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/Utilities/SRLog.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/Utilities/SRMutex.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/Security/SRPinningSecurityPolicy.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/Proxy/SRProxyConnect.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/Utilities/SRRandom.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/RunLoop/SRRunLoopThread.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/Utilities/SRSIMDHelpers.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/SRSecurityPolicy.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/Internal/Utilities/SRURLUtilities.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/detox/ios_src/SocketRocket/SocketRocket/SRWebSocket.h
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue