[NEW] Message layout (#426)
* message container/component * Separator component * Reply * Url * tests updated * Minor changes * Audio component * Broadcast button * Minor touches * Reply preview * Edited * Minor bug fixes * - Update roadmap - Bump version to 1.2 * Onboarding styles fix
42
README.md
|
@ -50,37 +50,35 @@ Readme will guide you on how to config.
|
||||||
|
|
||||||
### Current priorities
|
### Current priorities
|
||||||
|
|
||||||
1) Onboarding ([#392][i392])
|
1) Open PDF and other file types ([#341][i341])
|
||||||
2) Splash screen ([#399][i399])
|
2) [NEW] Commands ([#405][i405])
|
||||||
3) Add empty chat background ([#398][i398])
|
3) Better message actions ([#329][i329])
|
||||||
4) Rooms list layout ([#395][i395])
|
4) [NEW] Login/Register/Forgot Password layout ([#400][i400])
|
||||||
5) Create channel layout ([#401][i401])
|
|
||||||
|
|
||||||
### To do
|
### To do
|
||||||
| Task | Status |
|
| Task | Status |
|
||||||
|--------------------|-----|
|
|--------------------|-----|
|
||||||
| [NEW] Reply Preview ([#311][i311]) | ✅ |
|
| [NEW] Reply Preview ([#311][i311]) | ✅ |
|
||||||
| Image upload improvements ([#368][i368]) | ✅ |
|
| Image upload improvements ([#368][i368]) | ✅ |
|
||||||
| [NEW] Onboarding ([#392][i392]) | WIP |
|
| [NEW] Onboarding ([#392][i392]) | ✅ |
|
||||||
| [NEW] Contextual bar layout ([#402][i402]) | ❌ |
|
| [NEW] Create channel layout ([#401][i401]) | ✅ |
|
||||||
| [NEW] Create channel layout ([#401][i401]) | ❌ |
|
| [NEW] Splash screen ([#399][i399]) | ✅ |
|
||||||
| [NEW] Login/Register/Forgot Password layout ([#400][i400]) | ❌ |
|
| [NEW] Add empty chat background ([#398][i398]) | ✅ |
|
||||||
| [NEW] Splash screen ([#399][i399]) | ❌ |
|
| [NEW] Message layout ([#397][i397]) | ✅ |
|
||||||
| [NEW] Add empty chat background ([#398][i398]) | ❌ |
|
| [NEW] Rooms list layout ([#395][i395]) | ✅ |
|
||||||
| [NEW] Message layout ([#397][i397]) | ❌ |
|
| Add components to Storybook ([#38][i38]) | WIP |
|
||||||
|
| Open PDF and other file types ([#341][i341]) | WIP |
|
||||||
|
| Better message actions ([#329][i329]) | ❌ |
|
||||||
| [NEW] Settings layout ([#396][i396]) | ❌ |
|
| [NEW] Settings layout ([#396][i396]) | ❌ |
|
||||||
| [NEW] Rooms list layout ([#395][i395]) | ❌ |
|
| [NEW] Contextual bar layout ([#402][i402]) | ❌ |
|
||||||
|
| [NEW] Login/Register/Forgot Password layout ([#400][i400]) | ❌ |
|
||||||
| [NEW] Commands ([#405][i405]) | ❌ |
|
| [NEW] Commands ([#405][i405]) | ❌ |
|
||||||
| [Android] Add Fastlane ([#404][i404]) | ❌ |
|
| [Android] Add Fastlane ([#404][i404]) | ❌ |
|
||||||
| [Android] Adaptive icons ([#403][i403]) | ❌ |
|
|
||||||
| [NEW] Auto versioning app on Circle CI ([#393][i393]) | ❌ |
|
| [NEW] Auto versioning app on Circle CI ([#393][i393]) | ❌ |
|
||||||
| [Android] Group notifications by room ([#391][i391]) | ❌ |
|
| [Android] Group notifications by room ([#391][i391]) | ❌ |
|
||||||
| Open PDF and other file types ([#341][i341]) | ❌ |
|
|
||||||
| Better message actions ([#329][i329]) | ❌ |
|
|
||||||
| Integrate project with code push ([#233][i233]) | ❌ |
|
| Integrate project with code push ([#233][i233]) | ❌ |
|
||||||
| Custom icons ([#210][i210]) | ❌ |
|
| Custom icons ([#210][i210]) | ❌ |
|
||||||
| Share Extension ([#69][i69]) | ❌ |
|
| Share Extension ([#69][i69]) | ❌ |
|
||||||
| Add components to Storybook ([#38][i38]) | ❌ |
|
|
||||||
| Upload files ([#2][i2]) | ❌ |
|
| Upload files ([#2][i2]) | ❌ |
|
||||||
|
|
||||||
[i2]: https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/2
|
[i2]: https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/2
|
||||||
|
@ -124,10 +122,10 @@ Readme will guide you on how to config.
|
||||||
| Messages list: load more on scroll | ✅ |
|
| Messages list: load more on scroll | ✅ |
|
||||||
| Messages list: receive new messages via subscription | ✅ |
|
| Messages list: receive new messages via subscription | ✅ |
|
||||||
| Subscriptions list | ✅ |
|
| Subscriptions list | ✅ |
|
||||||
| Segmented subscriptions list: Favorites | ❌ |
|
| Segmented subscriptions list: Favorites | ✅ |
|
||||||
| Segmented subscriptions list: Unreads | ❌ |
|
| Segmented subscriptions list: Unreads | ✅ |
|
||||||
| Segmented subscriptions list: DMs | ❌ |
|
| Segmented subscriptions list: DMs | ✅ |
|
||||||
| Segmented subscriptions list: Channels | ❌ |
|
| Segmented subscriptions list: Channels | ✅ |
|
||||||
| Subscriptions list: update user status via subscription | ✅ |
|
| Subscriptions list: update user status via subscription | ✅ |
|
||||||
| Numbers os messages unread in the Subscriptions list | ✅ |
|
| Numbers os messages unread in the Subscriptions list | ✅ |
|
||||||
| Status change | ✅ |
|
| Status change | ✅ |
|
||||||
|
@ -205,7 +203,7 @@ Readme will guide you on how to config.
|
||||||
| Localized in Portuguese (pt-BR) | ❌ |
|
| Localized in Portuguese (pt-BR) | ❌ |
|
||||||
| Localized in Russian | ✅ |
|
| Localized in Russian | ✅ |
|
||||||
| Localized in English | ✅ |
|
| Localized in English | ✅ |
|
||||||
| Full name setting | ❌ |
|
| Full name setting | ✅ |
|
||||||
| Read only rooms | ✅ |
|
| Read only rooms | ✅ |
|
||||||
| Typing status | ✅ |
|
| Typing status | ✅ |
|
||||||
| Create channel/group | ✅ |
|
| Create channel/group | ✅ |
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
module.exports = 'test-file-stub';
|
|
@ -0,0 +1,7 @@
|
||||||
|
// @flow
|
||||||
|
/* eslint-disable */
|
||||||
|
import I18nJs from 'i18n-js';
|
||||||
|
|
||||||
|
I18nJs.locale = 'en'; // a locale from your available translations
|
||||||
|
export const getLanguages = (): Promise<string[]> => Promise.resolve(['en']);
|
||||||
|
export default I18nJs;
|
|
@ -0,0 +1,5 @@
|
||||||
|
export default function() {
|
||||||
|
return {
|
||||||
|
show: () => {}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export default () => 'Video';
|
|
@ -0,0 +1 @@
|
||||||
|
export default () => 'Video';
|
|
@ -13,29 +13,28 @@ import RoomItem from '../app/presentation/RoomItem';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
const date = new Date(2017, 10, 10, 10);
|
const date = new Date(2017, 10, 10, 10);
|
||||||
|
const onPress = () => {};
|
||||||
jest.mock('react-native-img-cache', () => { return { CachedImage: 'View' } });
|
|
||||||
|
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
expect(renderer.create(<Provider store={store}><View><RoomItem type="d" _updatedAt={date} name="name" /></View></Provider>).toJSON()).toMatchSnapshot();
|
expect(renderer.create(<Provider store={store}><View><RoomItem onPress={onPress} type="d" _updatedAt={date} name="name" baseUrl="baseUrl" /></View></Provider>).toJSON()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render unread', () => {
|
it('render unread', () => {
|
||||||
expect(renderer.create(<Provider store={store}><View><RoomItem type="d" _updatedAt={date} name="name" unread={1} /></View></Provider>).toJSON()).toMatchSnapshot();
|
expect(renderer.create(<Provider store={store}><View><RoomItem onPress={onPress} type="d" _updatedAt={date} name="name" unread={1} /></View></Provider>).toJSON()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render unread +999', () => {
|
it('render unread +999', () => {
|
||||||
expect(renderer.create(<Provider store={store}><View><RoomItem type="d" _updatedAt={date} name="name" unread={1000} /></View></Provider>).toJSON()).toMatchSnapshot();
|
expect(renderer.create(<Provider store={store}><View><RoomItem onPress={onPress} type="d" _updatedAt={date} name="name" unread={1000} /></View></Provider>).toJSON()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render no icon', () => {
|
it('render no icon', () => {
|
||||||
expect(renderer.create(<Provider store={store}><View><RoomItem type="X" _updatedAt={date} name="name" /></View></Provider>).toJSON()).toMatchSnapshot();
|
expect(renderer.create(<Provider store={store}><View><RoomItem onPress={onPress} type="X" _updatedAt={date} name="name" /></View></Provider>).toJSON()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render private group', () => {
|
it('render private group', () => {
|
||||||
expect(renderer.create(<Provider store={store}><View><RoomItem type="g" _updatedAt={date} name="private-group" /> </View></Provider>).toJSON()).toMatchSnapshot();
|
expect(renderer.create(<Provider store={store}><View><RoomItem onPress={onPress} type="g" _updatedAt={date} name="private-group" /> </View></Provider>).toJSON()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render channel', () => {
|
it('render channel', () => {
|
||||||
expect(renderer.create(<Provider store={store}><View><RoomItem type="c" _updatedAt={date} name="general" /></View></Provider>).toJSON()).toMatchSnapshot();
|
expect(renderer.create(<Provider store={store}><View><RoomItem onPress={onPress} type="c" _updatedAt={date} name="general" /></View></Provider>).toJSON()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
|
@ -102,7 +102,7 @@ android {
|
||||||
minSdkVersion 19
|
minSdkVersion 19
|
||||||
targetSdkVersion 27
|
targetSdkVersion 27
|
||||||
versionCode VERSIONCODE as Integer
|
versionCode VERSIONCODE as Integer
|
||||||
versionName "1.1.1"
|
versionName "1.2"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "x86"
|
abiFilters "armeabi-v7a", "x86"
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 1023 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 275 B |
After Width: | Height: | Size: 630 B |
After Width: | Height: | Size: 687 B |
After Width: | Height: | Size: 740 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 234 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 330 B |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 436 B |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 523 B |
|
@ -79,5 +79,9 @@ export default {
|
||||||
},
|
},
|
||||||
Store_Last_Message: {
|
Store_Last_Message: {
|
||||||
type: 'valueAsBoolean'
|
type: 'valueAsBoolean'
|
||||||
|
},
|
||||||
|
UI_Use_Real_Name: {
|
||||||
|
type: 'valueAsBoolean'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
export const settingsUpdatedAt = new Date('2018-09-10');
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { StyleSheet, Text, View, ViewPropTypes } from 'react-native';
|
import { StyleSheet, Text, View, ViewPropTypes } from 'react-native';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
|
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
|
||||||
|
@ -19,13 +18,10 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(state => ({
|
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
|
||||||
}))
|
|
||||||
export default class Avatar extends React.PureComponent {
|
export default class Avatar extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
style: ViewPropTypes.style,
|
style: ViewPropTypes.style,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
avatar: PropTypes.string,
|
avatar: PropTypes.string,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ViewPropTypes, Image } from 'react-native';
|
import { ViewPropTypes, Image } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
@connect(state => ({
|
|
||||||
baseUrl: state.settings.Site_Url
|
|
||||||
}))
|
|
||||||
export default class CustomEmoji extends React.Component {
|
export default class CustomEmoji extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
baseUrl: PropTypes.string.isRequired,
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
|
|
@ -10,9 +10,9 @@ import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
|
|
||||||
const emojisPerRow = Platform.OS === 'ios' ? 8 : 9;
|
const emojisPerRow = Platform.OS === 'ios' ? 8 : 9;
|
||||||
|
|
||||||
const renderEmoji = (emoji, size) => {
|
const renderEmoji = (emoji, size, baseUrl) => {
|
||||||
if (emoji.isCustom) {
|
if (emoji.isCustom) {
|
||||||
return <CustomEmoji style={[styles.customCategoryEmoji, { height: size - 8, width: size - 8 }]} emoji={emoji} />;
|
return <CustomEmoji style={[styles.customCategoryEmoji, { height: size - 8, width: size - 8 }]} emoji={emoji} baseUrl={baseUrl} />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Text style={[styles.categoryEmoji, { height: size, width: size, fontSize: size - 14 }]}>
|
<Text style={[styles.categoryEmoji, { height: size, width: size, fontSize: size - 14 }]}>
|
||||||
|
@ -25,6 +25,7 @@ const renderEmoji = (emoji, size) => {
|
||||||
@responsive
|
@responsive
|
||||||
export default class EmojiCategory extends React.Component {
|
export default class EmojiCategory extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
emojis: PropTypes.any,
|
emojis: PropTypes.any,
|
||||||
window: PropTypes.any,
|
window: PropTypes.any,
|
||||||
onEmojiSelected: PropTypes.func,
|
onEmojiSelected: PropTypes.func,
|
||||||
|
@ -44,6 +45,7 @@ export default class EmojiCategory extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem(emoji, size) {
|
renderItem(emoji, size) {
|
||||||
|
const { baseUrl } = this.props;
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
|
@ -51,7 +53,7 @@ export default class EmojiCategory extends React.Component {
|
||||||
onPress={() => this.props.onEmojiSelected(emoji)}
|
onPress={() => this.props.onEmojiSelected(emoji)}
|
||||||
testID={`reaction-picker-${ emoji.isCustom ? emoji.content : emoji }`}
|
testID={`reaction-picker-${ emoji.isCustom ? emoji.content : emoji }`}
|
||||||
>
|
>
|
||||||
{renderEmoji(emoji, size)}
|
{renderEmoji(emoji, size, baseUrl)}
|
||||||
</TouchableOpacity>);
|
</TouchableOpacity>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ const scrollProps = {
|
||||||
|
|
||||||
export default class EmojiPicker extends Component {
|
export default class EmojiPicker extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
onEmojiSelected: PropTypes.func,
|
onEmojiSelected: PropTypes.func,
|
||||||
tabEmojiStyle: PropTypes.object,
|
tabEmojiStyle: PropTypes.object,
|
||||||
emojisPerRow: PropTypes.number,
|
emojisPerRow: PropTypes.number,
|
||||||
|
@ -110,6 +111,7 @@ export default class EmojiPicker extends Component {
|
||||||
style={styles.categoryContainer}
|
style={styles.categoryContainer}
|
||||||
size={this.props.emojisPerRow}
|
size={this.props.emojisPerRow}
|
||||||
width={this.props.width}
|
width={this.props.width}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -123,12 +125,14 @@ export default class EmojiPicker extends Component {
|
||||||
<ScrollableTabView
|
<ScrollableTabView
|
||||||
renderTabBar={() => <TabBar tabEmojiStyle={this.props.tabEmojiStyle} />}
|
renderTabBar={() => <TabBar tabEmojiStyle={this.props.tabEmojiStyle} />}
|
||||||
contentProps={scrollProps}
|
contentProps={scrollProps}
|
||||||
|
style={styles.background}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
categories.tabs.map((tab, i) => (
|
categories.tabs.map((tab, i) => (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
key={tab.category}
|
key={tab.category}
|
||||||
tabLabel={tab.tabLabel}
|
tabLabel={tab.tabLabel}
|
||||||
|
style={styles.background}
|
||||||
{...scrollProps}
|
{...scrollProps}
|
||||||
>
|
>
|
||||||
{this.renderCategory(tab.category, i)}
|
{this.renderCategory(tab.category, i)}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
|
background: {
|
||||||
|
backgroundColor: '#fff'
|
||||||
|
},
|
||||||
container: {
|
container: {
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { KeyboardRegistry } from 'react-native-keyboard-input';
|
import { KeyboardRegistry } from 'react-native-keyboard-input';
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import store from '../../lib/createStore';
|
import store from '../../lib/createStore';
|
||||||
import EmojiPicker from '../EmojiPicker';
|
import EmojiPicker from '../EmojiPicker';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
export default class EmojiKeyboard extends React.PureComponent {
|
export default class EmojiKeyboard extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const state = store.getState();
|
||||||
|
this.baseUrl = state.settings.Site_Url || state.server ? state.server.server : '';
|
||||||
|
}
|
||||||
onEmojiSelected = (emoji) => {
|
onEmojiSelected = (emoji) => {
|
||||||
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
|
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<View style={styles.emojiKeyboardContainer} testID='messagebox-keyboard-emoji'>
|
||||||
<View style={styles.emojiKeyboardContainer} testID='messagebox-keyboard-emoji'>
|
<EmojiPicker onEmojiSelected={emoji => this.onEmojiSelected(emoji)} baseUrl={this.baseUrl} />
|
||||||
<EmojiPicker onEmojiSelected={emoji => this.onEmojiSelected(emoji)} />
|
</View>
|
||||||
</View>
|
|
||||||
</Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,17 @@ import Markdown from '../message/Markdown';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flexDirection: 'row'
|
flexDirection: 'row',
|
||||||
|
marginTop: 10,
|
||||||
|
backgroundColor: '#fff'
|
||||||
},
|
},
|
||||||
messageContainer: {
|
messageContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
marginHorizontal: 15,
|
marginHorizontal: 10,
|
||||||
backgroundColor: '#F3F4F5',
|
backgroundColor: '#F3F4F5',
|
||||||
paddingHorizontal: 15,
|
paddingHorizontal: 15,
|
||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
borderRadius: 2
|
borderRadius: 4
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
@ -35,18 +37,23 @@ const styles = StyleSheet.create({
|
||||||
marginLeft: 5
|
marginLeft: 5
|
||||||
},
|
},
|
||||||
close: {
|
close: {
|
||||||
marginRight: 15
|
marginRight: 10
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
Message_TimeFormat: state.settings.Message_TimeFormat
|
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||||
|
customEmojis: state.customEmojis,
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
}))
|
}))
|
||||||
export default class ReplyPreview extends Component {
|
export default class ReplyPreview extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
message: PropTypes.object.isRequired,
|
message: PropTypes.object.isRequired,
|
||||||
Message_TimeFormat: PropTypes.string.isRequired,
|
Message_TimeFormat: PropTypes.string.isRequired,
|
||||||
close: PropTypes.func.isRequired
|
close: PropTypes.func.isRequired,
|
||||||
|
customEmojis: PropTypes.object.isRequired,
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
username: PropTypes.string.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
close = () => {
|
close = () => {
|
||||||
|
@ -54,7 +61,9 @@ export default class ReplyPreview extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { message, Message_TimeFormat } = this.props;
|
const {
|
||||||
|
message, Message_TimeFormat, customEmojis, baseUrl, username
|
||||||
|
} = this.props;
|
||||||
const time = moment(message.ts).format(Message_TimeFormat);
|
const time = moment(message.ts).format(Message_TimeFormat);
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
@ -63,9 +72,9 @@ export default class ReplyPreview extends Component {
|
||||||
<Text style={styles.username}>{message.u.username}</Text>
|
<Text style={styles.username}>{message.u.username}</Text>
|
||||||
<Text style={styles.time}>{time}</Text>
|
<Text style={styles.time}>{time}</Text>
|
||||||
</View>
|
</View>
|
||||||
<Markdown msg={message.msg} />
|
<Markdown msg={message.msg} customEmojis={customEmojis} baseUrl={baseUrl} username={username} />
|
||||||
</View>
|
</View>
|
||||||
<Icon name='close' size={20} style={styles.close} onPress={this.close} />
|
<Icon name='close' color='#9ea2a8' size={20} style={styles.close} onPress={this.close} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -530,6 +530,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
text={item.username || item.name}
|
text={item.username || item.name}
|
||||||
size={30}
|
size={30}
|
||||||
type={item.username ? 'd' : 'c'}
|
type={item.username ? 'd' : 'c'}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
/>,
|
/>,
|
||||||
<Text key='mention-item-name'>{ item.username || item.name }</Text>
|
<Text key='mention-item-name'>{ item.username || item.name }</Text>
|
||||||
]
|
]
|
||||||
|
@ -556,11 +557,13 @@ export default class MessageBox extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
renderReplyPreview = () => {
|
renderReplyPreview = () => {
|
||||||
const { replyMessage, replying, closeReply } = this.props;
|
const {
|
||||||
|
replyMessage, replying, closeReply, username
|
||||||
|
} = this.props;
|
||||||
if (!replying) {
|
if (!replying) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return <ReplyPreview key='reply-preview' message={replyMessage} close={closeReply} />;
|
return <ReplyPreview key='reply-preview' message={replyMessage} close={closeReply} username={username} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderFilesActions = () => {
|
renderFilesActions = () => {
|
||||||
|
@ -584,29 +587,30 @@ export default class MessageBox extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
[
|
[
|
||||||
this.renderMentions(),
|
this.renderMentions(),
|
||||||
this.renderReplyPreview(),
|
<View style={styles.composer} key='messagebox'>
|
||||||
<View
|
{this.renderReplyPreview()}
|
||||||
key='messagebox'
|
<View
|
||||||
style={[styles.textArea, this.props.editing && styles.editing]}
|
style={[styles.textArea, this.props.editing && styles.editing]}
|
||||||
testID='messagebox'
|
testID='messagebox'
|
||||||
>
|
>
|
||||||
{this.leftButtons}
|
{this.leftButtons}
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={component => this.component = component}
|
ref={component => this.component = component}
|
||||||
style={styles.textBoxInput}
|
style={styles.textBoxInput}
|
||||||
returnKeyType='default'
|
returnKeyType='default'
|
||||||
keyboardType='twitter'
|
keyboardType='twitter'
|
||||||
blurOnSubmit={false}
|
blurOnSubmit={false}
|
||||||
placeholder={I18n.t('New_Message')}
|
placeholder={I18n.t('New_Message')}
|
||||||
onChangeText={text => this.onChangeText(text)}
|
onChangeText={text => this.onChangeText(text)}
|
||||||
value={this.state.text}
|
value={this.state.text}
|
||||||
underlineColorAndroid='transparent'
|
underlineColorAndroid='transparent'
|
||||||
defaultValue=''
|
defaultValue=''
|
||||||
multiline
|
multiline
|
||||||
placeholderTextColor='#9EA2A8'
|
placeholderTextColor='#9EA2A8'
|
||||||
testID='messagebox-input'
|
testID='messagebox-input'
|
||||||
/>
|
/>
|
||||||
{this.rightButtons}
|
{this.rightButtons}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,13 +11,17 @@ export default StyleSheet.create({
|
||||||
borderTopColor: '#D8D8D8',
|
borderTopColor: '#D8D8D8',
|
||||||
zIndex: 2
|
zIndex: 2
|
||||||
},
|
},
|
||||||
|
composer: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
flexDirection: 'column',
|
||||||
|
borderTopColor: '#e1e5e8',
|
||||||
|
borderTopWidth: 1
|
||||||
|
},
|
||||||
textArea: {
|
textArea: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flexGrow: 0,
|
flexGrow: 0,
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff'
|
||||||
borderTopColor: '#ECECEC',
|
|
||||||
borderTopWidth: 1
|
|
||||||
},
|
},
|
||||||
textBoxInput: {
|
textBoxInput: {
|
||||||
textAlignVertical: 'center',
|
textAlignVertical: 'center',
|
||||||
|
|
|
@ -85,7 +85,8 @@ const keyExtractor = item => item.id;
|
||||||
server: state.login.user && state.login.user.server,
|
server: state.login.user && state.login.user.server,
|
||||||
status: state.login.user && state.login.user.status,
|
status: state.login.user && state.login.user.status,
|
||||||
username: state.login.user && state.login.user.username
|
username: state.login.user && state.login.user.username
|
||||||
}
|
},
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
selectServerRequest: server => dispatch(selectServerRequest(server)),
|
selectServerRequest: server => dispatch(selectServerRequest(server)),
|
||||||
logout: () => dispatch(logout()),
|
logout: () => dispatch(logout()),
|
||||||
|
@ -93,6 +94,7 @@ const keyExtractor = item => item.id;
|
||||||
}))
|
}))
|
||||||
export default class Sidebar extends Component {
|
export default class Sidebar extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
navigator: PropTypes.object,
|
navigator: PropTypes.object,
|
||||||
server: PropTypes.string.isRequired,
|
server: PropTypes.string.isRequired,
|
||||||
selectServerRequest: PropTypes.func.isRequired,
|
selectServerRequest: PropTypes.func.isRequired,
|
||||||
|
@ -323,7 +325,7 @@ export default class Sidebar extends Component {
|
||||||
)
|
)
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { user, server } = this.props;
|
const { user, server, baseUrl } = this.props;
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -341,6 +343,7 @@ export default class Sidebar extends Component {
|
||||||
text={user.username}
|
text={user.username}
|
||||||
size={30}
|
size={30}
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
|
baseUrl={baseUrl}
|
||||||
/>
|
/>
|
||||||
<View style={styles.headerTextContainer}>
|
<View style={styles.headerTextContainer}>
|
||||||
<View style={styles.headerUsername}>
|
<View style={styles.headerUsername}>
|
||||||
|
|
|
@ -1,74 +1,55 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, StyleSheet, TouchableOpacity, Text, Easing } from 'react-native';
|
import { View, StyleSheet, TouchableOpacity, Text, Easing, Image } from 'react-native';
|
||||||
import Video from 'react-native-video';
|
import Video from 'react-native-video';
|
||||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
||||||
import Slider from 'react-native-slider';
|
import Slider from 'react-native-slider';
|
||||||
import { connect } from 'react-redux';
|
import moment from 'moment';
|
||||||
|
|
||||||
import Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
audioContainer: {
|
audioContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
height: 50,
|
height: 56,
|
||||||
margin: 5,
|
backgroundColor: '#f7f8fa',
|
||||||
backgroundColor: '#eee',
|
borderRadius: 4,
|
||||||
borderRadius: 6
|
marginBottom: 10
|
||||||
},
|
},
|
||||||
playPauseButton: {
|
playPauseButton: {
|
||||||
width: 50,
|
width: 56,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'transparent',
|
|
||||||
borderRightColor: '#ccc',
|
|
||||||
borderRightWidth: 1
|
|
||||||
},
|
|
||||||
playPauseIcon: {
|
|
||||||
color: '#ccc',
|
|
||||||
backgroundColor: 'transparent'
|
backgroundColor: 'transparent'
|
||||||
},
|
},
|
||||||
progressContainer: {
|
playPauseImage: {
|
||||||
|
width: 30,
|
||||||
|
height: 30
|
||||||
|
},
|
||||||
|
slider: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
marginRight: 10
|
||||||
height: '100%',
|
|
||||||
marginHorizontal: 10
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
color: '#888',
|
|
||||||
fontSize: 10
|
|
||||||
},
|
|
||||||
currentTime: {
|
|
||||||
position: 'absolute',
|
|
||||||
left: 0,
|
|
||||||
bottom: 2
|
|
||||||
},
|
},
|
||||||
duration: {
|
duration: {
|
||||||
position: 'absolute',
|
marginRight: 16,
|
||||||
right: 0,
|
fontSize: 14,
|
||||||
bottom: 2
|
fontWeight: '500',
|
||||||
|
color: '#54585e'
|
||||||
|
},
|
||||||
|
thumbStyle: {
|
||||||
|
width: 12,
|
||||||
|
height: 12
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const formatTime = (t = 0, duration = 0) => {
|
const formatTime = seconds => moment.utc(seconds * 1000).format('mm:ss');
|
||||||
const time = Math.min(
|
|
||||||
Math.max(t, 0),
|
|
||||||
duration
|
|
||||||
);
|
|
||||||
const formattedMinutes = Math.floor(time / 60).toFixed(0).padStart(2, 0);
|
|
||||||
const formattedSeconds = Math.floor(time % 60).toFixed(0).padStart(2, 0);
|
|
||||||
return `${ formattedMinutes }:${ formattedSeconds }`;
|
|
||||||
};
|
|
||||||
|
|
||||||
@connect(state => ({
|
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
|
||||||
}))
|
|
||||||
export default class Audio extends React.PureComponent {
|
export default class Audio extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
file: PropTypes.object.isRequired,
|
file: PropTypes.object.isRequired,
|
||||||
baseUrl: PropTypes.string.isRequired,
|
baseUrl: PropTypes.string.isRequired,
|
||||||
user: PropTypes.object.isRequired
|
user: PropTypes.object.isRequired,
|
||||||
|
customEmojis: PropTypes.object.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -90,7 +71,7 @@ export default class Audio extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgress(data) {
|
onProgress(data) {
|
||||||
if (data.currentTime < this.state.duration) {
|
if (data.currentTime <= this.state.duration) {
|
||||||
this.setState({ currentTime: data.currentTime });
|
this.setState({ currentTime: data.currentTime });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,10 +83,6 @@ export default class Audio extends React.PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentTime() {
|
|
||||||
return formatTime(this.state.currentTime, this.state.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
getDuration() {
|
getDuration() {
|
||||||
return formatTime(this.state.duration);
|
return formatTime(this.state.duration);
|
||||||
}
|
}
|
||||||
|
@ -116,7 +93,10 @@ export default class Audio extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { uri, paused } = this.state;
|
const { uri, paused } = this.state;
|
||||||
const { description } = this.props.file;
|
const {
|
||||||
|
user, baseUrl, customEmojis, file
|
||||||
|
} = this.props;
|
||||||
|
const { description } = file;
|
||||||
return (
|
return (
|
||||||
[
|
[
|
||||||
<View key='audio' style={styles.audioContainer}>
|
<View key='audio' style={styles.audioContainer}>
|
||||||
|
@ -136,29 +116,30 @@ export default class Audio extends React.PureComponent {
|
||||||
onPress={() => this.togglePlayPause()}
|
onPress={() => this.togglePlayPause()}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
paused ? <Icon name='play-arrow' size={50} style={styles.playPauseIcon} />
|
paused ?
|
||||||
: <Icon name='pause' size={47} style={styles.playPauseIcon} />
|
<Image source={{ uri: 'play' }} style={styles.playPauseImage} /> :
|
||||||
|
<Image source={{ uri: 'pause' }} style={styles.playPauseImage} />
|
||||||
}
|
}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<View style={styles.progressContainer}>
|
<Slider
|
||||||
<Text style={[styles.label, styles.currentTime]}>{this.getCurrentTime()}</Text>
|
style={styles.slider}
|
||||||
<Text style={[styles.label, styles.duration]}>{this.getDuration()}</Text>
|
value={this.state.currentTime}
|
||||||
<Slider
|
maximumValue={this.state.duration}
|
||||||
value={this.state.currentTime}
|
minimumValue={0}
|
||||||
maximumValue={this.state.duration}
|
animateTransitions
|
||||||
minimumValue={0}
|
animationConfig={{
|
||||||
animateTransitions
|
duration: 250,
|
||||||
animationConfig={{
|
easing: Easing.linear,
|
||||||
duration: 250,
|
delay: 0
|
||||||
easing: Easing.linear,
|
}}
|
||||||
delay: 0
|
thumbTintColor='#1d74f5'
|
||||||
}}
|
minimumTrackTintColor='#1d74f5'
|
||||||
thumbTintColor='#ccc'
|
onValueChange={value => this.setState({ currentTime: value })}
|
||||||
onValueChange={value => this.setState({ currentTime: value })}
|
thumbStyle={styles.thumbStyle}
|
||||||
/>
|
/>
|
||||||
</View>
|
<Text style={styles.duration}>{this.getDuration()}</Text>
|
||||||
</View>,
|
</View>,
|
||||||
<Markdown key='description' msg={description} />
|
<Markdown key='description' msg={description} baseUrl={baseUrl} customEmojis={customEmojis} username={user.username} />
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,28 +2,28 @@ import React from 'react';
|
||||||
import { Text, ViewPropTypes } from 'react-native';
|
import { Text, ViewPropTypes } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { emojify } from 'react-emojione';
|
import { emojify } from 'react-emojione';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
|
|
||||||
@connect(state => ({
|
|
||||||
customEmojis: state.customEmojis
|
|
||||||
}))
|
|
||||||
export default class Emoji extends React.PureComponent {
|
export default class Emoji extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
content: PropTypes.string,
|
content: PropTypes.string.isRequired,
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
standardEmojiStyle: Text.propTypes.style,
|
standardEmojiStyle: Text.propTypes.style,
|
||||||
customEmojiStyle: ViewPropTypes.style,
|
customEmojiStyle: ViewPropTypes.style,
|
||||||
customEmojis: PropTypes.object.isRequired
|
customEmojis: PropTypes.oneOfType([
|
||||||
|
PropTypes.array,
|
||||||
|
PropTypes.object
|
||||||
|
])
|
||||||
};
|
};
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
content, standardEmojiStyle, customEmojiStyle, customEmojis
|
content, standardEmojiStyle, customEmojiStyle, customEmojis, baseUrl
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const parsedContent = content.replace(/^:|:$/g, '');
|
const parsedContent = content.replace(/^:|:$/g, '');
|
||||||
const emojiExtension = customEmojis[parsedContent];
|
const emojiExtension = customEmojis[parsedContent];
|
||||||
if (emojiExtension) {
|
if (emojiExtension) {
|
||||||
const emoji = { extension: emojiExtension, content: parsedContent };
|
const emoji = { extension: emojiExtension, content: parsedContent };
|
||||||
return <CustomEmoji key={content} style={customEmojiStyle} emoji={emoji} />;
|
return <CustomEmoji key={content} baseUrl={baseUrl} style={customEmojiStyle} emoji={emoji} />;
|
||||||
}
|
}
|
||||||
return <Text style={standardEmojiStyle}>{ emojify(`${ content }`, { output: 'unicode' }) }</Text>;
|
return <Text style={standardEmojiStyle}>{ emojify(`${ content }`, { output: 'unicode' }) }</Text>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,29 +2,30 @@ import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { TouchableOpacity } from 'react-native';
|
import { TouchableOpacity } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import PhotoModal from './PhotoModal';
|
import PhotoModal from './PhotoModal';
|
||||||
import Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
@connect(state => ({
|
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
|
||||||
}))
|
|
||||||
export default class extends React.PureComponent {
|
export default class extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
file: PropTypes.object.isRequired,
|
file: PropTypes.object.isRequired,
|
||||||
baseUrl: PropTypes.string.isRequired,
|
baseUrl: PropTypes.string.isRequired,
|
||||||
user: PropTypes.object.isRequired,
|
user: PropTypes.object.isRequired,
|
||||||
customEmojis: PropTypes.object
|
customEmojis: PropTypes.oneOfType([
|
||||||
|
PropTypes.array,
|
||||||
|
PropTypes.object
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
state = { modalVisible: false };
|
state = { modalVisible: false };
|
||||||
|
|
||||||
getDescription() {
|
getDescription() {
|
||||||
const { file, customEmojis } = this.props;
|
const {
|
||||||
|
file, customEmojis, baseUrl, user
|
||||||
|
} = this.props;
|
||||||
if (file.description) {
|
if (file.description) {
|
||||||
return <Markdown msg={file.description} customEmojis={customEmojis} />;
|
return <Markdown msg={file.description} customEmojis={customEmojis} baseUrl={baseUrl} username={user.username} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,13 +48,14 @@ export default class extends React.PureComponent {
|
||||||
<FastImage
|
<FastImage
|
||||||
style={styles.image}
|
style={styles.image}
|
||||||
source={{ uri: encodeURI(img) }}
|
source={{ uri: encodeURI(img) }}
|
||||||
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
/>
|
/>
|
||||||
{this.getDescription()}
|
{this.getDescription()}
|
||||||
</TouchableOpacity>,
|
</TouchableOpacity>,
|
||||||
<PhotoModal
|
<PhotoModal
|
||||||
key='modal'
|
key='modal'
|
||||||
title={this.props.file.title}
|
title={file.title}
|
||||||
description={this.props.file.description}
|
description={file.description}
|
||||||
image={img}
|
image={img}
|
||||||
isVisible={this.state.modalVisible}
|
isVisible={this.state.modalVisible}
|
||||||
onClose={() => this.setState({ modalVisible: false })}
|
onClose={() => this.setState({ modalVisible: false })}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, Platform, Image } from 'react-native';
|
import { Text, Image } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { emojify } from 'react-emojione';
|
import { emojify } from 'react-emojione';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import MarkdownRenderer, { PluginContainer } from 'react-native-markdown-renderer';
|
import MarkdownRenderer, { PluginContainer } from 'react-native-markdown-renderer';
|
||||||
import MarkdownFlowdock from 'markdown-it-flowdock';
|
import MarkdownFlowdock from 'markdown-it-flowdock';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -16,16 +15,13 @@ const formatText = text =>
|
||||||
(match, url, title) => `[${ title }](${ url })`
|
(match, url, title) => `[${ title }](${ url })`
|
||||||
);
|
);
|
||||||
|
|
||||||
@connect(state => ({
|
|
||||||
customEmojis: state.customEmojis
|
|
||||||
}))
|
|
||||||
export default class Markdown extends React.Component {
|
export default class Markdown extends React.Component {
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
return nextProps.msg !== this.props.msg;
|
return nextProps.msg !== this.props.msg;
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
msg, customEmojis, style, rules
|
msg, customEmojis, style, rules, baseUrl, username, edited
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -36,21 +32,38 @@ export default class Markdown extends React.Component {
|
||||||
return (
|
return (
|
||||||
<MarkdownRenderer
|
<MarkdownRenderer
|
||||||
rules={{
|
rules={{
|
||||||
...Platform.OS === 'android' ? {} : {
|
paragraph: (node, children) => (
|
||||||
paragraph: (node, children) => (
|
<Text key={node.key} style={styles.paragraph}>
|
||||||
<Text key={node.key} style={styles.paragraph}>
|
{children}{edited ? <Text style={styles.edited}> (edited)</Text> : null}
|
||||||
{children}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
mention: node => (
|
|
||||||
<Text key={node.key} onPress={() => alert(`Username @${ node.content }`)} style={styles.mention}>
|
|
||||||
@{node.content}
|
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
|
mention: (node) => {
|
||||||
|
const { content, key } = node;
|
||||||
|
let mentionStyle = styles.mention;
|
||||||
|
if (content === 'all' || content === 'here') {
|
||||||
|
mentionStyle = {
|
||||||
|
...mentionStyle,
|
||||||
|
...styles.mentionAll
|
||||||
|
};
|
||||||
|
} else if (content === username) {
|
||||||
|
mentionStyle = {
|
||||||
|
...mentionStyle,
|
||||||
|
...styles.mentionLoggedUser
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
key={key}
|
||||||
|
onPress={() => alert(`Username ${ content }`)}
|
||||||
|
style={mentionStyle}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
},
|
||||||
hashtag: node => (
|
hashtag: node => (
|
||||||
<Text key={node.key} onPress={() => alert(`Room #${ node.content }`)} style={styles.mention}>
|
<Text key={node.key} onPress={() => alert(`Room #${ node.content }`)} style={styles.mention}>
|
||||||
#{node.content}
|
#{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
emoji: (node) => {
|
emoji: (node) => {
|
||||||
|
@ -59,7 +72,7 @@ export default class Markdown extends React.Component {
|
||||||
const emojiExtension = customEmojis[content];
|
const emojiExtension = customEmojis[content];
|
||||||
if (emojiExtension) {
|
if (emojiExtension) {
|
||||||
const emoji = { extension: emojiExtension, content };
|
const emoji = { extension: emojiExtension, content };
|
||||||
return <CustomEmoji key={node.key} style={styles.customEmoji} emoji={emoji} />;
|
return <CustomEmoji key={node.key} baseUrl={baseUrl} style={styles.customEmoji} emoji={emoji} />;
|
||||||
}
|
}
|
||||||
return <Text key={node.key}>:{content}:</Text>;
|
return <Text key={node.key}>:{content}:</Text>;
|
||||||
}
|
}
|
||||||
|
@ -74,6 +87,11 @@ export default class Markdown extends React.Component {
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
paragraph: styles.paragraph,
|
paragraph: styles.paragraph,
|
||||||
|
text: {
|
||||||
|
color: '#0C0D0F',
|
||||||
|
fontSize: 16,
|
||||||
|
letterSpacing: 0.1
|
||||||
|
},
|
||||||
codeInline: {
|
codeInline: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: '#CCCCCC',
|
borderColor: '#CCCCCC',
|
||||||
|
@ -81,6 +99,9 @@ export default class Markdown extends React.Component {
|
||||||
padding: 2,
|
padding: 2,
|
||||||
borderRadius: 4
|
borderRadius: 4
|
||||||
},
|
},
|
||||||
|
link: {
|
||||||
|
color: '#1D74F5'
|
||||||
|
},
|
||||||
...style
|
...style
|
||||||
}}
|
}}
|
||||||
plugins={[
|
plugins={[
|
||||||
|
@ -95,7 +116,10 @@ export default class Markdown extends React.Component {
|
||||||
|
|
||||||
Markdown.propTypes = {
|
Markdown.propTypes = {
|
||||||
msg: PropTypes.string,
|
msg: PropTypes.string,
|
||||||
customEmojis: PropTypes.object,
|
username: PropTypes.string.isRequired,
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
customEmojis: PropTypes.object.isRequired,
|
||||||
style: PropTypes.any,
|
style: PropTypes.any,
|
||||||
rules: PropTypes.object
|
rules: PropTypes.object,
|
||||||
|
edited: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,351 @@
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { View, Text, TouchableOpacity, ViewPropTypes, Image as ImageRN } from 'react-native';
|
||||||
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { KeyboardUtils } from 'react-native-keyboard-input';
|
||||||
|
|
||||||
|
import Image from './Image';
|
||||||
|
import User from './User';
|
||||||
|
import Avatar from '../Avatar';
|
||||||
|
import Audio from './Audio';
|
||||||
|
import Video from './Video';
|
||||||
|
import Markdown from './Markdown';
|
||||||
|
import Url from './Url';
|
||||||
|
import Reply from './Reply';
|
||||||
|
import ReactionsModal from './ReactionsModal';
|
||||||
|
import Emoji from './Emoji';
|
||||||
|
import styles from './styles';
|
||||||
|
import Touch from '../../utils/touch';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import messagesStatus from '../../constants/messagesStatus';
|
||||||
|
|
||||||
|
const SYSTEM_MESSAGES = [
|
||||||
|
'r',
|
||||||
|
'au',
|
||||||
|
'ru',
|
||||||
|
'ul',
|
||||||
|
'uj',
|
||||||
|
'rm',
|
||||||
|
'user-muted',
|
||||||
|
'user-unmuted',
|
||||||
|
'message_pinned',
|
||||||
|
'subscription-role-added',
|
||||||
|
'subscription-role-removed',
|
||||||
|
'room_changed_description',
|
||||||
|
'room_changed_announcement',
|
||||||
|
'room_changed_topic',
|
||||||
|
'room_changed_privacy'
|
||||||
|
];
|
||||||
|
|
||||||
|
const getInfoMessage = ({
|
||||||
|
type, role, msg, user
|
||||||
|
}) => {
|
||||||
|
const { username } = user;
|
||||||
|
if (type === 'rm') {
|
||||||
|
return I18n.t('Message_removed');
|
||||||
|
} else if (type === 'uj') {
|
||||||
|
return I18n.t('Has_joined_the_channel');
|
||||||
|
} else if (type === 'r') {
|
||||||
|
return I18n.t('Room_name_changed', { name: msg, userBy: username });
|
||||||
|
} else if (type === 'message_pinned') {
|
||||||
|
return I18n.t('Message_pinned');
|
||||||
|
} else if (type === 'ul') {
|
||||||
|
return I18n.t('Has_left_the_channel');
|
||||||
|
} else if (type === 'ru') {
|
||||||
|
return I18n.t('User_removed_by', { userRemoved: msg, userBy: username });
|
||||||
|
} else if (type === 'au') {
|
||||||
|
return I18n.t('User_added_by', { userAdded: msg, userBy: username });
|
||||||
|
} else if (type === 'user-muted') {
|
||||||
|
return I18n.t('User_muted_by', { userMuted: msg, userBy: username });
|
||||||
|
} else if (type === 'user-unmuted') {
|
||||||
|
return I18n.t('User_unmuted_by', { userUnmuted: msg, userBy: username });
|
||||||
|
} else if (type === 'subscription-role-added') {
|
||||||
|
return `${ msg } was set ${ role } by ${ username }`;
|
||||||
|
} else if (type === 'subscription-role-removed') {
|
||||||
|
return `${ msg } is no longer ${ role } by ${ username }`;
|
||||||
|
} else if (type === 'room_changed_description') {
|
||||||
|
return I18n.t('Room_changed_description', { description: msg, userBy: username });
|
||||||
|
} else if (type === 'room_changed_announcement') {
|
||||||
|
return I18n.t('Room_changed_announcement', { announcement: msg, userBy: username });
|
||||||
|
} else if (type === 'room_changed_topic') {
|
||||||
|
return I18n.t('Room_changed_topic', { topic: msg, userBy: username });
|
||||||
|
} else if (type === 'room_changed_privacy') {
|
||||||
|
return I18n.t('Room_changed_privacy', { type: msg, userBy: username });
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Message extends PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
customEmojis: PropTypes.object.isRequired,
|
||||||
|
timeFormat: PropTypes.string.isRequired,
|
||||||
|
msg: PropTypes.string,
|
||||||
|
user: PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
username: PropTypes.string.isRequired,
|
||||||
|
token: PropTypes.string.isRequired
|
||||||
|
}),
|
||||||
|
author: PropTypes.shape({
|
||||||
|
_id: PropTypes.string.isRequired,
|
||||||
|
username: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string
|
||||||
|
}),
|
||||||
|
status: PropTypes.any,
|
||||||
|
reactions: PropTypes.any,
|
||||||
|
editing: PropTypes.bool,
|
||||||
|
style: ViewPropTypes.style,
|
||||||
|
archived: PropTypes.bool,
|
||||||
|
broadcast: PropTypes.bool,
|
||||||
|
reactionsModal: PropTypes.bool,
|
||||||
|
type: PropTypes.string,
|
||||||
|
header: PropTypes.bool,
|
||||||
|
avatar: PropTypes.string,
|
||||||
|
alias: PropTypes.string,
|
||||||
|
ts: PropTypes.instanceOf(Date),
|
||||||
|
edited: PropTypes.bool,
|
||||||
|
attachments: PropTypes.oneOfType([
|
||||||
|
PropTypes.array,
|
||||||
|
PropTypes.object
|
||||||
|
]),
|
||||||
|
urls: PropTypes.oneOfType([
|
||||||
|
PropTypes.array,
|
||||||
|
PropTypes.object
|
||||||
|
]),
|
||||||
|
useRealName: PropTypes.bool,
|
||||||
|
// methods
|
||||||
|
closeReactions: PropTypes.func,
|
||||||
|
onErrorPress: PropTypes.func,
|
||||||
|
onLongPress: PropTypes.func,
|
||||||
|
onReactionLongPress: PropTypes.func,
|
||||||
|
onReactionPress: PropTypes.func,
|
||||||
|
replyBroadcast: PropTypes.func,
|
||||||
|
toggleReactionPicker: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
archived: false,
|
||||||
|
broadcast: false,
|
||||||
|
attachments: [],
|
||||||
|
urls: [],
|
||||||
|
reactions: [],
|
||||||
|
onLongPress: () => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPress = () => {
|
||||||
|
KeyboardUtils.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
isInfoMessage() {
|
||||||
|
return SYSTEM_MESSAGES.includes(this.props.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
isOwn = () => this.props.author._id === this.props.user.id;
|
||||||
|
|
||||||
|
isDeleted() {
|
||||||
|
return this.props.type === 'rm';
|
||||||
|
}
|
||||||
|
|
||||||
|
isTemp() {
|
||||||
|
return this.props.status === messagesStatus.TEMP || this.props.status === messagesStatus.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasError() {
|
||||||
|
return this.props.status === messagesStatus.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAvatar = () => {
|
||||||
|
const {
|
||||||
|
header, avatar, author, baseUrl
|
||||||
|
} = this.props;
|
||||||
|
if (header) {
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
style={styles.avatar}
|
||||||
|
text={avatar ? '' : author.username}
|
||||||
|
size={36}
|
||||||
|
borderRadius={4}
|
||||||
|
avatar={avatar}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUsername = () => {
|
||||||
|
const {
|
||||||
|
header, timeFormat, author, alias, ts, useRealName
|
||||||
|
} = this.props;
|
||||||
|
if (header) {
|
||||||
|
return (
|
||||||
|
<User
|
||||||
|
onPress={this._onPress}
|
||||||
|
timeFormat={timeFormat}
|
||||||
|
username={(useRealName && author.name) || author.username}
|
||||||
|
alias={alias}
|
||||||
|
ts={ts}
|
||||||
|
temp={this.isTemp()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent() {
|
||||||
|
if (this.isInfoMessage()) {
|
||||||
|
return <Text style={styles.textInfo}>{getInfoMessage({ ...this.props })}</Text>;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
customEmojis, msg, baseUrl, user, edited
|
||||||
|
} = this.props;
|
||||||
|
return <Markdown msg={msg} customEmojis={customEmojis} baseUrl={baseUrl} username={user.username} edited={edited} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAttachment() {
|
||||||
|
const { attachments, timeFormat } = this.props;
|
||||||
|
|
||||||
|
if (attachments.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachments.map((file, index) => {
|
||||||
|
const { user, baseUrl, customEmojis } = this.props;
|
||||||
|
if (file.image_url) {
|
||||||
|
return <Image key={file.image_url} file={file} user={user} baseUrl={baseUrl} customEmojis={customEmojis} />;
|
||||||
|
}
|
||||||
|
if (file.audio_url) {
|
||||||
|
return <Audio key={file.audio_url} file={file} user={user} baseUrl={baseUrl} customEmojis={customEmojis} />;
|
||||||
|
}
|
||||||
|
if (file.video_url) {
|
||||||
|
return <Video key={file.video_url} file={file} user={user} baseUrl={baseUrl} customEmojis={customEmojis} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} user={user} baseUrl={baseUrl} customEmojis={customEmojis} />;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUrl = () => {
|
||||||
|
const { urls } = this.props;
|
||||||
|
if (urls.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls.map((url, index) => (
|
||||||
|
<Url url={url} key={url.url} index={index} />
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
renderError = () => {
|
||||||
|
if (!this.hasError()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <Icon name='error-outline' color='red' size={20} style={styles.errorIcon} onPress={this.props.onErrorPress} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderReaction = (reaction) => {
|
||||||
|
const reacted = reaction.usernames.findIndex(item => item.value === this.props.user.username) !== -1;
|
||||||
|
const reactedContainerStyle = reacted && styles.reactedContainer;
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => this.props.onReactionPress(reaction.emoji)}
|
||||||
|
onLongPress={this.props.onReactionLongPress}
|
||||||
|
key={reaction.emoji}
|
||||||
|
testID={`message-reaction-${ reaction.emoji }`}
|
||||||
|
>
|
||||||
|
<View style={[styles.reactionContainer, reactedContainerStyle]}>
|
||||||
|
<Emoji
|
||||||
|
content={reaction.emoji}
|
||||||
|
customEmojis={this.props.customEmojis}
|
||||||
|
standardEmojiStyle={styles.reactionEmoji}
|
||||||
|
customEmojiStyle={styles.reactionCustomEmoji}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
|
/>
|
||||||
|
<Text style={styles.reactionCount}>{ reaction.usernames.length }</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderReactions() {
|
||||||
|
const { reactions } = this.props;
|
||||||
|
if (reactions.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={styles.reactionsContainer}>
|
||||||
|
{reactions.map(this.renderReaction)}
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={this.props.toggleReactionPicker}
|
||||||
|
key='message-add-reaction'
|
||||||
|
testID='message-add-reaction'
|
||||||
|
style={styles.reactionContainer}
|
||||||
|
>
|
||||||
|
<ImageRN source={{ uri: 'add_reaction' }} style={styles.addReaction} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBroadcastReply() {
|
||||||
|
if (this.props.broadcast && !this.isOwn()) {
|
||||||
|
return (
|
||||||
|
<Touch
|
||||||
|
onPress={this.props.replyBroadcast}
|
||||||
|
style={styles.broadcastButton}
|
||||||
|
>
|
||||||
|
<View style={styles.broadcastButtonContainer}>
|
||||||
|
<ImageRN source={{ uri: 'reply' }} style={styles.broadcastButtonIcon} />
|
||||||
|
<Text style={styles.broadcastButtonText}>Reply</Text>
|
||||||
|
</View>
|
||||||
|
</Touch>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
editing, style, header, archived, onLongPress, reactionsModal, closeReactions, msg, ts, reactions, author, user, timeFormat, customEmojis, baseUrl
|
||||||
|
} = this.props;
|
||||||
|
const accessibilityLabel = I18n.t('Message_accessibility', { user: author.username, time: moment(ts).format(timeFormat), message: msg });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Touch
|
||||||
|
onPress={this.onPress}
|
||||||
|
onLongPress={onLongPress}
|
||||||
|
disabled={this.isInfoMessage() || this.hasError() || archived}
|
||||||
|
accessibilityLabel={accessibilityLabel}
|
||||||
|
style={[styles.container, header && { marginBottom: 10 }]}
|
||||||
|
>
|
||||||
|
<View style={[styles.message, editing && styles.editing, style]}>
|
||||||
|
<View style={styles.flex}>
|
||||||
|
{this.renderError()}
|
||||||
|
{this.renderAvatar()}
|
||||||
|
<View style={[styles.messageContent, header && styles.hasHeader, this.isTemp() && styles.temp]}>
|
||||||
|
{this.renderUsername()}
|
||||||
|
{this.renderContent()}
|
||||||
|
{this.renderAttachment()}
|
||||||
|
{this.renderUrl()}
|
||||||
|
{this.renderReactions()}
|
||||||
|
{this.renderBroadcastReply()}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{reactionsModal ?
|
||||||
|
<ReactionsModal
|
||||||
|
isVisible={reactionsModal}
|
||||||
|
reactions={reactions}
|
||||||
|
user={user}
|
||||||
|
customEmojis={customEmojis}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
close={closeReactions}
|
||||||
|
/>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
</Touch>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ import { View, Text, TouchableWithoutFeedback, FlatList, StyleSheet } from 'reac
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Modal from 'react-native-modal';
|
import Modal from 'react-native-modal';
|
||||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import Emoji from './Emoji';
|
import Emoji from './Emoji';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
|
@ -55,16 +54,17 @@ const styles = StyleSheet.create({
|
||||||
const standardEmojiStyle = { fontSize: 20 };
|
const standardEmojiStyle = { fontSize: 20 };
|
||||||
const customEmojiStyle = { width: 20, height: 20 };
|
const customEmojiStyle = { width: 20, height: 20 };
|
||||||
|
|
||||||
@connect(state => ({
|
|
||||||
customEmojis: state.customEmojis
|
|
||||||
}))
|
|
||||||
export default class ReactionsModal extends React.PureComponent {
|
export default class ReactionsModal extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
isVisible: PropTypes.bool.isRequired,
|
isVisible: PropTypes.bool.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
close: PropTypes.func.isRequired,
|
||||||
reactions: PropTypes.object.isRequired,
|
reactions: PropTypes.object.isRequired,
|
||||||
user: PropTypes.object.isRequired,
|
user: PropTypes.object.isRequired,
|
||||||
customEmojis: PropTypes.object.isRequired
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
customEmojis: PropTypes.oneOfType([
|
||||||
|
PropTypes.array,
|
||||||
|
PropTypes.object
|
||||||
|
])
|
||||||
}
|
}
|
||||||
renderItem = (item) => {
|
renderItem = (item) => {
|
||||||
const count = item.usernames.length;
|
const count = item.usernames.length;
|
||||||
|
@ -83,6 +83,7 @@ export default class ReactionsModal extends React.PureComponent {
|
||||||
standardEmojiStyle={standardEmojiStyle}
|
standardEmojiStyle={standardEmojiStyle}
|
||||||
customEmojiStyle={customEmojiStyle}
|
customEmojiStyle={customEmojiStyle}
|
||||||
customEmojis={this.props.customEmojis}
|
customEmojis={this.props.customEmojis}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.peopleItemContainer}>
|
<View style={styles.peopleItemContainer}>
|
||||||
|
@ -97,22 +98,22 @@ export default class ReactionsModal extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
isVisible, onClose, reactions
|
isVisible, close, reactions
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
onBackdropPress={onClose}
|
onBackdropPress={close}
|
||||||
onBackButtonPress={onClose}
|
onBackButtonPress={close}
|
||||||
backdropOpacity={0.9}
|
backdropOpacity={0.9}
|
||||||
>
|
>
|
||||||
<TouchableWithoutFeedback onPress={onClose}>
|
<TouchableWithoutFeedback onPress={close}>
|
||||||
<View style={styles.titleContainer}>
|
<View style={styles.titleContainer}>
|
||||||
<Icon
|
<Icon
|
||||||
style={styles.closeButton}
|
style={styles.closeButton}
|
||||||
name='close'
|
name='close'
|
||||||
size={20}
|
size={20}
|
||||||
onPress={onClose}
|
onPress={close}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.title}>{I18n.t('Reactions')}</Text>
|
<Text style={styles.title}>{I18n.t('Reactions')}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,39 +1,41 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, TouchableOpacity, 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 Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
import QuoteMark from './QuoteMark';
|
|
||||||
import Avatar from '../Avatar';
|
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
|
import Touch from '../../utils/touch';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginTop: 2,
|
marginTop: 15,
|
||||||
alignSelf: 'flex-end'
|
alignSelf: 'flex-end'
|
||||||
},
|
},
|
||||||
attachmentContainer: {
|
attachmentContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'column'
|
borderRadius: 4,
|
||||||
|
flexDirection: 'column',
|
||||||
|
backgroundColor: '#f3f4f5',
|
||||||
|
padding: 15
|
||||||
},
|
},
|
||||||
authorContainer: {
|
authorContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
author: {
|
author: {
|
||||||
fontWeight: 'bold',
|
color: '#1d74f5',
|
||||||
marginHorizontal: 5,
|
fontSize: 18,
|
||||||
flex: 1
|
fontWeight: '500',
|
||||||
|
marginRight: 10
|
||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
fontSize: 10,
|
fontSize: 14,
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
color: '#888',
|
color: '#9ea2a8',
|
||||||
marginLeft: 5
|
marginLeft: 5
|
||||||
},
|
},
|
||||||
fieldsContainer: {
|
fieldsContainer: {
|
||||||
|
@ -47,6 +49,9 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
fieldTitle: {
|
fieldTitle: {
|
||||||
fontWeight: 'bold'
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
marginTop: {
|
||||||
|
marginTop: 4
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -58,23 +63,13 @@ const onPress = (attachment) => {
|
||||||
openLink(attachment.title_link || attachment.author_link);
|
openLink(attachment.title_link || attachment.author_link);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Reply = ({ attachment, timeFormat }) => {
|
const Reply = ({
|
||||||
|
attachment, timeFormat, baseUrl, customEmojis, user, index
|
||||||
|
}) => {
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderAvatar = () => {
|
|
||||||
if (!attachment.author_icon && !attachment.author_name) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Avatar
|
|
||||||
text={attachment.author_name}
|
|
||||||
size={16}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderAuthor = () => (
|
const renderAuthor = () => (
|
||||||
attachment.author_name ? <Text style={styles.author}>{attachment.author_name}</Text> : null
|
attachment.author_name ? <Text style={styles.author}>{attachment.author_name}</Text> : null
|
||||||
);
|
);
|
||||||
|
@ -90,7 +85,6 @@ const Reply = ({ attachment, timeFormat }) => {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View style={styles.authorContainer}>
|
<View style={styles.authorContainer}>
|
||||||
{renderAvatar()}
|
|
||||||
{renderAuthor()}
|
{renderAuthor()}
|
||||||
{renderTime()}
|
{renderTime()}
|
||||||
</View>
|
</View>
|
||||||
|
@ -98,7 +92,7 @@ const Reply = ({ attachment, timeFormat }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderText = () => (
|
const renderText = () => (
|
||||||
attachment.text ? <Markdown msg={attachment.text} /> : null
|
attachment.text ? <Markdown msg={attachment.text} customEmojis={customEmojis} baseUrl={baseUrl} username={user.username} /> : null
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderFields = () => {
|
const renderFields = () => {
|
||||||
|
@ -119,28 +113,26 @@ const Reply = ({ attachment, timeFormat }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<Touch
|
||||||
onPress={() => onPress(attachment)}
|
onPress={() => onPress(attachment)}
|
||||||
style={styles.button}
|
style={[styles.button, index > 0 && styles.marginTop]}
|
||||||
>
|
>
|
||||||
<QuoteMark color={attachment.color} />
|
|
||||||
<View style={styles.attachmentContainer}>
|
<View style={styles.attachmentContainer}>
|
||||||
{renderTitle()}
|
{renderTitle()}
|
||||||
{renderText()}
|
{renderText()}
|
||||||
{renderFields()}
|
{renderFields()}
|
||||||
{attachment.attachments ?
|
|
||||||
attachment.attachments
|
|
||||||
.map(attach => <Reply key={attach.text} attachment={attach} timeFormat={timeFormat} />)
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</Touch>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Reply.propTypes = {
|
Reply.propTypes = {
|
||||||
attachment: PropTypes.object.isRequired,
|
attachment: PropTypes.object.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired
|
timeFormat: PropTypes.string.isRequired,
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
customEmojis: PropTypes.object.isRequired,
|
||||||
|
user: PropTypes.object.isRequired,
|
||||||
|
index: PropTypes.number
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Reply;
|
export default Reply;
|
||||||
|
|
|
@ -1,67 +1,82 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, TouchableOpacity, StyleSheet, Image } from 'react-native';
|
import { View, Text, StyleSheet } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
|
|
||||||
import QuoteMark from './QuoteMark';
|
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
|
import Touch from '../../utils/touch';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
flex: 1,
|
marginTop: 10
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginVertical: 2
|
|
||||||
},
|
},
|
||||||
image: {
|
container: {
|
||||||
height: 80,
|
flex: 1,
|
||||||
width: 80,
|
flexDirection: 'column',
|
||||||
resizeMode: 'cover',
|
borderRadius: 4,
|
||||||
borderRadius: 6
|
backgroundColor: '#F3F4F5'
|
||||||
},
|
},
|
||||||
textContainer: {
|
textContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
height: '100%',
|
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
padding: 4,
|
padding: 15,
|
||||||
justifyContent: 'flex-start',
|
justifyContent: 'flex-start',
|
||||||
alignItems: 'flex-start'
|
alignItems: 'flex-start'
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontWeight: 'bold',
|
fontWeight: '500',
|
||||||
fontSize: 12
|
color: '#1D74F5',
|
||||||
|
fontSize: 16,
|
||||||
|
marginTop: 5
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
fontSize: 12
|
marginTop: 5,
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#0C0D0F'
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '500',
|
||||||
|
color: '#9EA2A8'
|
||||||
|
},
|
||||||
|
marginTop: {
|
||||||
|
marginTop: 4
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
width: '100%',
|
||||||
|
height: 150,
|
||||||
|
borderTopLeftRadius: 4,
|
||||||
|
borderTopRightRadius: 4
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onPress = (url) => {
|
const onPress = (url) => {
|
||||||
openLink(url);
|
openLink(url);
|
||||||
};
|
};
|
||||||
const Url = ({ url }) => {
|
const Url = ({ url, index }) => {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity onPress={() => onPress(url.url)} style={styles.button}>
|
<Touch onPress={() => onPress(url.url)} style={[styles.button, index > 0 && styles.marginTop]}>
|
||||||
<QuoteMark />
|
<View style={styles.container}>
|
||||||
{url.image ?
|
{/* <View style={{ backgroundColor: 'red', height: 150, borderTopLeftRadius: 5, borderTopRightRadius: 5 }}>
|
||||||
<Image
|
{url.image ? <FastImage source={{ uri: url.image }} /> : null}
|
||||||
style={styles.image}
|
</View> */}
|
||||||
source={{ uri: encodeURI(url.image) }}
|
{url.image ? <FastImage source={{ uri: url.image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} /> : null}
|
||||||
/>
|
<View style={styles.textContainer}>
|
||||||
: null
|
<Text style={styles.url} numberOfLines={1}>{url.url}</Text>
|
||||||
}
|
<Text style={styles.title} numberOfLines={2}>{url.title}</Text>
|
||||||
<View style={styles.textContainer}>
|
<Text style={styles.description} numberOfLines={2}>{url.description}</Text>
|
||||||
<Text style={styles.title}>{url.title}</Text>
|
</View>
|
||||||
<Text style={styles.description} numberOfLines={1}>{url.description}</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</Touch>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Url.propTypes = {
|
Url.propTypes = {
|
||||||
url: PropTypes.object.isRequired
|
url: PropTypes.object.isRequired,
|
||||||
|
index: PropTypes.number
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Url;
|
export default Url;
|
||||||
|
|
|
@ -2,14 +2,13 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, StyleSheet } from 'react-native';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
|
||||||
import Avatar from '../Avatar';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
username: {
|
username: {
|
||||||
color: '#000',
|
color: '#0C0D0F',
|
||||||
fontWeight: '400',
|
fontWeight: '600',
|
||||||
fontSize: 14
|
fontSize: 16,
|
||||||
|
lineHeight: 22
|
||||||
},
|
},
|
||||||
usernameView: {
|
usernameView: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
@ -17,67 +16,50 @@ const styles = StyleSheet.create({
|
||||||
marginBottom: 2
|
marginBottom: 2
|
||||||
},
|
},
|
||||||
alias: {
|
alias: {
|
||||||
fontSize: 10,
|
fontSize: 14,
|
||||||
color: '#888',
|
color: '#9EA2A8',
|
||||||
paddingLeft: 5
|
paddingLeft: 6,
|
||||||
|
lineHeight: 16
|
||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
fontSize: 10,
|
fontSize: 14,
|
||||||
color: '#888',
|
color: '#9EA2A8',
|
||||||
paddingLeft: 5,
|
paddingLeft: 10,
|
||||||
fontWeight: '400'
|
fontWeight: '300',
|
||||||
},
|
lineHeight: 16
|
||||||
edited: {
|
|
||||||
marginLeft: 5,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default class User extends React.PureComponent {
|
export default class User extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
item: PropTypes.object.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
Message_TimeFormat: PropTypes.string.isRequired,
|
username: PropTypes.string,
|
||||||
|
alias: PropTypes.string,
|
||||||
|
ts: PropTypes.instanceOf(Date),
|
||||||
|
temp: PropTypes.bool,
|
||||||
onPress: PropTypes.func
|
onPress: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEdited = (item) => {
|
|
||||||
if (!item.editedBy) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<View style={styles.edited}>
|
|
||||||
<Icon name='pencil-square-o' color='#888' size={10} />
|
|
||||||
<Avatar
|
|
||||||
style={{ marginLeft: 5 }}
|
|
||||||
text={item.editedBy.username}
|
|
||||||
size={20}
|
|
||||||
avatar={item.avatar}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { item } = this.props;
|
const {
|
||||||
|
username, alias, ts, temp
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
const extraStyle = {};
|
const extraStyle = {};
|
||||||
if (item.temp) {
|
if (temp) {
|
||||||
extraStyle.opacity = 0.3;
|
extraStyle.opacity = 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
const username = item.alias || item.u.username;
|
const aliasUsername = alias ? (<Text style={styles.alias}>@{username}</Text>) : null;
|
||||||
const aliasUsername = item.alias ? (<Text style={styles.alias}>@{item.u.username}</Text>) : null;
|
const time = moment(ts).format(this.props.timeFormat);
|
||||||
const time = moment(item.ts).format(this.props.Message_TimeFormat);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.usernameView}>
|
<View style={styles.usernameView}>
|
||||||
<Text onPress={this.props.onPress} style={styles.username}>
|
<Text onPress={this.props.onPress} style={styles.username}>
|
||||||
{username}
|
{alias || username}
|
||||||
</Text>
|
</Text>
|
||||||
{aliasUsername}
|
{aliasUsername}
|
||||||
<Text style={styles.time}>{time}</Text>
|
<Text style={styles.time}>{time}</Text>
|
||||||
{this.renderEdited(item)}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { StyleSheet, TouchableOpacity, Image, Platform } from 'react-native';
|
import { StyleSheet, TouchableOpacity, Image, Platform, View } from 'react-native';
|
||||||
import Modal from 'react-native-modal';
|
import Modal from 'react-native-modal';
|
||||||
import VideoPlayer from 'react-native-video-controls';
|
import VideoPlayer from 'react-native-video-controls';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
|
|
||||||
|
@ -11,31 +10,31 @@ const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(Platform.OS === 'io
|
||||||
const isTypeSupported = type => SUPPORTED_TYPES.indexOf(type) !== -1;
|
const isTypeSupported = type => SUPPORTED_TYPES.indexOf(type) !== -1;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
button: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
height: 100,
|
borderRadius: 4,
|
||||||
margin: 5
|
height: 150,
|
||||||
|
backgroundColor: '#1f2329',
|
||||||
|
marginBottom: 10,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
},
|
},
|
||||||
modal: {
|
modal: {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
backgroundColor: '#000'
|
backgroundColor: '#000'
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
flex: 1,
|
width: 54,
|
||||||
width: null,
|
height: 54
|
||||||
height: null,
|
|
||||||
resizeMode: 'contain'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(state => ({
|
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
|
||||||
}))
|
|
||||||
export default class Video extends React.PureComponent {
|
export default class Video extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
file: PropTypes.object.isRequired,
|
file: PropTypes.object.isRequired,
|
||||||
baseUrl: PropTypes.string.isRequired,
|
baseUrl: PropTypes.string.isRequired,
|
||||||
user: PropTypes.object.isRequired
|
user: PropTypes.object.isRequired,
|
||||||
|
customEmojis: PropTypes.object.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
state = { isVisible: false };
|
state = { isVisible: false };
|
||||||
|
@ -62,19 +61,21 @@ export default class Video extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { isVisible } = this.state;
|
const { isVisible } = this.state;
|
||||||
const { description } = this.props.file;
|
const { description } = this.props.file;
|
||||||
|
const { baseUrl, user, customEmojis } = this.props;
|
||||||
return (
|
return (
|
||||||
[
|
[
|
||||||
<TouchableOpacity
|
<View key='button'>
|
||||||
key='button'
|
<TouchableOpacity
|
||||||
style={styles.container}
|
style={styles.button}
|
||||||
onPress={() => this.open()}
|
onPress={() => this.open()}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
source={require('../../static/images/logo.png')}
|
source={{ uri: 'play_video' }}
|
||||||
style={styles.image}
|
style={styles.image}
|
||||||
/>
|
/>
|
||||||
<Markdown msg={description} />
|
</TouchableOpacity>
|
||||||
</TouchableOpacity>,
|
<Markdown msg={description} customEmojis={customEmojis} baseUrl={baseUrl} username={user.username} />
|
||||||
|
</View>,
|
||||||
<Modal
|
<Modal
|
||||||
key='modal'
|
key='modal'
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
|
|
|
@ -1,120 +1,56 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, Text, TouchableOpacity, Vibration, ViewPropTypes } from 'react-native';
|
import { Vibration, ViewPropTypes } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
||||||
import moment from 'moment';
|
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import { KeyboardUtils } from 'react-native-keyboard-input';
|
|
||||||
|
|
||||||
import Image from './Image';
|
import Message from './Message';
|
||||||
import User from './User';
|
import { errorActionsShow, toggleReactionPicker, replyBroadcast } from '../../actions/messages';
|
||||||
import Avatar from '../Avatar';
|
|
||||||
import Audio from './Audio';
|
|
||||||
import Video from './Video';
|
|
||||||
import Markdown from './Markdown';
|
|
||||||
import Url from './Url';
|
|
||||||
import Reply from './Reply';
|
|
||||||
import ReactionsModal from './ReactionsModal';
|
|
||||||
import Emoji from './Emoji';
|
|
||||||
import styles from './styles';
|
|
||||||
import { actionsShow, errorActionsShow, toggleReactionPicker, replyBroadcast } from '../../actions/messages';
|
|
||||||
import messagesStatus from '../../constants/messagesStatus';
|
|
||||||
import Touch from '../../utils/touch';
|
|
||||||
import I18n from '../../i18n';
|
|
||||||
|
|
||||||
const SYSTEM_MESSAGES = [
|
|
||||||
'r',
|
|
||||||
'au',
|
|
||||||
'ru',
|
|
||||||
'ul',
|
|
||||||
'uj',
|
|
||||||
'rm',
|
|
||||||
'user-muted',
|
|
||||||
'user-unmuted',
|
|
||||||
'message_pinned',
|
|
||||||
'subscription-role-added',
|
|
||||||
'subscription-role-removed',
|
|
||||||
'room_changed_description',
|
|
||||||
'room_changed_announcement',
|
|
||||||
'room_changed_topic',
|
|
||||||
'room_changed_privacy'
|
|
||||||
];
|
|
||||||
|
|
||||||
const getInfoMessage = ({
|
|
||||||
t, role, msg, u
|
|
||||||
}) => {
|
|
||||||
if (t === 'rm') {
|
|
||||||
return I18n.t('Message_removed');
|
|
||||||
} else if (t === 'uj') {
|
|
||||||
return I18n.t('Has_joined_the_channel');
|
|
||||||
} else if (t === 'r') {
|
|
||||||
return I18n.t('Room_name_changed', { name: msg, userBy: u.username });
|
|
||||||
} else if (t === 'message_pinned') {
|
|
||||||
return I18n.t('Message_pinned');
|
|
||||||
} else if (t === 'ul') {
|
|
||||||
return I18n.t('Has_left_the_channel');
|
|
||||||
} else if (t === 'ru') {
|
|
||||||
return I18n.t('User_removed_by', { userRemoved: msg, userBy: u.username });
|
|
||||||
} else if (t === 'au') {
|
|
||||||
return I18n.t('User_added_by', { userAdded: msg, userBy: u.username });
|
|
||||||
} else if (t === 'user-muted') {
|
|
||||||
return I18n.t('User_muted_by', { userMuted: msg, userBy: u.username });
|
|
||||||
} else if (t === 'user-unmuted') {
|
|
||||||
return I18n.t('User_unmuted_by', { userUnmuted: msg, userBy: u.username });
|
|
||||||
} else if (t === 'subscription-role-added') {
|
|
||||||
return `${ msg } was set ${ role } by ${ u.username }`;
|
|
||||||
} else if (t === 'subscription-role-removed') {
|
|
||||||
return `${ msg } is no longer ${ role } by ${ u.username }`;
|
|
||||||
} else if (t === 'room_changed_description') {
|
|
||||||
return I18n.t('Room_changed_description', { description: msg, userBy: u.username });
|
|
||||||
} else if (t === 'room_changed_announcement') {
|
|
||||||
return I18n.t('Room_changed_announcement', { announcement: msg, userBy: u.username });
|
|
||||||
} else if (t === 'room_changed_topic') {
|
|
||||||
return I18n.t('Room_changed_topic', { topic: msg, userBy: u.username });
|
|
||||||
} else if (t === 'room_changed_privacy') {
|
|
||||||
return I18n.t('Room_changed_privacy', { type: msg, userBy: u.username });
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
message: state.messages.message,
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
editing: state.messages.editing,
|
|
||||||
customEmojis: state.customEmojis,
|
customEmojis: state.customEmojis,
|
||||||
|
editing: state.messages.editing,
|
||||||
|
Message_GroupingPeriod: state.settings.Message_GroupingPeriod,
|
||||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||||
Message_GroupingPeriod: state.settings.Message_GroupingPeriod
|
message: state.messages.message,
|
||||||
|
useRealName: state.settings.UI_Use_Real_Name
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
actionsShow: actionMessage => dispatch(actionsShow(actionMessage)),
|
|
||||||
errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage)),
|
errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage)),
|
||||||
toggleReactionPicker: message => dispatch(toggleReactionPicker(message)),
|
replyBroadcast: message => dispatch(replyBroadcast(message)),
|
||||||
replyBroadcast: message => dispatch(replyBroadcast(message))
|
toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
|
||||||
}))
|
}))
|
||||||
export default class Message extends React.Component {
|
export default class MessageContainer extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
status: PropTypes.any,
|
|
||||||
item: PropTypes.object.isRequired,
|
item: PropTypes.object.isRequired,
|
||||||
reactions: PropTypes.any.isRequired,
|
reactions: PropTypes.any.isRequired,
|
||||||
Message_TimeFormat: PropTypes.string.isRequired,
|
|
||||||
Message_GroupingPeriod: PropTypes.number.isRequired,
|
|
||||||
customTimeFormat: PropTypes.string,
|
|
||||||
message: PropTypes.object.isRequired,
|
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
username: PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
token: PropTypes.string.isRequired
|
token: PropTypes.string.isRequired
|
||||||
}),
|
}),
|
||||||
editing: PropTypes.bool,
|
customTimeFormat: PropTypes.string,
|
||||||
errorActionsShow: PropTypes.func,
|
|
||||||
toggleReactionPicker: PropTypes.func,
|
|
||||||
replyBroadcast: PropTypes.func,
|
|
||||||
onReactionPress: PropTypes.func,
|
|
||||||
style: ViewPropTypes.style,
|
style: ViewPropTypes.style,
|
||||||
onLongPress: PropTypes.func,
|
status: PropTypes.number,
|
||||||
_updatedAt: PropTypes.instanceOf(Date),
|
|
||||||
archived: PropTypes.bool,
|
archived: PropTypes.bool,
|
||||||
broadcast: PropTypes.bool,
|
broadcast: PropTypes.bool,
|
||||||
previousItem: PropTypes.object
|
previousItem: PropTypes.object,
|
||||||
|
_updatedAt: PropTypes.instanceOf(Date),
|
||||||
|
// redux
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
|
customEmojis: PropTypes.object,
|
||||||
|
editing: PropTypes.bool,
|
||||||
|
Message_GroupingPeriod: PropTypes.number,
|
||||||
|
Message_TimeFormat: PropTypes.string,
|
||||||
|
message: PropTypes.object,
|
||||||
|
useRealName: PropTypes.bool,
|
||||||
|
// methods - props
|
||||||
|
onLongPress: PropTypes.func,
|
||||||
|
onReactionPress: PropTypes.func,
|
||||||
|
// methods - redux
|
||||||
|
errorActionsShow: PropTypes.func,
|
||||||
|
replyBroadcast: PropTypes.func,
|
||||||
|
toggleReactionPicker: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -127,7 +63,7 @@ export default class Message extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { reactionsModal: false };
|
this.state = { reactionsModal: false };
|
||||||
this.onClose = this.onClose.bind(this);
|
this.closeReactions = this.closeReactions.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
@ -147,12 +83,12 @@ export default class Message extends React.Component {
|
||||||
if (this.props.broadcast !== nextProps.broadcast) {
|
if (this.props.broadcast !== nextProps.broadcast) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (this.props.editing !== nextProps.editing) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return this.props._updatedAt.toGMTString() !== nextProps._updatedAt.toGMTString();
|
return this.props._updatedAt.toGMTString() !== nextProps._updatedAt.toGMTString();
|
||||||
}
|
}
|
||||||
|
|
||||||
onPress = () => {
|
|
||||||
KeyboardUtils.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
onLongPress = () => {
|
onLongPress = () => {
|
||||||
this.props.onLongPress(this.parseMessage());
|
this.props.onLongPress(this.parseMessage());
|
||||||
|
@ -165,10 +101,9 @@ export default class Message extends React.Component {
|
||||||
onReactionPress = (emoji) => {
|
onReactionPress = (emoji) => {
|
||||||
this.props.onReactionPress(emoji, this.props.item._id);
|
this.props.onReactionPress(emoji, this.props.item._id);
|
||||||
}
|
}
|
||||||
onClose() {
|
|
||||||
this.setState({ reactionsModal: false });
|
|
||||||
}
|
onReactionLongPress = () => {
|
||||||
onReactionLongPress() {
|
|
||||||
this.setState({ reactionsModal: true });
|
this.setState({ reactionsModal: true });
|
||||||
Vibration.vibrate(50);
|
Vibration.vibrate(50);
|
||||||
}
|
}
|
||||||
|
@ -178,29 +113,12 @@ export default class Message extends React.Component {
|
||||||
return customTimeFormat || Message_TimeFormat;
|
return customTimeFormat || Message_TimeFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage = () => JSON.parse(JSON.stringify(this.props.item));
|
closeReactions = () => {
|
||||||
|
this.setState({ reactionsModal: false });
|
||||||
isInfoMessage() {
|
|
||||||
return SYSTEM_MESSAGES.includes(this.props.item.t);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isOwn = () => this.props.item.u && this.props.item.u._id === this.props.user.id;
|
isHeader = () => {
|
||||||
|
|
||||||
isDeleted() {
|
|
||||||
return this.props.item.t === 'rm';
|
|
||||||
}
|
|
||||||
|
|
||||||
isTemp() {
|
|
||||||
return this.props.item.status === messagesStatus.TEMP || this.props.item.status === messagesStatus.ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasError() {
|
|
||||||
return this.props.item.status === messagesStatus.ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderHeader = (username) => {
|
|
||||||
const { item, previousItem } = this.props;
|
const { item, previousItem } = this.props;
|
||||||
|
|
||||||
if (previousItem && (
|
if (previousItem && (
|
||||||
(previousItem.ts.toDateString() === item.ts.toDateString()) &&
|
(previousItem.ts.toDateString() === item.ts.toDateString()) &&
|
||||||
(previousItem.u.username === item.u.username) &&
|
(previousItem.u.username === item.u.username) &&
|
||||||
|
@ -208,172 +126,61 @@ export default class Message extends React.Component {
|
||||||
(previousItem.status === item.status) &&
|
(previousItem.status === item.status) &&
|
||||||
(item.ts - previousItem.ts < this.props.Message_GroupingPeriod * 1000)
|
(item.ts - previousItem.ts < this.props.Message_GroupingPeriod * 1000)
|
||||||
)) {
|
)) {
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
return (
|
|
||||||
<View style={[styles.flex, { marginTop: 5 }]}>
|
|
||||||
<Avatar
|
|
||||||
style={styles.avatar}
|
|
||||||
text={item.avatar ? '' : username}
|
|
||||||
size={20}
|
|
||||||
avatar={item.avatar}
|
|
||||||
/>
|
|
||||||
<User
|
|
||||||
onPress={this._onPress}
|
|
||||||
item={item}
|
|
||||||
Message_TimeFormat={this.timeFormat}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContent() {
|
parseMessage = () => JSON.parse(JSON.stringify(this.props.item));
|
||||||
if (this.isInfoMessage()) {
|
|
||||||
return <Text style={styles.textInfo}>{getInfoMessage(this.props.item)}</Text>;
|
toggleReactionPicker = () => {
|
||||||
}
|
this.props.toggleReactionPicker(this.parseMessage());
|
||||||
const { item } = this.props;
|
|
||||||
return <Markdown msg={item.msg} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAttachment() {
|
replyBroadcast = () => {
|
||||||
if (this.props.item.attachments.length === 0) {
|
this.props.replyBroadcast(this.parseMessage());
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.props.item.attachments.map((file) => {
|
|
||||||
const { user } = this.props;
|
|
||||||
if (file.image_url) {
|
|
||||||
return <Image file={file} user={user} />;
|
|
||||||
}
|
|
||||||
if (file.audio_url) {
|
|
||||||
return <Audio file={file} user={user} />;
|
|
||||||
}
|
|
||||||
if (file.video_url) {
|
|
||||||
return <Video file={file} user={user} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Reply attachment={file} timeFormat={this.timeFormat} />;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderUrl = () => {
|
|
||||||
const { urls } = this.props.item;
|
|
||||||
if (urls.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return urls.map(url => (
|
|
||||||
<Url url={url} key={url.url} />
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
renderError = () => {
|
|
||||||
if (!this.hasError()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<TouchableOpacity onPress={this.onErrorPress}>
|
|
||||||
<Icon name='error-outline' color='red' size={20} style={styles.errorIcon} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderReaction = (reaction) => {
|
|
||||||
const reacted = reaction.usernames.findIndex(item => item.value === this.props.user.username) !== -1;
|
|
||||||
const reactedContainerStyle = reacted && styles.reactedContainer;
|
|
||||||
return (
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => this.onReactionPress(reaction.emoji)}
|
|
||||||
onLongPress={() => this.onReactionLongPress()}
|
|
||||||
key={reaction.emoji}
|
|
||||||
testID={`message-reaction-${ reaction.emoji }`}
|
|
||||||
>
|
|
||||||
<View style={[styles.reactionContainer, reactedContainerStyle]}>
|
|
||||||
<Emoji
|
|
||||||
content={reaction.emoji}
|
|
||||||
standardEmojiStyle={styles.reactionEmoji}
|
|
||||||
customEmojiStyle={styles.reactionCustomEmoji}
|
|
||||||
/>
|
|
||||||
<Text style={styles.reactionCount}>{ reaction.usernames.length }</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderReactions() {
|
|
||||||
if (this.props.item.reactions.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<View style={styles.reactionsContainer}>
|
|
||||||
{this.props.item.reactions.map(this.renderReaction)}
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => this.props.toggleReactionPicker(this.parseMessage())}
|
|
||||||
key='message-add-reaction'
|
|
||||||
testID='message-add-reaction'
|
|
||||||
style={[styles.reactionContainer, styles.addReactionContainer]}
|
|
||||||
>
|
|
||||||
<Icon name='insert-emoticon' color='#1D74F5' size={18} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderBroadcastReply() {
|
|
||||||
if (!this.props.broadcast || this.isOwn()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.broadcastButton}
|
|
||||||
onPress={() => this.props.replyBroadcast(this.parseMessage())}
|
|
||||||
>
|
|
||||||
<Text style={styles.broadcastButtonText}>Reply</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
item, message, editing, style, archived
|
item, message, editing, user, style, archived, baseUrl, customEmojis, useRealName, broadcast
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const username = item.alias || item.u.username;
|
const {
|
||||||
|
msg, ts, attachments, urls, reactions, t, status, avatar, u, alias, editedBy
|
||||||
|
} = item;
|
||||||
const isEditing = message._id === item._id && editing;
|
const isEditing = message._id === item._id && editing;
|
||||||
const accessibilityLabel = I18n.t('Message_accessibility', { user: username, time: moment(item.ts).format(this.timeFormat), message: this.props.item.msg });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Touch
|
<Message
|
||||||
onPress={this.onPress}
|
msg={msg}
|
||||||
|
author={u}
|
||||||
|
ts={ts}
|
||||||
|
type={t}
|
||||||
|
status={status}
|
||||||
|
attachments={attachments}
|
||||||
|
urls={urls}
|
||||||
|
reactions={reactions}
|
||||||
|
alias={alias}
|
||||||
|
editing={isEditing}
|
||||||
|
header={this.isHeader()}
|
||||||
|
avatar={avatar}
|
||||||
|
user={user}
|
||||||
|
edited={editedBy && !!editedBy.username}
|
||||||
|
timeFormat={this.timeFormat}
|
||||||
|
style={style}
|
||||||
|
archived={archived}
|
||||||
|
broadcast={broadcast}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
customEmojis={customEmojis}
|
||||||
|
reactionsModal={this.state.reactionsModal}
|
||||||
|
useRealName={useRealName}
|
||||||
|
closeReactions={this.closeReactions}
|
||||||
|
onErrorPress={this.onErrorPress}
|
||||||
onLongPress={this.onLongPress}
|
onLongPress={this.onLongPress}
|
||||||
disabled={this.isInfoMessage() || this.hasError() || archived}
|
onReactionLongPress={this.onReactionLongPress}
|
||||||
underlayColor='#FFFFFF'
|
onReactionPress={this.onReactionPress}
|
||||||
activeOpacity={0.3}
|
replyBroadcast={this.replyBroadcast}
|
||||||
accessibilityLabel={accessibilityLabel}
|
toggleReactionPicker={this.toggleReactionPicker}
|
||||||
>
|
/>
|
||||||
<View style={[styles.message, isEditing && styles.editing, style]}>
|
|
||||||
{this.renderHeader(username)}
|
|
||||||
<View style={styles.flex}>
|
|
||||||
{this.renderError()}
|
|
||||||
<View style={[styles.messageContent, this.isTemp() && styles.temp]}>
|
|
||||||
{this.renderContent()}
|
|
||||||
{this.renderAttachment()}
|
|
||||||
{this.renderUrl()}
|
|
||||||
{this.renderReactions()}
|
|
||||||
{this.renderBroadcastReply()}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
{this.state.reactionsModal ?
|
|
||||||
<ReactionsModal
|
|
||||||
isVisible={this.state.reactionsModal}
|
|
||||||
onClose={this.onClose}
|
|
||||||
reactions={item.reactions}
|
|
||||||
user={this.props.user}
|
|
||||||
/>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</View>
|
|
||||||
</Touch>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,38 @@
|
||||||
import { StyleSheet, Platform } from 'react-native';
|
import { StyleSheet, Platform } from 'react-native';
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
paddingVertical: 5
|
||||||
|
},
|
||||||
messageContent: {
|
messageContent: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
marginLeft: 30
|
marginLeft: 51
|
||||||
|
},
|
||||||
|
hasHeader: {
|
||||||
|
marginLeft: 15
|
||||||
},
|
},
|
||||||
flex: {
|
flex: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
paddingHorizontal: 12,
|
paddingLeft: 10,
|
||||||
paddingVertical: 3,
|
paddingRight: 15,
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
transform: [{ scaleY: -1 }],
|
transform: [{ scaleY: -1 }],
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
textInfo: {
|
textInfo: {
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
color: '#a0a0a0'
|
color: '#a0a0a0',
|
||||||
|
fontSize: 16
|
||||||
},
|
},
|
||||||
editing: {
|
editing: {
|
||||||
backgroundColor: '#fff5df'
|
backgroundColor: '#fff5df'
|
||||||
},
|
},
|
||||||
customEmoji: {
|
customEmoji: {
|
||||||
width: 16,
|
width: 20,
|
||||||
height: 16
|
height: 20
|
||||||
},
|
},
|
||||||
temp: { opacity: 0.3 },
|
temp: { opacity: 0.3 },
|
||||||
codeStyle: {
|
codeStyle: {
|
||||||
|
@ -48,57 +55,83 @@ export default StyleSheet.create({
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingHorizontal: 10,
|
|
||||||
paddingVertical: 5,
|
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
borderColor: '#e1e5e8',
|
||||||
marginRight: 10,
|
marginRight: 10,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
maxHeight: 28,
|
height: 28,
|
||||||
backgroundColor: '#E8F2FF'
|
minWidth: 46,
|
||||||
},
|
backgroundColor: '#FFF'
|
||||||
addReactionContainer: {
|
|
||||||
paddingHorizontal: 15
|
|
||||||
},
|
},
|
||||||
reactionCount: {
|
reactionCount: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
marginLeft: 3,
|
marginLeft: 3,
|
||||||
|
marginRight: 8.5,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: '#1D74F5'
|
color: '#1D74F5'
|
||||||
},
|
},
|
||||||
reactionEmoji: {
|
reactionEmoji: {
|
||||||
fontSize: 14
|
fontSize: 13,
|
||||||
|
marginLeft: 7
|
||||||
},
|
},
|
||||||
reactionCustomEmoji: {
|
reactionCustomEmoji: {
|
||||||
width: 20,
|
width: 19,
|
||||||
height: 20
|
height: 19,
|
||||||
|
marginLeft: 7
|
||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
marginRight: 10
|
marginTop: 5
|
||||||
},
|
},
|
||||||
reactedContainer: {
|
reactedContainer: {
|
||||||
borderWidth: 0,
|
borderColor: '#1d74f580',
|
||||||
backgroundColor: '#D1DAE6'
|
backgroundColor: '#e8f2ff'
|
||||||
|
},
|
||||||
|
addReaction: {
|
||||||
|
width: 17,
|
||||||
|
height: 17
|
||||||
},
|
},
|
||||||
errorIcon: {
|
errorIcon: {
|
||||||
padding: 10,
|
|
||||||
paddingRight: 12,
|
paddingRight: 12,
|
||||||
paddingLeft: 0
|
paddingLeft: 0,
|
||||||
|
alignSelf: 'center'
|
||||||
},
|
},
|
||||||
broadcastButton: {
|
broadcastButton: {
|
||||||
borderColor: '#1d74f5',
|
width: 107,
|
||||||
borderWidth: 2,
|
height: 44,
|
||||||
borderRadius: 2,
|
marginTop: 15
|
||||||
paddingVertical: 10,
|
},
|
||||||
width: 100,
|
broadcastButtonContainer: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginTop: 6
|
backgroundColor: '#1d74f5',
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
broadcastButtonIcon: {
|
||||||
|
width: 14,
|
||||||
|
height: 12,
|
||||||
|
marginRight: 11
|
||||||
},
|
},
|
||||||
broadcastButtonText: {
|
broadcastButtonText: {
|
||||||
color: '#1d74f5'
|
color: '#fff',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '500'
|
||||||
},
|
},
|
||||||
mention: {
|
mention: {
|
||||||
color: '#13679a'
|
color: '#0072FE',
|
||||||
|
fontWeight: '500',
|
||||||
|
padding: 5,
|
||||||
|
backgroundColor: '#E8F2FF'
|
||||||
|
},
|
||||||
|
mentionLoggedUser: {
|
||||||
|
color: '#fff',
|
||||||
|
backgroundColor: '#1D74F5'
|
||||||
|
},
|
||||||
|
mentionAll: {
|
||||||
|
color: '#fff',
|
||||||
|
backgroundColor: '#FF5B5A'
|
||||||
},
|
},
|
||||||
paragraph: {
|
paragraph: {
|
||||||
marginTop: 0,
|
marginTop: 0,
|
||||||
|
@ -115,11 +148,17 @@ export default StyleSheet.create({
|
||||||
image: {
|
image: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
maxWidth: 400,
|
maxWidth: 400,
|
||||||
height: 300
|
minHeight: 200,
|
||||||
|
borderRadius: 4,
|
||||||
|
marginBottom: 10
|
||||||
},
|
},
|
||||||
inlineImage: {
|
inlineImage: {
|
||||||
width: 300,
|
width: 300,
|
||||||
height: 300,
|
height: 300,
|
||||||
resizeMode: 'contain'
|
resizeMode: 'contain'
|
||||||
|
},
|
||||||
|
edited: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#9EA2A8'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -173,7 +173,6 @@ export default {
|
||||||
Leave_channel: 'Leave channel',
|
Leave_channel: 'Leave channel',
|
||||||
leave: 'leave',
|
leave: 'leave',
|
||||||
Livechat: 'Livechat',
|
Livechat: 'Livechat',
|
||||||
Loading_messages_ellipsis: 'Loading messages...',
|
|
||||||
Login: 'Login',
|
Login: 'Login',
|
||||||
Logout: 'Logout',
|
Logout: 'Logout',
|
||||||
Members: 'Members',
|
Members: 'Members',
|
||||||
|
|
|
@ -50,7 +50,7 @@ export default async function canOpenRoom({ rid, path }) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const data = await (this.ddp && this.ddp.status ? canOpenRoomDDP.call(this, { rid, type, name }) : canOpenRoomREST.call(this, { type, rid }));
|
const data = await (this.ddp && this.ddp.status && false ? canOpenRoomDDP.call(this, { rid, type, name }) : canOpenRoomREST.call(this, { type, rid }));
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('canOpenRoom', e);
|
log('canOpenRoom', e);
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default async function() {
|
||||||
return new Promise(async(resolve, reject) => {
|
return new Promise(async(resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const { subscriptions, rooms } = await (this.ddp.status ? getRoomDpp.apply(this) : getRoomRest.apply(this));
|
const { subscriptions, rooms } = await (this.ddp.status && false ? getRoomDpp.apply(this) : getRoomRest.apply(this));
|
||||||
|
|
||||||
const data = rooms.map(room => ({ room, sub: database.objects('subscriptions').filtered('rid == $0', room._id) }));
|
const data = rooms.map(room => ({ room, sub: database.objects('subscriptions').filtered('rid == $0', room._id) }));
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import reduxStore from '../createStore';
|
||||||
import database from '../realm';
|
import database from '../realm';
|
||||||
import * as actions from '../../actions';
|
import * as actions from '../../actions';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
import { settingsUpdatedAt } from '../../constants/settings';
|
||||||
|
|
||||||
const getLastUpdate = () => {
|
const getLastUpdate = () => {
|
||||||
const [setting] = database.objects('settings').sorted('_updatedAt', true);
|
const [setting] = database.objects('settings').sorted('_updatedAt', true);
|
||||||
|
@ -20,7 +21,8 @@ function updateServer(param) {
|
||||||
export default async function() {
|
export default async function() {
|
||||||
try {
|
try {
|
||||||
const lastUpdate = getLastUpdate();
|
const lastUpdate = getLastUpdate();
|
||||||
const result = await (!lastUpdate ? this.ddp.call('public-settings/get') : this.ddp.call('public-settings/get', new Date(lastUpdate)));
|
const fetchNewSettings = lastUpdate < settingsUpdatedAt;
|
||||||
|
const result = await ((!lastUpdate || fetchNewSettings) ? this.ddp.call('public-settings/get') : this.ddp.call('public-settings/get', new Date(lastUpdate)));
|
||||||
const data = result.update || result || [];
|
const data = result.update || result || [];
|
||||||
|
|
||||||
const filteredSettings = this._prepareSettings(this._filterSettings(data));
|
const filteredSettings = this._prepareSettings(this._filterSettings(data));
|
||||||
|
|
|
@ -155,9 +155,9 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ddp = new Ddp(url, login);
|
this.ddp = new Ddp(url, login);
|
||||||
// if (login) {
|
if (login) {
|
||||||
// protectedFunction(() => RocketChat.getRooms());
|
protectedFunction(() => RocketChat.getRooms());
|
||||||
// }
|
}
|
||||||
|
|
||||||
this.ddp.on('login', protectedFunction(() => reduxStore.dispatch(loginRequest())));
|
this.ddp.on('login', protectedFunction(() => reduxStore.dispatch(loginRequest())));
|
||||||
|
|
||||||
|
|
|
@ -122,12 +122,14 @@ const renderNumber = (unread, userMentions) => {
|
||||||
const attrs = ['name', 'unread', 'userMentions', 'alert', 'showLastMessage', 'type'];
|
const attrs = ['name', 'unread', 'userMentions', 'alert', 'showLastMessage', 'type'];
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
StoreLastMessage: state.settings.Store_Last_Message
|
StoreLastMessage: state.settings.Store_Last_Message,
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
}))
|
}))
|
||||||
export default class RoomItem extends React.Component {
|
export default class RoomItem extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
StoreLastMessage: PropTypes.bool,
|
StoreLastMessage: PropTypes.bool,
|
||||||
_updatedAt: PropTypes.instanceOf(Date),
|
_updatedAt: PropTypes.instanceOf(Date),
|
||||||
lastMessage: PropTypes.object,
|
lastMessage: PropTypes.object,
|
||||||
|
@ -162,8 +164,10 @@ export default class RoomItem extends React.Component {
|
||||||
return attrs.some(key => nextProps[key] !== this.props[key]);
|
return attrs.some(key => nextProps[key] !== this.props[key]);
|
||||||
}
|
}
|
||||||
get avatar() {
|
get avatar() {
|
||||||
const { type, name, avatarSize } = this.props;
|
const {
|
||||||
return <Avatar text={name} size={avatarSize} type={type} style={{ marginHorizontal: 15 }} />;
|
type, name, avatarSize, baseUrl
|
||||||
|
} = this.props;
|
||||||
|
return <Avatar text={name} size={avatarSize} type={type} baseUrl={baseUrl} style={{ marginHorizontal: 15 }} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
get lastMessage() {
|
get lastMessage() {
|
||||||
|
|
|
@ -42,11 +42,11 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
const UserItem = ({
|
const UserItem = ({
|
||||||
name, username, onPress, testID, onLongPress, style, icon
|
name, username, onPress, testID, onLongPress, style, icon, baseUrl
|
||||||
}) => (
|
}) => (
|
||||||
<Touch onPress={onPress} onLongPress={onLongPress} style={styles.button} testID={testID}>
|
<Touch onPress={onPress} onLongPress={onLongPress} style={styles.button} testID={testID}>
|
||||||
<View style={[styles.container, style]}>
|
<View style={[styles.container, style]}>
|
||||||
<Avatar text={username} size={30} type='d' style={styles.avatar} />
|
<Avatar text={username} size={30} type='d' style={styles.avatar} baseUrl={baseUrl} />
|
||||||
<View style={styles.textContainer}>
|
<View style={styles.textContainer}>
|
||||||
<Text style={styles.name}>{name}</Text>
|
<Text style={styles.name}>{name}</Text>
|
||||||
<Text style={styles.username}>@{username}</Text>
|
<Text style={styles.username}>@{username}</Text>
|
||||||
|
@ -59,6 +59,7 @@ const UserItem = ({
|
||||||
UserItem.propTypes = {
|
UserItem.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
username: PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
onPress: PropTypes.func.isRequired,
|
onPress: PropTypes.func.isRequired,
|
||||||
testID: PropTypes.string.isRequired,
|
testID: PropTypes.string.isRequired,
|
||||||
onLongPress: PropTypes.func,
|
onLongPress: PropTypes.func,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { BACKGROUND } from 'redux-enhancer-react-native-appstate';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
// import { roomsSuccess, roomsFailure } from '../actions/rooms';
|
// import { roomsSuccess, roomsFailure } from '../actions/rooms';
|
||||||
import { addUserTyping, removeUserTyping, setLastOpen } from '../actions/room';
|
import { addUserTyping, removeUserTyping, setLastOpen } from '../actions/room';
|
||||||
import { messagesRequest, editCancel } from '../actions/messages';
|
import { messagesRequest, editCancel, replyCancel } from '../actions/messages';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/realm';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
|
@ -93,6 +93,7 @@ const watchRoomOpen = function* watchRoomOpen({ room }) {
|
||||||
cancel(thread);
|
cancel(thread);
|
||||||
sub.stop();
|
sub.stop();
|
||||||
yield put(editCancel());
|
yield put(editCancel());
|
||||||
|
yield put(replyCancel());
|
||||||
|
|
||||||
// subscriptions.forEach((sub) => {
|
// subscriptions.forEach((sub) => {
|
||||||
// sub.unsubscribe().catch(e => alert(e));
|
// sub.unsubscribe().catch(e => alert(e));
|
||||||
|
|
|
@ -15,7 +15,7 @@ const Touch = ({ children, onPress, ...props }) => (
|
||||||
|
|
||||||
Touch.propTypes = {
|
Touch.propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
onPress: PropTypes.func.isRequired
|
onPress: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Touch;
|
export default Touch;
|
||||||
|
|
|
@ -16,7 +16,8 @@ import { showErrorAlert } from '../utils/info';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
backgroundColor: '#f7f8fa'
|
backgroundColor: '#f7f8fa',
|
||||||
|
flex: 1
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
@ -68,6 +69,7 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
createChannel: state.createChannel,
|
createChannel: state.createChannel,
|
||||||
users: state.selectedUsers.users
|
users: state.selectedUsers.users
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
|
@ -78,6 +80,7 @@ const styles = StyleSheet.create({
|
||||||
export default class CreateChannelView extends LoggedView {
|
export default class CreateChannelView extends LoggedView {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigator: PropTypes.object,
|
navigator: PropTypes.object,
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
create: PropTypes.func.isRequired,
|
create: PropTypes.func.isRequired,
|
||||||
removeUser: PropTypes.func.isRequired,
|
removeUser: PropTypes.func.isRequired,
|
||||||
createChannel: PropTypes.object.isRequired,
|
createChannel: PropTypes.object.isRequired,
|
||||||
|
@ -218,6 +221,7 @@ export default class CreateChannelView extends LoggedView {
|
||||||
username={item.name}
|
username={item.name}
|
||||||
onPress={() => this.removeUser(item)}
|
onPress={() => this.removeUser(item)}
|
||||||
testID={`create-channel-view-item-${ item.name }`}
|
testID={`create-channel-view-item-${ item.name }`}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -241,7 +245,7 @@ export default class CreateChannelView extends LoggedView {
|
||||||
contentContainerStyle={[sharedStyles.container, styles.container]}
|
contentContainerStyle={[sharedStyles.container, styles.container]}
|
||||||
keyboardVerticalOffset={128}
|
keyboardVerticalOffset={128}
|
||||||
>
|
>
|
||||||
<SafeAreaView testID='create-channel-view'>
|
<SafeAreaView testID='create-channel-view' style={styles.container}>
|
||||||
<ScrollView {...scrollPersistTaps}>
|
<ScrollView {...scrollPersistTaps}>
|
||||||
<View style={sharedStyles.separatorVertical}>
|
<View style={sharedStyles.separatorVertical}>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
|
|
@ -17,8 +17,7 @@ import I18n from '../../i18n';
|
||||||
id: state.login.user && state.login.user.id,
|
id: state.login.user && state.login.user.id,
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
},
|
}
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
openMentionedMessages: (rid, limit) => dispatch(openMentionedMessages(rid, limit)),
|
openMentionedMessages: (rid, limit) => dispatch(openMentionedMessages(rid, limit)),
|
||||||
closeMentionedMessages: () => dispatch(closeMentionedMessages())
|
closeMentionedMessages: () => dispatch(closeMentionedMessages())
|
||||||
|
@ -30,7 +29,6 @@ export default class MentionedMessagesView extends LoggedView {
|
||||||
messages: PropTypes.array,
|
messages: PropTypes.array,
|
||||||
ready: PropTypes.bool,
|
ready: PropTypes.bool,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
openMentionedMessages: PropTypes.func,
|
openMentionedMessages: PropTypes.func,
|
||||||
closeMentionedMessages: PropTypes.func
|
closeMentionedMessages: PropTypes.func
|
||||||
}
|
}
|
||||||
|
@ -87,9 +85,7 @@ export default class MentionedMessagesView extends LoggedView {
|
||||||
style={styles.message}
|
style={styles.message}
|
||||||
reactions={item.reactions}
|
reactions={item.reactions}
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
baseUrl={this.props.baseUrl}
|
|
||||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||||
onLongPress={() => {}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, StyleSheet, SafeAreaView, FlatList, Text, Platform, Image } from 'react-native';
|
import { View, StyleSheet, SafeAreaView, FlatList, Text, Platform, Image } from 'react-native';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import database from '../lib/realm';
|
import database from '../lib/realm';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
@ -40,8 +41,11 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@connect(state => ({
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class SelectedUsersView extends LoggedView {
|
export default class NewMessageView extends LoggedView {
|
||||||
static navigatorButtons = {
|
static navigatorButtons = {
|
||||||
leftButtons: [{
|
leftButtons: [{
|
||||||
id: 'cancel',
|
id: 'cancel',
|
||||||
|
@ -51,6 +55,7 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigator: PropTypes.object,
|
navigator: PropTypes.object,
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
onPressItem: PropTypes.func.isRequired
|
onPressItem: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -140,6 +145,7 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
name={item.search ? item.name : item.fname}
|
name={item.search ? item.name : item.fname}
|
||||||
username={item.search ? item.username : item.name}
|
username={item.search ? item.username : item.name}
|
||||||
onPress={() => this.onPressItem(item)}
|
onPress={() => this.onPressItem(item)}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
testID={`new-message-view-item-${ item.name }`}
|
testID={`new-message-view-item-${ item.name }`}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -32,10 +32,10 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
color: '#9EA2A8',
|
color: '#9EA2A8',
|
||||||
fontSize: moderateScale(17),
|
fontSize: 17,
|
||||||
paddingTop: scale(14),
|
paddingTop: 14,
|
||||||
paddingBottom: scale(14),
|
paddingBottom: 14,
|
||||||
paddingHorizontal: scale(16)
|
paddingHorizontal: 16
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ export default StyleSheet.create({
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
paddingHorizontal: scale(45),
|
paddingHorizontal: scale(45),
|
||||||
marginTop: verticalScale(30),
|
marginTop: verticalScale(30),
|
||||||
marginBottom: verticalScale(50),
|
marginBottom: verticalScale(35),
|
||||||
maxHeight: verticalScale(250),
|
maxHeight: verticalScale(250),
|
||||||
resizeMode: 'contain'
|
resizeMode: 'contain'
|
||||||
},
|
},
|
||||||
|
@ -48,13 +48,13 @@ export default StyleSheet.create({
|
||||||
marginTop: verticalScale(30)
|
marginTop: verticalScale(30)
|
||||||
},
|
},
|
||||||
buttonContainer: {
|
buttonContainer: {
|
||||||
marginHorizontal: scale(15),
|
marginHorizontal: 15,
|
||||||
marginVertical: scale(5),
|
marginVertical: 5,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
height: verticalScale(60),
|
height: 60,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderWidth: StyleSheet.hairlineWidth,
|
borderWidth: 1,
|
||||||
borderRadius: moderateScale(2)
|
borderRadius: 2
|
||||||
},
|
},
|
||||||
buttonCenter: {
|
buttonCenter: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
@ -62,13 +62,13 @@ export default StyleSheet.create({
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
},
|
},
|
||||||
buttonTitle: {
|
buttonTitle: {
|
||||||
fontSize: moderateScale(16),
|
fontSize: 16,
|
||||||
fontWeight: '600'
|
fontWeight: '600'
|
||||||
},
|
},
|
||||||
buttonSubtitle: {
|
buttonSubtitle: {
|
||||||
color: '#9EA2A8',
|
color: '#9EA2A8',
|
||||||
fontSize: moderateScale(14),
|
fontSize: 14,
|
||||||
height: moderateScale(18)
|
height: 18
|
||||||
},
|
},
|
||||||
buttonIconContainer: {
|
buttonIconContainer: {
|
||||||
width: 65,
|
width: 65,
|
||||||
|
@ -76,7 +76,7 @@ export default StyleSheet.create({
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
},
|
},
|
||||||
buttonIcon: {
|
buttonIcon: {
|
||||||
marginHorizontal: scale(10),
|
marginHorizontal: 10,
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20
|
height: 20
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,8 +23,7 @@ const options = [I18n.t('Unpin'), I18n.t('Cancel')];
|
||||||
id: state.login.user && state.login.user.id,
|
id: state.login.user && state.login.user.id,
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
},
|
}
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
openPinnedMessages: (rid, limit) => dispatch(openPinnedMessages(rid, limit)),
|
openPinnedMessages: (rid, limit) => dispatch(openPinnedMessages(rid, limit)),
|
||||||
closePinnedMessages: () => dispatch(closePinnedMessages()),
|
closePinnedMessages: () => dispatch(closePinnedMessages()),
|
||||||
|
@ -37,7 +36,6 @@ export default class PinnedMessagesView extends LoggedView {
|
||||||
messages: PropTypes.array,
|
messages: PropTypes.array,
|
||||||
ready: PropTypes.bool,
|
ready: PropTypes.bool,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
openPinnedMessages: PropTypes.func,
|
openPinnedMessages: PropTypes.func,
|
||||||
closePinnedMessages: PropTypes.func,
|
closePinnedMessages: PropTypes.func,
|
||||||
togglePinRequest: PropTypes.func
|
togglePinRequest: PropTypes.func
|
||||||
|
@ -113,7 +111,6 @@ export default class PinnedMessagesView extends LoggedView {
|
||||||
style={styles.message}
|
style={styles.message}
|
||||||
reactions={item.reactions}
|
reactions={item.reactions}
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
baseUrl={this.props.baseUrl}
|
|
||||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||||
onLongPress={this.onLongPress}
|
onLongPress={this.onLongPress}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -30,11 +30,13 @@ import Touch from '../../utils/touch';
|
||||||
customFields: state.login.user && state.login.user.customFields,
|
customFields: state.login.user && state.login.user.customFields,
|
||||||
emails: state.login.user && state.login.user.emails
|
emails: state.login.user && state.login.user.emails
|
||||||
},
|
},
|
||||||
Accounts_CustomFields: state.settings.Accounts_CustomFields
|
Accounts_CustomFields: state.settings.Accounts_CustomFields,
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class ProfileView extends LoggedView {
|
export default class ProfileView extends LoggedView {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
navigator: PropTypes.object,
|
navigator: PropTypes.object,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
Accounts_CustomFields: PropTypes.string
|
Accounts_CustomFields: PropTypes.string
|
||||||
|
@ -279,7 +281,7 @@ export default class ProfileView extends LoggedView {
|
||||||
renderAvatarButtons = () => (
|
renderAvatarButtons = () => (
|
||||||
<View style={styles.avatarButtons}>
|
<View style={styles.avatarButtons}>
|
||||||
{this.renderAvatarButton({
|
{this.renderAvatarButton({
|
||||||
child: <Avatar text={this.props.user.username} size={50} forceInitials />,
|
child: <Avatar text={this.props.user.username} size={50} baseUrl={this.props.baseUrl} forceInitials />,
|
||||||
onPress: () => this.resetAvatar(),
|
onPress: () => this.resetAvatar(),
|
||||||
key: 'profile-view-reset-avatar'
|
key: 'profile-view-reset-avatar'
|
||||||
})}
|
})}
|
||||||
|
@ -298,7 +300,7 @@ export default class ProfileView extends LoggedView {
|
||||||
const { url, blob, contentType } = this.state.avatarSuggestions[service];
|
const { url, blob, contentType } = this.state.avatarSuggestions[service];
|
||||||
return this.renderAvatarButton({
|
return this.renderAvatarButton({
|
||||||
key: `profile-view-avatar-${ service }`,
|
key: `profile-view-avatar-${ service }`,
|
||||||
child: <Avatar avatar={url} size={50} />,
|
child: <Avatar avatar={url} size={50} baseUrl={this.props.baseUrl} />,
|
||||||
onPress: () => this.setAvatar({
|
onPress: () => this.setAvatar({
|
||||||
url, data: blob, service, contentType
|
url, data: blob, service, contentType
|
||||||
})
|
})
|
||||||
|
@ -381,6 +383,7 @@ export default class ProfileView extends LoggedView {
|
||||||
text={username}
|
text={username}
|
||||||
avatar={this.state.avatar && this.state.avatar.url}
|
avatar={this.state.avatar && this.state.avatar.url}
|
||||||
size={100}
|
size={100}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<RCTextInput
|
<RCTextInput
|
||||||
|
|
|
@ -23,13 +23,15 @@ const renderSeparator = () => <View style={styles.separator} />;
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
userId: state.login.user && state.login.user.id,
|
userId: state.login.user && state.login.user.id,
|
||||||
username: state.login.user && state.login.user.username
|
username: state.login.user && state.login.user.username,
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
leaveRoom: rid => dispatch(leaveRoom(rid))
|
leaveRoom: rid => dispatch(leaveRoom(rid))
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class RoomActionsView extends LoggedView {
|
export default class RoomActionsView extends LoggedView {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
rid: PropTypes.string,
|
rid: PropTypes.string,
|
||||||
navigator: PropTypes.object,
|
navigator: PropTypes.object,
|
||||||
userId: PropTypes.string,
|
userId: PropTypes.string,
|
||||||
|
@ -343,6 +345,7 @@ export default class RoomActionsView extends LoggedView {
|
||||||
size={50}
|
size={50}
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
type={t}
|
type={t}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
>
|
>
|
||||||
{t === 'd' ? <Status style={sharedStyles.status} id={member._id} /> : null }
|
{t === 'd' ? <Status style={sharedStyles.status} id={member._id} /> : null }
|
||||||
</Avatar>,
|
</Avatar>,
|
||||||
|
|
|
@ -86,7 +86,6 @@ export default class RoomFilesView extends LoggedView {
|
||||||
reactions={item.reactions}
|
reactions={item.reactions}
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||||
onLongPress={() => {}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ export default class RoomInfoView extends LoggedView {
|
||||||
navigator: PropTypes.object,
|
navigator: PropTypes.object,
|
||||||
rid: PropTypes.string,
|
rid: PropTypes.string,
|
||||||
userId: PropTypes.string,
|
userId: PropTypes.string,
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
activeUsers: PropTypes.object,
|
activeUsers: PropTypes.object,
|
||||||
Message_TimeFormat: PropTypes.string,
|
Message_TimeFormat: PropTypes.string,
|
||||||
roles: PropTypes.object
|
roles: PropTypes.object
|
||||||
|
@ -192,6 +193,7 @@ export default class RoomInfoView extends LoggedView {
|
||||||
size={100}
|
size={100}
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
type={room.t}
|
type={room.t}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
>
|
>
|
||||||
{room.t === 'd' ? <Status style={[sharedStyles.status, styles.status]} id={roomUser._id} /> : null}
|
{room.t === 'd' ? <Status style={[sharedStyles.status, styles.status]} id={roomUser._id} /> : null}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
|
|
@ -12,13 +12,15 @@ const margin = Platform.OS === 'android' ? 40 : 20;
|
||||||
const tabEmojiStyle = { fontSize: 15 };
|
const tabEmojiStyle = { fontSize: 15 };
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
showReactionPicker: state.messages.showReactionPicker
|
showReactionPicker: state.messages.showReactionPicker,
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
|
toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
|
||||||
}))
|
}))
|
||||||
@responsive
|
@responsive
|
||||||
export default class ReactionPicker extends React.Component {
|
export default class ReactionPicker extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
window: PropTypes.any,
|
window: PropTypes.any,
|
||||||
showReactionPicker: PropTypes.bool,
|
showReactionPicker: PropTypes.bool,
|
||||||
toggleReactionPicker: PropTypes.func,
|
toggleReactionPicker: PropTypes.func,
|
||||||
|
@ -37,7 +39,7 @@ export default class ReactionPicker extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { window: { width, height }, showReactionPicker } = this.props;
|
const { window: { width, height }, showReactionPicker, baseUrl } = this.props;
|
||||||
|
|
||||||
return (showReactionPicker ?
|
return (showReactionPicker ?
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -56,6 +58,7 @@ export default class ReactionPicker extends React.Component {
|
||||||
tabEmojiStyle={tabEmojiStyle}
|
tabEmojiStyle={tabEmojiStyle}
|
||||||
width={Math.min(width, height) - margin}
|
width={Math.min(width, height) - margin}
|
||||||
onEmojiSelected={(emoji, shortname) => this.onEmojiSelected(emoji, shortname)}
|
onEmojiSelected={(emoji, shortname) => this.onEmojiSelected(emoji, shortname)}
|
||||||
|
baseUrl={baseUrl}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</Modal> : null
|
</Modal> : null
|
||||||
|
|
|
@ -9,51 +9,62 @@ const styles = StyleSheet.create({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginVertical: 10
|
marginBottom: 25,
|
||||||
|
marginTop: 15,
|
||||||
|
transform: [{ scaleY: -1 }]
|
||||||
},
|
},
|
||||||
line: {
|
line: {
|
||||||
borderTopColor: '#eaeaea',
|
backgroundColor: '#9ea2a8',
|
||||||
borderTopWidth: StyleSheet.hairlineWidth,
|
height: 1,
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
color: '#444444',
|
color: '#9ea2a8',
|
||||||
fontSize: 11,
|
fontSize: 14,
|
||||||
paddingHorizontal: 10,
|
fontWeight: '600'
|
||||||
transform: [{ scaleY: -1 }]
|
|
||||||
},
|
},
|
||||||
unreadLine: {
|
unreadLine: {
|
||||||
borderTopColor: 'red'
|
backgroundColor: '#f5455c'
|
||||||
},
|
},
|
||||||
unreadText: {
|
unreadText: {
|
||||||
color: 'red'
|
color: '#f5455c'
|
||||||
|
},
|
||||||
|
marginLeft: {
|
||||||
|
marginLeft: 10
|
||||||
|
},
|
||||||
|
marginRight: {
|
||||||
|
marginRight: 10
|
||||||
|
},
|
||||||
|
marginHorizontal: {
|
||||||
|
marginHorizontal: 10
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const DateSeparator = ({ ts, unread }) => {
|
const DateSeparator = ({ ts, unread }) => {
|
||||||
const date = ts ? moment(ts).format('MMMM DD, YYYY') : null;
|
const date = ts ? moment(ts).format('MMM DD, YYYY') : null;
|
||||||
if (ts && unread) {
|
if (ts && unread) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text style={[styles.text, styles.unreadText]}>{date}</Text>
|
<Text style={[styles.text, styles.unreadText, styles.marginLeft]}>{date}</Text>
|
||||||
<View style={[styles.line, styles.unreadLine]} />
|
<View style={[styles.line, styles.unreadLine, styles.marginHorizontal]} />
|
||||||
<Text style={[styles.text, styles.unreadText]}>{I18n.t('unread_messages')}</Text>
|
<Text style={[styles.text, styles.unreadText, styles.marginRight]}>{I18n.t('unread_messages')}</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (ts) {
|
if (ts) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.line} />
|
<View style={[styles.line, styles.marginLeft]} />
|
||||||
<Text style={styles.text}>{date}</Text>
|
<Text style={[styles.text, styles.marginHorizontal]}>{date}</Text>
|
||||||
<View style={styles.line} />
|
<View style={[styles.line, styles.marginRight]} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={[styles.line, styles.unreadLine]} />
|
<View style={[styles.line, styles.unreadLine, styles.marginLeft]} />
|
||||||
<Text style={[styles.text, styles.unreadText]}>{I18n.t('unread_messages')}</Text>
|
<Text style={[styles.text, styles.unreadText, styles.marginHorizontal]}>{I18n.t('unread_messages')}</Text>
|
||||||
|
<View style={[styles.line, styles.unreadLine, styles.marginRight]} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -232,15 +232,15 @@ export default class RoomView extends LoggedView {
|
||||||
<Message
|
<Message
|
||||||
key={item._id}
|
key={item._id}
|
||||||
item={item}
|
item={item}
|
||||||
_updatedAt={item._updatedAt}
|
|
||||||
status={item.status}
|
status={item.status}
|
||||||
reactions={JSON.parse(JSON.stringify(item.reactions))}
|
reactions={JSON.parse(JSON.stringify(item.reactions))}
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
onReactionPress={this.onReactionPress}
|
|
||||||
onLongPress={this.onMessageLongPress}
|
|
||||||
archived={this.state.room.archived}
|
archived={this.state.room.archived}
|
||||||
broadcast={this.state.room.broadcast}
|
broadcast={this.state.room.broadcast}
|
||||||
previousItem={previousItem}
|
previousItem={previousItem}
|
||||||
|
_updatedAt={item._updatedAt}
|
||||||
|
onReactionPress={this.onReactionPress}
|
||||||
|
onLongPress={this.onMessageLongPress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -273,7 +273,7 @@ export default class RoomView extends LoggedView {
|
||||||
|
|
||||||
renderHeader = () => {
|
renderHeader = () => {
|
||||||
if (!this.state.end) {
|
if (!this.state.end) {
|
||||||
return <Text style={styles.loadingMore}>{I18n.t('Loading_messages_ellipsis')}</Text>;
|
return <ActivityIndicator style={[styles.loading, { transform: [{ scaleY: -1 }] }]} />;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,8 @@ export default StyleSheet.create({
|
||||||
flexDirection: 'column'
|
flexDirection: 'column'
|
||||||
},
|
},
|
||||||
loading: {
|
loading: {
|
||||||
flex: 1
|
flex: 1,
|
||||||
|
marginVertical: 15
|
||||||
},
|
},
|
||||||
imageBackground: {
|
imageBackground: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
|
|
@ -7,7 +7,8 @@ import I18n from '../../../i18n';
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: 'center'
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
flexDirection: 'row'
|
flexDirection: 'row'
|
||||||
|
|
|
@ -43,7 +43,7 @@ if (Platform.OS === 'android') {
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
userId: state.login.user && state.login.user.id,
|
userId: state.login.user && state.login.user.id,
|
||||||
server: state.server.server,
|
server: state.server.server,
|
||||||
Site_Url: state.settings.Site_Url,
|
baseUrl: state.settings.baseUrl || state.server ? state.server.server : '',
|
||||||
searchText: state.rooms.searchText,
|
searchText: state.rooms.searchText,
|
||||||
loadingServer: state.server.loading,
|
loadingServer: state.server.loading,
|
||||||
showServerDropdown: state.rooms.showServerDropdown,
|
showServerDropdown: state.rooms.showServerDropdown,
|
||||||
|
@ -51,7 +51,8 @@ if (Platform.OS === 'android') {
|
||||||
sortBy: state.sortPreferences.sortBy,
|
sortBy: state.sortPreferences.sortBy,
|
||||||
groupByType: state.sortPreferences.groupByType,
|
groupByType: state.sortPreferences.groupByType,
|
||||||
showFavorites: state.sortPreferences.showFavorites,
|
showFavorites: state.sortPreferences.showFavorites,
|
||||||
showUnread: state.sortPreferences.showUnread
|
showUnread: state.sortPreferences.showUnread,
|
||||||
|
useRealName: state.settings.UI_Use_Real_Name
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
toggleSortDropdown: () => dispatch(toggleSortDropdown())
|
toggleSortDropdown: () => dispatch(toggleSortDropdown())
|
||||||
}))
|
}))
|
||||||
|
@ -72,7 +73,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigator: PropTypes.object,
|
navigator: PropTypes.object,
|
||||||
userId: PropTypes.string,
|
userId: PropTypes.string,
|
||||||
Site_Url: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
server: PropTypes.string,
|
server: PropTypes.string,
|
||||||
searchText: PropTypes.string,
|
searchText: PropTypes.string,
|
||||||
loadingServer: PropTypes.bool,
|
loadingServer: PropTypes.bool,
|
||||||
|
@ -82,12 +83,14 @@ export default class RoomsListView extends LoggedView {
|
||||||
groupByType: PropTypes.bool,
|
groupByType: PropTypes.bool,
|
||||||
showFavorites: PropTypes.bool,
|
showFavorites: PropTypes.bool,
|
||||||
showUnread: PropTypes.bool,
|
showUnread: PropTypes.bool,
|
||||||
toggleSortDropdown: PropTypes.func
|
toggleSortDropdown: PropTypes.func,
|
||||||
|
useRealName: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super('RoomsListView', props);
|
super('RoomsListView', props);
|
||||||
|
|
||||||
|
this.data = [];
|
||||||
this.state = {
|
this.state = {
|
||||||
search: [],
|
search: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
|
@ -396,18 +399,19 @@ export default class RoomsListView extends LoggedView {
|
||||||
|
|
||||||
renderItem = ({ item }) => {
|
renderItem = ({ item }) => {
|
||||||
const id = item.rid.replace(this.props.userId, '').trim();
|
const id = item.rid.replace(this.props.userId, '').trim();
|
||||||
|
const { useRealName } = this.props;
|
||||||
return (<RoomItem
|
return (<RoomItem
|
||||||
alert={item.alert}
|
alert={item.alert}
|
||||||
unread={item.unread}
|
unread={item.unread}
|
||||||
userMentions={item.userMentions}
|
userMentions={item.userMentions}
|
||||||
favorite={item.f}
|
favorite={item.f}
|
||||||
lastMessage={item.lastMessage}
|
lastMessage={item.lastMessage}
|
||||||
name={item.name}
|
name={(useRealName && item.fname) || item.name}
|
||||||
_updatedAt={item.roomUpdatedAt}
|
_updatedAt={item.roomUpdatedAt}
|
||||||
key={item._id}
|
key={item._id}
|
||||||
id={id}
|
id={id}
|
||||||
type={item.t}
|
type={item.t}
|
||||||
baseUrl={this.props.Site_Url}
|
baseUrl={this.props.baseUrl}
|
||||||
onPress={() => this._onPressItem(item)}
|
onPress={() => this._onPressItem(item)}
|
||||||
testID={`rooms-list-view-item-${ item.name }`}
|
testID={`rooms-list-view-item-${ item.name }`}
|
||||||
height={ROW_HEIGHT}
|
height={ROW_HEIGHT}
|
||||||
|
@ -417,6 +421,15 @@ export default class RoomsListView extends LoggedView {
|
||||||
renderSeparator = () => <View style={styles.separator} />;
|
renderSeparator = () => <View style={styles.separator} />;
|
||||||
|
|
||||||
renderSection = (data, header) => {
|
renderSection = (data, header) => {
|
||||||
|
if (header === 'Unread' && !this.props.showUnread) {
|
||||||
|
return null;
|
||||||
|
} else if (header === 'Favorites' && !this.props.showFavorites) {
|
||||||
|
return null;
|
||||||
|
} else if (['Channels', 'Direct_Messages', 'Private_Groups', 'Livechat'].includes(header) && !this.props.groupByType) {
|
||||||
|
return null;
|
||||||
|
} else if (header === 'Chats' && this.props.groupByType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
|
|
|
@ -97,9 +97,7 @@ export default class SearchMessagesView extends LoggedView {
|
||||||
style={styles.message}
|
style={styles.message}
|
||||||
reactions={item.reactions}
|
reactions={item.reactions}
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
baseUrl={this.props.baseUrl}
|
|
||||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||||
onLongPress={() => {}}
|
|
||||||
onReactionPress={async(emoji) => {
|
onReactionPress={async(emoji) => {
|
||||||
try {
|
try {
|
||||||
await RocketChat.setReaction(emoji, item._id);
|
await RocketChat.setReaction(emoji, item._id);
|
||||||
|
@ -124,7 +122,7 @@ export default class SearchMessagesView extends LoggedView {
|
||||||
placeholder={I18n.t('Search_Messages')}
|
placeholder={I18n.t('Search_Messages')}
|
||||||
testID='search-message-view-input'
|
testID='search-message-view-input'
|
||||||
/>
|
/>
|
||||||
<Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} />
|
<Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} username='' baseUrl='' customEmojis={{}} />
|
||||||
<View style={styles.divider} />
|
<View style={styles.divider} />
|
||||||
</View>
|
</View>
|
||||||
<FlatList
|
<FlatList
|
||||||
|
|
|
@ -29,6 +29,7 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
users: state.selectedUsers.users,
|
users: state.selectedUsers.users,
|
||||||
loading: state.selectedUsers.loading
|
loading: state.selectedUsers.loading
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
|
@ -43,6 +44,7 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
navigator: PropTypes.object,
|
navigator: PropTypes.object,
|
||||||
rid: PropTypes.string,
|
rid: PropTypes.string,
|
||||||
nextAction: PropTypes.string.isRequired,
|
nextAction: PropTypes.string.isRequired,
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
addUser: PropTypes.func.isRequired,
|
addUser: PropTypes.func.isRequired,
|
||||||
removeUser: PropTypes.func.isRequired,
|
removeUser: PropTypes.func.isRequired,
|
||||||
reset: PropTypes.func.isRequired,
|
reset: PropTypes.func.isRequired,
|
||||||
|
@ -185,6 +187,7 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
username={item.name}
|
username={item.name}
|
||||||
onPress={() => this._onPressSelectedItem(item)}
|
onPress={() => this._onPressSelectedItem(item)}
|
||||||
testID={`selected-user-${ item.name }`}
|
testID={`selected-user-${ item.name }`}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
style={{ paddingRight: 15 }}
|
style={{ paddingRight: 15 }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -211,6 +214,7 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
onPress={() => this._onPressItem(item._id, item)}
|
onPress={() => this._onPressItem(item._id, item)}
|
||||||
testID={`select-users-view-item-${ item.name }`}
|
testID={`select-users-view-item-${ item.name }`}
|
||||||
icon={this.isChecked(username) ? 'check' : null}
|
icon={this.isChecked(username) ? 'check' : null}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,8 +17,7 @@ import I18n from '../../i18n';
|
||||||
id: state.login.user && state.login.user.id,
|
id: state.login.user && state.login.user.id,
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
},
|
}
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
openSnippetedMessages: (rid, limit) => dispatch(openSnippetedMessages(rid, limit)),
|
openSnippetedMessages: (rid, limit) => dispatch(openSnippetedMessages(rid, limit)),
|
||||||
closeSnippetedMessages: () => dispatch(closeSnippetedMessages())
|
closeSnippetedMessages: () => dispatch(closeSnippetedMessages())
|
||||||
|
@ -30,7 +29,6 @@ export default class SnippetedMessagesView extends LoggedView {
|
||||||
messages: PropTypes.array,
|
messages: PropTypes.array,
|
||||||
ready: PropTypes.bool,
|
ready: PropTypes.bool,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
openSnippetedMessages: PropTypes.func,
|
openSnippetedMessages: PropTypes.func,
|
||||||
closeSnippetedMessages: PropTypes.func
|
closeSnippetedMessages: PropTypes.func
|
||||||
}
|
}
|
||||||
|
@ -87,9 +85,7 @@ export default class SnippetedMessagesView extends LoggedView {
|
||||||
style={styles.message}
|
style={styles.message}
|
||||||
reactions={item.reactions}
|
reactions={item.reactions}
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
baseUrl={this.props.baseUrl}
|
|
||||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||||
onLongPress={() => {}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,7 @@ const options = [I18n.t('Unstar'), I18n.t('Cancel')];
|
||||||
id: state.login.user && state.login.user.id,
|
id: state.login.user && state.login.user.id,
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
},
|
}
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
openStarredMessages: (rid, limit) => dispatch(openStarredMessages(rid, limit)),
|
openStarredMessages: (rid, limit) => dispatch(openStarredMessages(rid, limit)),
|
||||||
closeStarredMessages: () => dispatch(closeStarredMessages()),
|
closeStarredMessages: () => dispatch(closeStarredMessages()),
|
||||||
|
@ -37,7 +36,6 @@ export default class StarredMessagesView extends LoggedView {
|
||||||
messages: PropTypes.array,
|
messages: PropTypes.array,
|
||||||
ready: PropTypes.bool,
|
ready: PropTypes.bool,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
openStarredMessages: PropTypes.func,
|
openStarredMessages: PropTypes.func,
|
||||||
closeStarredMessages: PropTypes.func,
|
closeStarredMessages: PropTypes.func,
|
||||||
toggleStarRequest: PropTypes.func
|
toggleStarRequest: PropTypes.func
|
||||||
|
@ -113,7 +111,6 @@ export default class StarredMessagesView extends LoggedView {
|
||||||
style={styles.message}
|
style={styles.message}
|
||||||
reactions={item.reactions}
|
reactions={item.reactions}
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
baseUrl={this.props.baseUrl}
|
|
||||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||||
onLongPress={this.onLongPress}
|
onLongPress={this.onLongPress}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe('Rooms list screen', () => {
|
||||||
// });
|
// });
|
||||||
|
|
||||||
it('should have room item', async() => {
|
it('should have room item', async() => {
|
||||||
await expect(element(by.id('rooms-list-view-item-general'))).toExist();
|
await expect(element(by.id('rooms-list-view-item-general')).atIndex(0)).toExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render - Header
|
// Render - Header
|
||||||
|
|
|
@ -88,8 +88,8 @@ describe('Create room screen', () => {
|
||||||
|
|
||||||
it('should navigate to create channel view', async() => {
|
it('should navigate to create channel view', async() => {
|
||||||
await element(by.id('selected-users-view-submit')).tap();
|
await element(by.id('selected-users-view-submit')).tap();
|
||||||
await waitFor(element(by.id('create-channel-view'))).toBeVisible().withTimeout(5000);
|
await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(5000);
|
||||||
await expect(element(by.id('create-channel-view'))).toBeVisible();
|
await expect(element(by.id('create-channel-view'))).toExist();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -145,6 +145,7 @@ describe('Create room screen', () => {
|
||||||
await expect(element(by.text(`private${ data.random }`))).toBeVisible();
|
await expect(element(by.text(`private${ data.random }`))).toBeVisible();
|
||||||
await tapBack(2);
|
await tapBack(2);
|
||||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('rooms-list-view-search')).replaceText(`private${ data.random }`);
|
||||||
await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible().withTimeout(60000);
|
await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible().withTimeout(60000);
|
||||||
await expect(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible();
|
await expect(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,10 +9,11 @@ async function mockMessage(message) {
|
||||||
await element(by.id('messagebox-input')).tap();
|
await element(by.id('messagebox-input')).tap();
|
||||||
await element(by.id('messagebox-input')).typeText(`${ data.random }${ message }`);
|
await element(by.id('messagebox-input')).typeText(`${ data.random }${ message }`);
|
||||||
await element(by.id('messagebox-send-message')).tap();
|
await element(by.id('messagebox-send-message')).tap();
|
||||||
await waitFor(element(by.text(`${ data.random }${ message }`))).toBeVisible().withTimeout(60000);
|
await waitFor(element(by.text(`${ data.random }${ message }`))).toExist().withTimeout(60000);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function navigateToRoom() {
|
async function navigateToRoom() {
|
||||||
|
await element(by.id('rooms-list-view-search')).replaceText(`private${ data.random }`);
|
||||||
await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible().withTimeout(60000);
|
await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible().withTimeout(60000);
|
||||||
await element(by.id(`rooms-list-view-item-private${ data.random }`)).tap();
|
await element(by.id(`rooms-list-view-item-private${ data.random }`)).tap();
|
||||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||||
|
@ -92,8 +93,7 @@ describe('Room screen', () => {
|
||||||
describe('Messagebox', async() => {
|
describe('Messagebox', async() => {
|
||||||
it('should send message', async() => {
|
it('should send message', async() => {
|
||||||
await mockMessage('message');
|
await mockMessage('message');
|
||||||
await waitFor(element(by.text(`${ data.random }message`))).toBeVisible().withTimeout(60000);
|
await expect(element(by.text(`${ data.random }message`))).toExist();
|
||||||
await expect(element(by.text(`${ data.random }message`))).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show/hide emoji keyboard', async() => {
|
it('should show/hide emoji keyboard', async() => {
|
||||||
|
@ -139,9 +139,9 @@ describe('Room screen', () => {
|
||||||
await element(by.id(`mention-item-${ data.user }`)).tap();
|
await element(by.id(`mention-item-${ data.user }`)).tap();
|
||||||
await expect(element(by.id('messagebox-input'))).toHaveText(`@${ data.user } `);
|
await expect(element(by.id('messagebox-input'))).toHaveText(`@${ data.user } `);
|
||||||
await element(by.id('messagebox-input')).tap();
|
await element(by.id('messagebox-input')).tap();
|
||||||
await element(by.id('messagebox-input')).typeText('test');
|
await element(by.id('messagebox-input')).typeText(`${ data.random }mention`);
|
||||||
await element(by.id('messagebox-send-message')).tap();
|
await element(by.id('messagebox-send-message')).tap();
|
||||||
await waitFor(element(by.text(`@${ data.user } test`))).toBeVisible().withTimeout(60000);
|
await waitFor(element(by.text(`@${ data.user } ${ data.random }mention`))).toBeVisible().withTimeout(60000);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show and tap on room autocomplete', async() => {
|
it('should show and tap on room autocomplete', async() => {
|
||||||
|
@ -250,8 +250,8 @@ describe('Room screen', () => {
|
||||||
await element(by.text('Edit')).tap();
|
await element(by.text('Edit')).tap();
|
||||||
await element(by.id('messagebox-input')).typeText('ed');
|
await element(by.id('messagebox-input')).typeText('ed');
|
||||||
await element(by.id('messagebox-send-message')).tap();
|
await element(by.id('messagebox-send-message')).tap();
|
||||||
await waitFor(element(by.text(`${ data.random }edited`))).toBeVisible().withTimeout(60000);
|
await waitFor(element(by.text(`${ data.random }edited (edited)`))).toBeVisible().withTimeout(60000);
|
||||||
await expect(element(by.text(`${ data.random }edited`))).toBeVisible();
|
await expect(element(by.text(`${ data.random }edited (edited)`))).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should quote message', async() => {
|
it('should quote message', async() => {
|
||||||
|
@ -266,13 +266,15 @@ describe('Room screen', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pin message', async() => {
|
it('should pin message', async() => {
|
||||||
await element(by.text(`${ data.random }edited`)).longPress();
|
await waitFor(element(by.text(`${ data.random }edited (edited)`))).toBeVisible().whileElement(by.id('room-view-messages')).scroll(200, 'up');
|
||||||
|
await element(by.text(`${ data.random }edited (edited)`)).longPress();
|
||||||
await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000);
|
await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000);
|
||||||
await expect(element(by.text('Message actions'))).toBeVisible();
|
await expect(element(by.text('Message actions'))).toBeVisible();
|
||||||
await element(by.text('Pin')).tap();
|
await element(by.text('Pin')).tap();
|
||||||
await waitFor(element(by.text('Message actions'))).toBeNotVisible().withTimeout(5000);
|
await waitFor(element(by.text('Message actions'))).toBeNotVisible().withTimeout(5000);
|
||||||
await waitFor(element(by.text(`${ data.random }edited`)).atIndex(1)).toBeVisible().withTimeout(60000);
|
await waitFor(element(by.text(`${ data.random }edited (edited)`))).toBeVisible().whileElement(by.id('room-view-messages')).scroll(200, 'up');
|
||||||
await element(by.text(`${ data.random }edited`)).atIndex(0).longPress();
|
await waitFor(element(by.text(`${ data.random }edited (edited)`)).atIndex(1)).toBeVisible().withTimeout(60000);
|
||||||
|
await element(by.text(`${ data.random }edited (edited)`)).atIndex(0).longPress();
|
||||||
await waitFor(element(by.text('Unpin'))).toBeVisible().withTimeout(2000);
|
await waitFor(element(by.text('Unpin'))).toBeVisible().withTimeout(2000);
|
||||||
await expect(element(by.text('Unpin'))).toBeVisible();
|
await expect(element(by.text('Unpin'))).toBeVisible();
|
||||||
await element(by.text('Cancel')).tap();
|
await element(by.text('Cancel')).tap();
|
||||||
|
|
|
@ -14,7 +14,7 @@ async function navigateToRoomActions(type) {
|
||||||
} else {
|
} else {
|
||||||
room = `private${ data.random }`;
|
room = `private${ data.random }`;
|
||||||
}
|
}
|
||||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(2000);
|
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(2000);
|
||||||
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
|
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
|
||||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
|
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
|
||||||
await element(by.id('room-view-header-actions')).tap();
|
await element(by.id('room-view-header-actions')).tap();
|
||||||
|
@ -210,8 +210,9 @@ describe('Room actions screen', () => {
|
||||||
it('should show mentioned messages', async() => {
|
it('should show mentioned messages', async() => {
|
||||||
await element(by.id('room-actions-mentioned')).tap();
|
await element(by.id('room-actions-mentioned')).tap();
|
||||||
await waitFor(element(by.id('mentioned-messages-view'))).toExist().withTimeout(2000);
|
await waitFor(element(by.id('mentioned-messages-view'))).toExist().withTimeout(2000);
|
||||||
await waitFor(element(by.text(`@${ data.user } test`).withAncestor(by.id('mentioned-messages-view')))).toBeVisible().withTimeout(60000);
|
await expect(element(by.id('mentioned-messages-view'))).toExist();
|
||||||
await expect(element(by.text(`@${ data.user } test`).withAncestor(by.id('mentioned-messages-view')))).toBeVisible();
|
// await waitFor(element(by.text(` ${ data.random }mention`))).toBeVisible().withTimeout(60000);
|
||||||
|
// await expect(element(by.text(` ${ data.random }mention`))).toBeVisible();
|
||||||
await backToActions();
|
await backToActions();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -233,14 +234,14 @@ describe('Room actions screen', () => {
|
||||||
await waitFor(element(by.id('room-actions-pinned'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
|
await waitFor(element(by.id('room-actions-pinned'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
|
||||||
await element(by.id('room-actions-pinned')).tap();
|
await element(by.id('room-actions-pinned')).tap();
|
||||||
await waitFor(element(by.id('pinned-messages-view'))).toExist().withTimeout(2000);
|
await waitFor(element(by.id('pinned-messages-view'))).toExist().withTimeout(2000);
|
||||||
await waitFor(element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view'))).atIndex(0)).toBeVisible().withTimeout(60000);
|
await waitFor(element(by.text(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view'))).atIndex(0)).toBeVisible().withTimeout(60000);
|
||||||
await expect(element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view')))).toBeVisible();
|
await expect(element(by.text(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view')))).toBeVisible();
|
||||||
await element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view'))).longPress();
|
await element(by.text(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view'))).longPress();
|
||||||
await waitFor(element(by.text('Unpin'))).toBeVisible().withTimeout(2000);
|
await waitFor(element(by.text('Unpin'))).toBeVisible().withTimeout(2000);
|
||||||
await expect(element(by.text('Unpin'))).toBeVisible();
|
await expect(element(by.text('Unpin'))).toBeVisible();
|
||||||
await element(by.text('Unpin')).tap();
|
await element(by.text('Unpin')).tap();
|
||||||
await waitFor(element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view'))).atIndex(0)).toBeNotVisible().withTimeout(60000);
|
await waitFor(element(by.text(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view'))).atIndex(0)).toBeNotVisible().withTimeout(60000);
|
||||||
await expect(element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view')))).toBeNotVisible();
|
await expect(element(by.text(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view')))).toBeNotVisible();
|
||||||
await backToActions();
|
await backToActions();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ async function navigateToRoomInfo(type) {
|
||||||
} else {
|
} else {
|
||||||
room = `private${ data.random }`;
|
room = `private${ data.random }`;
|
||||||
}
|
}
|
||||||
|
await element(by.id('rooms-list-view-search')).replaceText(room);
|
||||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(2000);
|
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(2000);
|
||||||
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
|
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
|
||||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
|
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
@ -310,6 +311,7 @@ describe('Room info screen', () => {
|
||||||
await expect(element(by.text('Yes, delete it!'))).toBeVisible();
|
await expect(element(by.text('Yes, delete it!'))).toBeVisible();
|
||||||
await element(by.text('Yes, delete it!')).tap();
|
await element(by.text('Yes, delete it!')).tap();
|
||||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||||
|
await element(by.id('rooms-list-view-search')).replaceText('');
|
||||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible().withTimeout(60000);
|
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible().withTimeout(60000);
|
||||||
await expect(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible();
|
await expect(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible();
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,14 +36,14 @@ describe('Broadcast room', () => {
|
||||||
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
|
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
|
||||||
await tapBack();
|
await tapBack();
|
||||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
|
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
|
||||||
await tapBack(2);
|
// await tapBack(2);
|
||||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
// await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||||
await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist().withTimeout(60000);
|
// await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist().withTimeout(60000);
|
||||||
await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist();
|
// await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send message', async() => {
|
it('should send message', async() => {
|
||||||
await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap();
|
// await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap();
|
||||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||||
await element(by.id('messagebox-input')).tap();
|
await element(by.id('messagebox-input')).tap();
|
||||||
await element(by.id('messagebox-input')).typeText(`${ data.random }message`);
|
await element(by.id('messagebox-input')).typeText(`${ data.random }message`);
|
||||||
|
|
|
@ -2,7 +2,7 @@ const random = require('./helpers/random');
|
||||||
const value = random(20);
|
const value = random(20);
|
||||||
const data = {
|
const data = {
|
||||||
server: 'https://stable.rocket.chat',
|
server: 'https://stable.rocket.chat',
|
||||||
alternateServer: 'https://open.rocket.chat',
|
alternateServer: 'https://unstable.rocket.chat',
|
||||||
user: `user${ value }`,
|
user: `user${ value }`,
|
||||||
password: `password${ value }`,
|
password: `password${ value }`,
|
||||||
alternateUser: 'detox',
|
alternateUser: 'detox',
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "pause.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "pause@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "pause@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 687 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "play.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "play@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "play@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 740 B |
After Width: | Height: | Size: 1.6 KiB |