[NEW] Basic support to Teams (#3016)

* Database migration

* RoomItem icon

* Team icons

* Teams group

* Small tweak on RoomTypeIcon

* RoomView Header

* Add team's channels to RoomView header

* Starting TeamChannelsView

* Icon size

* o data found

* Update TeamChannelsView, add teams subscriptions and send params to TeamChannelsView

* Use teams.ListRooms endpoint, render rooms list, remove unused functions

* Show team main on TeamChannelsView

* Disable swipe

* Pagination working

* Fix blinking no data found

* Search working

* Refactor to use BackgroundContainer while loading

* Go to room

* Cleanup

* Go to actions

* Events

* Lint

* Add debounce to go room

* Fix for tablet

* i18n

* Small fix

* Minor refactor

* Use local data when it exists

* Show last message

* Force teams migration

* Add stories to BackgroundContainer

* Remove unused component

* Move RoomViewHeader into containers folder

* Refactoring

* Testing RoomHeader

* i18n

* Fix server endpoint version

* Fix events

Co-authored-by: Gerzon Z <gerzonzcanario@gmail.com>
This commit is contained in:
Diego Mello 2021-04-07 15:31:25 -03:00 committed by GitHub
parent 5a96f1ff6b
commit d04d0f27b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 5456 additions and 186 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
import React from 'react';
import {
ImageBackground, StyleSheet, Text, View
ImageBackground, StyleSheet, Text, View, ActivityIndicator
} from 'react-native';
import PropTypes from 'prop-types';
import { withTheme } from '../../theme';
import sharedStyles from '../Styles';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
const styles = StyleSheet.create({
@ -29,15 +29,17 @@ const styles = StyleSheet.create({
}
});
const EmptyRoom = ({ theme, text }) => (
const BackgroundContainer = ({ theme, text, loading }) => (
<View style={styles.container}>
<ImageBackground source={{ uri: `message_empty_${ theme }` }} style={styles.image} />
<Text style={[styles.text, { color: themes[theme].auxiliaryTintColor }]}>{text}</Text>
{text ? <Text style={[styles.text, { color: themes[theme].auxiliaryTintColor }]}>{text}</Text> : null}
{loading ? <ActivityIndicator style={[styles.text, { color: themes[theme].auxiliaryTintColor }]} /> : null}
</View>
);
EmptyRoom.propTypes = {
BackgroundContainer.propTypes = {
text: PropTypes.string,
theme: PropTypes.string
theme: PropTypes.string,
loading: PropTypes.bool
};
export default withTheme(EmptyRoom);
export default withTheme(BackgroundContainer);

View File

@ -0,0 +1,49 @@
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions, react/prop-types */
import React from 'react';
import { storiesOf } from '@storybook/react-native';
import BackgroundContainer from '.';
import { ThemeContext } from '../../theme';
import { longText } from '../../../storybook/utils';
const stories = storiesOf('BackgroundContainer', module);
stories.add('basic', () => (
<BackgroundContainer />
));
stories.add('loading', () => (
<BackgroundContainer loading />
));
stories.add('text', () => (
<BackgroundContainer text='Text here' />
));
stories.add('long text', () => (
<BackgroundContainer text={longText} />
));
const ThemeStory = ({ theme, ...props }) => (
<ThemeContext.Provider
value={{ theme }}
>
<BackgroundContainer {...props} />
</ThemeContext.Provider>
);
stories.add('dark theme - loading', () => (
<ThemeStory theme='dark' loading />
));
stories.add('dark theme - text', () => (
<ThemeStory theme='dark' text={longText} />
));
stories.add('black theme - loading', () => (
<ThemeStory theme='black' loading />
));
stories.add('black theme - text', () => (
<ThemeStory theme='black' text={longText} />
));

View File

@ -4,16 +4,21 @@ import {
View, Text, StyleSheet, TouchableOpacity
} from 'react-native';
import I18n from '../../../i18n';
import sharedStyles from '../../Styles';
import { themes } from '../../../constants/colors';
import Markdown from '../../../containers/markdown';
import RoomTypeIcon from '../../../containers/RoomTypeIcon';
import I18n from '../../i18n';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import Markdown from '../markdown';
import RoomTypeIcon from '../RoomTypeIcon';
import { withTheme } from '../../theme';
const HIT_SLOP = {
top: 5, right: 5, bottom: 5, left: 5
};
const TITLE_SIZE = 16;
const SUBTITLE_SIZE = 12;
const getSubTitleSize = scale => SUBTITLE_SIZE * scale;
const styles = StyleSheet.create({
container: {
flex: 1,
@ -24,12 +29,12 @@ const styles = StyleSheet.create({
flexDirection: 'row'
},
title: {
...sharedStyles.textSemibold,
fontSize: TITLE_SIZE
flexShrink: 1,
...sharedStyles.textSemibold
},
subtitle: {
...sharedStyles.textRegular,
fontSize: 12
flexShrink: 1,
...sharedStyles.textRegular
},
typingUsers: {
...sharedStyles.textSemibold
@ -37,8 +42,9 @@ const styles = StyleSheet.create({
});
const SubTitle = React.memo(({
usersTyping, subtitle, renderFunc, theme
usersTyping, subtitle, renderFunc, theme, scale
}) => {
const fontSize = getSubTitleSize(scale);
// typing
if (usersTyping.length) {
let usersText;
@ -48,7 +54,7 @@ const SubTitle = React.memo(({
usersText = usersTyping.join(', ');
}
return (
<Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
<Text style={[styles.subtitle, { fontSize, color: themes[theme].auxiliaryText }]} numberOfLines={1}>
<Text style={styles.typingUsers}>{usersText} </Text>
{ usersTyping.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing') }...
</Text>
@ -66,7 +72,7 @@ const SubTitle = React.memo(({
<Markdown
preview
msg={subtitle}
style={[styles.subtitle, { color: themes[theme].auxiliaryText }]}
style={[styles.subtitle, { fontSize, color: themes[theme].auxiliaryText }]}
numberOfLines={1}
theme={theme}
/>
@ -80,18 +86,20 @@ SubTitle.propTypes = {
usersTyping: PropTypes.array,
theme: PropTypes.string,
subtitle: PropTypes.string,
renderFunc: PropTypes.func
renderFunc: PropTypes.func,
scale: PropTypes.number
};
const HeaderTitle = React.memo(({
title, tmid, prid, scale, theme
title, tmid, prid, scale, theme, testID
}) => {
const titleStyle = { fontSize: TITLE_SIZE * scale, color: themes[theme].headerTitleColor };
if (!tmid && !prid) {
return (
<Text
style={[styles.title, { fontSize: TITLE_SIZE * scale, color: themes[theme].headerTitleColor }]}
style={[styles.title, titleStyle]}
numberOfLines={1}
testID={`room-view-title-${ title }`}
testID={testID}
>
{title}
</Text>
@ -102,10 +110,10 @@ const HeaderTitle = React.memo(({
<Markdown
preview
msg={title}
style={[styles.title, { fontSize: TITLE_SIZE * scale, color: themes[theme].headerTitleColor }]}
style={[styles.title, titleStyle]}
numberOfLines={1}
theme={theme}
testID={`room-view-title-${ title }`}
testID={testID}
/>
);
});
@ -115,11 +123,12 @@ HeaderTitle.propTypes = {
tmid: PropTypes.string,
prid: PropTypes.string,
scale: PropTypes.number,
theme: PropTypes.string
theme: PropTypes.string,
testID: PropTypes.string
};
const Header = React.memo(({
title, subtitle, parentTitle, type, status, usersTyping, width, height, prid, tmid, connecting, goRoomActionsView, theme, isGroupChat
title, subtitle, parentTitle, type, status, usersTyping, width, height, prid, tmid, onPress, theme, isGroupChat, teamMain, testID
}) => {
const portrait = height > width;
let scale = 1;
@ -130,13 +139,11 @@ const Header = React.memo(({
}
}
const onPress = () => goRoomActionsView();
let renderFunc;
if (tmid) {
renderFunc = () => (
<View style={styles.titleContainer}>
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} />
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />
<Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{parentTitle}</Text>
</View>
);
@ -144,7 +151,7 @@ const Header = React.memo(({
return (
<TouchableOpacity
testID='room-view-header-actions'
testID='room-header'
accessibilityLabel={title}
onPress={onPress}
style={styles.container}
@ -152,17 +159,23 @@ const Header = React.memo(({
hitSlop={HIT_SLOP}
>
<View style={styles.titleContainer}>
{tmid ? null : <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} />}
{tmid ? null : <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />}
<HeaderTitle
title={title}
tmid={tmid}
prid={prid}
scale={scale}
connecting={connecting}
theme={theme}
testID={testID}
/>
</View>
<SubTitle usersTyping={tmid ? [] : usersTyping} subtitle={subtitle} theme={theme} renderFunc={renderFunc} />
<SubTitle
usersTyping={tmid ? [] : usersTyping}
subtitle={subtitle}
theme={theme}
renderFunc={renderFunc}
scale={scale}
/>
</TouchableOpacity>
);
});
@ -175,17 +188,18 @@ Header.propTypes = {
height: PropTypes.number.isRequired,
prid: PropTypes.string,
tmid: PropTypes.string,
teamMain: PropTypes.bool,
status: PropTypes.string,
theme: PropTypes.string,
usersTyping: PropTypes.array,
connecting: PropTypes.bool,
isGroupChat: PropTypes.bool,
parentTitle: PropTypes.string,
goRoomActionsView: PropTypes.func
onPress: PropTypes.func,
testID: PropTypes.string
};
Header.defaultProps = {
usersTyping: []
};
export default Header;
export default withTheme(Header);

View File

@ -0,0 +1,94 @@
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions, react/prop-types, react/destructuring-assignment */
import React from 'react';
import { View, Dimensions } from 'react-native';
import { storiesOf } from '@storybook/react-native';
import RoomHeaderComponent from './RoomHeader';
import Header from '../Header';
import { longText } from '../../../storybook/utils';
import { ThemeContext } from '../../theme';
const stories = storiesOf('RoomHeader', module);
// TODO: refactor after react-navigation v6
const HeaderExample = ({ title }) => (
<Header
headerTitle={() => (
<View style={{ flex: 1, paddingHorizontal: 12 }}>
{title()}
</View>
)}
/>
);
const { width, height } = Dimensions.get('window');
const RoomHeader = ({ ...props }) => (
<RoomHeaderComponent width={width} height={height} title='title' type='p' testID={props.title} onPress={() => alert('header pressed!')} {...props} />
);
stories.add('title and subtitle', () => (
<>
<HeaderExample title={() => <RoomHeader title='title' type='p' />} />
<HeaderExample title={() => <RoomHeader title={longText} type='p' />} />
<HeaderExample title={() => <RoomHeader subtitle='subtitle' />} />
<HeaderExample title={() => <RoomHeader subtitle={longText} />} />
<HeaderExample title={() => <RoomHeader title={longText} subtitle={longText} />} />
</>
));
stories.add('icons', () => (
<>
<HeaderExample title={() => <RoomHeader title='private channel' type='p' />} />
<HeaderExample title={() => <RoomHeader title='public channel' type='c' />} />
<HeaderExample title={() => <RoomHeader title='discussion' prid='asd' />} />
<HeaderExample title={() => <RoomHeader title='omnichannel' type='l' />} />
<HeaderExample title={() => <RoomHeader title='private team' type='p' teamMain />} />
<HeaderExample title={() => <RoomHeader title='public team' type='c' teamMain />} />
<HeaderExample title={() => <RoomHeader title='group dm' type='d' isGroupChat />} />
<HeaderExample title={() => <RoomHeader title='online dm' type='d' status='online' />} />
<HeaderExample title={() => <RoomHeader title='away dm' type='d' status='away' />} />
<HeaderExample title={() => <RoomHeader title='busy dm' type='d' status='busy' />} />
<HeaderExample title={() => <RoomHeader title='loading dm' type='d' status='loading' />} />
<HeaderExample title={() => <RoomHeader title='offline dm' type='d' />} />
</>
));
stories.add('typing', () => (
<>
<HeaderExample title={() => <RoomHeader usersTyping={['user 1']} />} />
<HeaderExample title={() => <RoomHeader usersTyping={['user 1', 'user 2']} />} />
<HeaderExample title={() => <RoomHeader usersTyping={['user 1', 'user 2', 'user 3', 'user 4', 'user 5']} />} />
</>
));
stories.add('landscape', () => (
<>
<HeaderExample title={() => <RoomHeader width={height} height={width} />} />
<HeaderExample title={() => <RoomHeader width={height} height={width} subtitle='subtitle' />} />
<HeaderExample title={() => <RoomHeader width={height} height={width} title={longText} subtitle={longText} />} />
</>
));
stories.add('thread', () => (
<>
<HeaderExample title={() => <RoomHeader tmid='123' parentTitle='parent title' />} />
<HeaderExample title={() => <RoomHeader tmid='123' title={'markdown\npreview\n#3\n4\n5'} parentTitle={longText} />} />
</>
));
const ThemeStory = ({ theme }) => (
<ThemeContext.Provider
value={{ theme }}
>
<HeaderExample title={() => <RoomHeader subtitle='subtitle' />} />
</ThemeContext.Provider>
);
stories.add('themes', () => (
<>
<ThemeStory theme='light' />
<ThemeStory theme='dark' />
<ThemeStory theme='black' />
</>
));

View File

@ -3,42 +3,37 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { dequal } from 'dequal';
import Header from './Header';
import LeftButtons from './LeftButtons';
import RightButtons from './RightButtons';
import { withTheme } from '../../../theme';
import { withDimensions } from '../../../dimensions';
import I18n from '../../../i18n';
import RoomHeader from './RoomHeader';
import { withDimensions } from '../../dimensions';
import I18n from '../../i18n';
class RoomHeaderView extends Component {
class RoomHeaderContainer extends Component {
static propTypes = {
title: PropTypes.string,
subtitle: PropTypes.string,
type: PropTypes.string,
prid: PropTypes.string,
tmid: PropTypes.string,
teamMain: PropTypes.bool,
usersTyping: PropTypes.string,
status: PropTypes.string,
statusText: PropTypes.string,
connecting: PropTypes.bool,
connected: PropTypes.bool,
theme: PropTypes.string,
roomUserId: PropTypes.string,
widthOffset: PropTypes.number,
goRoomActionsView: PropTypes.func,
onPress: PropTypes.func,
width: PropTypes.number,
height: PropTypes.number,
parentTitle: PropTypes.string,
isGroupChat: PropTypes.bool
isGroupChat: PropTypes.bool,
testID: PropTypes.string
};
shouldComponentUpdate(nextProps) {
const {
type, title, subtitle, status, statusText, connecting, connected, goRoomActionsView, usersTyping, theme, width, height
type, title, subtitle, status, statusText, connecting, connected, onPress, usersTyping, width, height
} = this.props;
if (nextProps.theme !== theme) {
return true;
}
if (nextProps.type !== type) {
return true;
}
@ -69,7 +64,7 @@ class RoomHeaderView extends Component {
if (!dequal(nextProps.usersTyping, usersTyping)) {
return true;
}
if (nextProps.goRoomActionsView !== goRoomActionsView) {
if (nextProps.onPress !== onPress) {
return true;
}
return false;
@ -80,6 +75,7 @@ class RoomHeaderView extends Component {
title,
subtitle: subtitleProp,
type,
teamMain,
prid,
tmid,
widthOffset,
@ -88,13 +84,13 @@ class RoomHeaderView extends Component {
connecting,
connected,
usersTyping,
goRoomActionsView,
onPress,
roomUserId,
theme,
width,
height,
parentTitle,
isGroupChat
isGroupChat,
testID
} = this.props;
let subtitle;
@ -107,23 +103,24 @@ class RoomHeaderView extends Component {
}
return (
<Header
<RoomHeader
prid={prid}
tmid={tmid}
title={title}
subtitle={type === 'd' ? statusText : subtitle}
type={type}
teamMain={teamMain}
status={status}
width={width}
height={height}
theme={theme}
usersTyping={usersTyping}
widthOffset={widthOffset}
roomUserId={roomUserId}
goRoomActionsView={goRoomActionsView}
connecting={connecting}
parentTitle={parentTitle}
isGroupChat={isGroupChat}
testID={testID}
onPress={onPress}
/>
);
}
@ -153,6 +150,4 @@ const mapStateToProps = (state, ownProps) => {
};
};
export default connect(mapStateToProps)(withDimensions(withTheme(RoomHeaderView)));
export { RightButtons, LeftButtons };
export default connect(mapStateToProps)(withDimensions(RoomHeaderContainer));

View File

@ -13,7 +13,7 @@ const styles = StyleSheet.create({
});
const RoomTypeIcon = React.memo(({
type, size, isGroupChat, status, style, theme
type, size, isGroupChat, status, style, theme, teamMain
}) => {
if (!type) {
return null;
@ -31,7 +31,9 @@ const RoomTypeIcon = React.memo(({
}
let icon = 'channel-private';
if (type === 'discussion') {
if (teamMain) {
icon = `teams${ type === 'p' ? '-private' : '' }`;
} else if (type === 'discussion') {
icon = 'discussions';
} else if (type === 'c') {
icon = 'channel-public';
@ -58,6 +60,7 @@ RoomTypeIcon.propTypes = {
theme: PropTypes.string,
type: PropTypes.string,
isGroupChat: PropTypes.bool,
teamMain: PropTypes.bool,
status: PropTypes.string,
size: PropTypes.number,
style: PropTypes.object

View File

@ -706,5 +706,8 @@
"Enter_workspace_URL": "Enter workspace URL",
"Workspace_URL_Example": "Ex. your-company.rocket.chat",
"This_room_encryption_has_been_enabled_by__username_": "This room's encryption has been enabled by {{username}}",
"This_room_encryption_has_been_disabled_by__username_": "This room's encryption has been disabled by {{username}}"
"This_room_encryption_has_been_disabled_by__username_": "This room's encryption has been disabled by {{username}}",
"Teams": "Teams",
"No_team_channels_found": "No channels found",
"Team_not_found": "Team not found"
}

View File

@ -656,5 +656,8 @@
"This_room_encryption_has_been_disabled_by__username_": "A criptografia para essa sala foi desabilitada por {{username}}",
"Apply_Your_Certificate": "Aplicar certificado",
"Do_you_have_a_certificate": "Você tem um certificado?",
"Your_certificate": "Seu certificado"
"Your_certificate": "Seu certificado",
"Teams": "Times",
"No_team_channels_found": "Nenhum canal encontrado",
"Team_not_found": "Time não encontrado"
}

View File

@ -125,4 +125,8 @@ export default class Subscription extends Model {
@field('e2e_key_id') e2eKeyId;
@field('avatar_etag') avatarETag;
@field('team_id') teamId;
@field('team_main') teamMain;
}

View File

@ -212,6 +212,18 @@ export default schemaMigrations({
]
})
]
},
{
toVersion: 13,
steps: [
addColumns({
table: 'subscriptions',
columns: [
{ name: 'team_id', type: 'string', isIndexed: true },
{ name: 'team_main', type: 'boolean', isOptional: true }
]
})
]
}
]
});

View File

@ -1,7 +1,7 @@
import { appSchema, tableSchema } from '@nozbe/watermelondb';
export default appSchema({
version: 12,
version: 13,
tables: [
tableSchema({
name: 'subscriptions',
@ -57,7 +57,9 @@ export default appSchema({
{ name: 'e2e_key', type: 'string', isOptional: true },
{ name: 'encrypted', type: 'boolean', isOptional: true },
{ name: 'e2e_key_id', type: 'string', isOptional: true },
{ name: 'avatar_etag', type: 'string', isOptional: true }
{ name: 'avatar_etag', type: 'string', isOptional: true },
{ name: 'team_id', type: 'string', isIndexed: true },
{ name: 'team_main', type: 'boolean', isOptional: true }
]
}),
tableSchema({

View File

@ -42,6 +42,8 @@ export const merge = (subscription, room) => {
subscription.encrypted = room.encrypted;
subscription.e2eKeyId = room.e2eKeyId;
subscription.avatarETag = room.avatarETag;
subscription.teamId = room.teamId;
subscription.teamMain = room.teamMain;
if (!subscription.roles || !subscription.roles.length) {
subscription.roles = [];
}

View File

@ -931,6 +931,19 @@ const RocketChat = {
// RC 2.3.0
return this.sdk.get('livechat/visitors.info', { visitorId });
},
getTeamListRoom({
teamId, count, offset, type, filter
}) {
const params = {
teamId, count, offset, type
};
if (filter) {
params.filter = filter;
}
// RC 3.13.0
return this.sdk.get('teams.listRooms', params);
},
closeLivechat(rid, comment) {
// RC 0.29.0
return this.methodCallWrapper('livechat:closeRoom', rid, comment, { clientAction: true });

View File

@ -44,7 +44,8 @@ const RoomItem = ({
onPress,
toggleFav,
toggleRead,
hideChannel
hideChannel,
teamMain
}) => (
<Touchable
onPress={onPress}
@ -78,6 +79,8 @@ const RoomItem = ({
prid={prid}
status={status}
isGroupChat={isGroupChat}
theme={theme}
teamMain={teamMain}
/>
<Title
name={name}
@ -120,6 +123,8 @@ const RoomItem = ({
prid={prid}
status={status}
isGroupChat={isGroupChat}
theme={theme}
teamMain={teamMain}
/>
<Title
name={name}
@ -159,6 +164,7 @@ RoomItem.propTypes = {
isFocused: PropTypes.bool,
isGroupChat: PropTypes.bool,
isRead: PropTypes.bool,
teamMain: PropTypes.bool,
date: PropTypes.string,
accessibilityLabel: PropTypes.string,
lastMessage: PropTypes.object,

View File

@ -4,14 +4,15 @@ import PropTypes from 'prop-types';
import RoomTypeIcon from '../../containers/RoomTypeIcon';
const TypeIcon = React.memo(({
type, prid, status, isGroupChat
}) => <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} />);
type, prid, status, isGroupChat, teamMain
}) => <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />);
TypeIcon.propTypes = {
type: PropTypes.string,
status: PropTypes.string,
prid: PropTypes.string,
isGroupChat: PropTypes.bool
isGroupChat: PropTypes.bool,
teamMain: PropTypes.bool
};
export default TypeIcon;

View File

@ -188,6 +188,7 @@ class RoomItemContainer extends React.Component {
tunreadUser={item.tunreadUser}
tunreadGroup={item.tunreadGroup}
swipeEnabled={swipeEnabled}
teamMain={item.teamMain}
/>
);
}

View File

@ -12,6 +12,7 @@ import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRo
import RocketChat from '../lib/rocketchat';
import buildMessage from '../lib/methods/helpers/buildMessage';
import protectedFunction from '../lib/methods/helpers/protectedFunction';
import UserPreferences from '../lib/userPreferences';
const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) {
const serversDB = database.servers;
@ -47,6 +48,16 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) {
// Server not found
}
}
// Force fetch all subscriptions to update columns related to Teams feature
// TODO: remove it a couple of releases
const teamsMigrationKey = `${ server }_TEAMS_MIGRATION`;
const teamsMigration = yield UserPreferences.getBoolAsync(teamsMigrationKey);
if (!teamsMigration) {
roomsUpdatedAt = null;
UserPreferences.setBoolAsync(teamsMigrationKey, true);
}
const [subscriptionsResult, roomsResult] = yield RocketChat.getRooms(roomsUpdatedAt);
const { subscriptions } = yield mergeSubscriptionsRooms(subscriptionsResult, roomsResult);

View File

@ -29,6 +29,7 @@ import ForwardLivechatView from '../views/ForwardLivechatView';
import LivechatEditView from '../views/LivechatEditView';
import PickerView from '../views/PickerView';
import ThreadMessagesView from '../views/ThreadMessagesView';
import TeamChannelsView from '../views/TeamChannelsView';
import MarkdownTableView from '../views/MarkdownTableView';
import ReadReceiptsView from '../views/ReadReceiptView';
import { themes } from '../constants/colors';
@ -168,6 +169,11 @@ const ChatsStackNavigator = () => {
component={ThreadMessagesView}
options={ThreadMessagesView.navigationOptions}
/>
<ChatsStack.Screen
name='TeamChannelsView'
component={TeamChannelsView}
options={TeamChannelsView.navigationOptions}
/>
<ChatsStack.Screen
name='MarkdownTableView'
component={MarkdownTableView}

View File

@ -30,6 +30,7 @@ import ForwardLivechatView from '../../views/ForwardLivechatView';
import LivechatEditView from '../../views/LivechatEditView';
import PickerView from '../../views/PickerView';
import ThreadMessagesView from '../../views/ThreadMessagesView';
import TeamChannelsView from '../../views/TeamChannelsView';
import MarkdownTableView from '../../views/MarkdownTableView';
import ReadReceiptsView from '../../views/ReadReceiptView';
import ProfileView from '../../views/ProfileView';
@ -193,6 +194,11 @@ const ModalStackNavigator = React.memo(({ navigation }) => {
name='ThreadMessagesView'
component={ThreadMessagesView}
/>
<ModalStack.Screen
name='TeamChannelsView'
component={TeamChannelsView}
options={TeamChannelsView.navigationOptions}
/>
<ModalStack.Screen
name='MarkdownTableView'
component={MarkdownTableView}

View File

@ -2,7 +2,7 @@ import Navigation from '../lib/Navigation';
import RocketChat from '../lib/rocketchat';
const navigate = ({ item, isMasterDetail, ...props }) => {
let navigationMethod = Navigation.navigate;
let navigationMethod = props.navigationMethod ?? Navigation.navigate;
if (isMasterDetail) {
navigationMethod = Navigation.replace;

View File

@ -212,6 +212,7 @@ export default {
ROOM_JOIN: 'room_join',
ROOM_GO_RA: 'room_go_ra',
ROOM_TOGGLE_FOLLOW_THREADS: 'room_toggle_follow_threads',
ROOM_GO_TEAM_CHANNELS: 'room_go_team_channels',
ROOM_GO_SEARCH: 'room_go_search',
ROOM_GO_THREADS: 'room_go_threads',
ROOM_GO_ROOM_INFO: 'room_go_room_info',
@ -310,5 +311,11 @@ export default {
// E2E ENCRYPTION SECURITY VIEW
E2E_SEC_CHANGE_PASSWORD: 'e2e_sec_change_password',
E2E_SEC_RESET_OWN_KEY: 'e2e_sec_reset_own_key'
E2E_SEC_RESET_OWN_KEY: 'e2e_sec_reset_own_key',
// TEAM CHANNELS VIEW
TC_SEARCH: 'tc_search',
TC_CANCEL_SEARCH: 'tc_cancel_search',
TC_GO_ACTIONS: 'tc_go_actions',
TC_GO_ROOM: 'tc_go_room'
};

View File

@ -175,7 +175,7 @@ class NewMessageView extends React.Component {
{maxUsers > 2 ? this.renderButton({
onPress: this.createGroupChat,
title: I18n.t('Create_Direct_Messages'),
icon: 'team',
icon: 'message',
testID: 'new-message-view-create-direct-message'
}) : null}
{this.renderButton({

View File

@ -461,7 +461,7 @@ class RoomActionsView extends React.Component {
? <Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}>{room.fname}</Text>
: (
<View style={styles.roomTitleRow}>
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} status={room.visitor?.status} />
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} teamMain={room.teamMain} status={room.visitor?.status} />
<Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}>{RocketChat.getRoomTitle(room)}</Text>
</View>
)

View File

@ -41,7 +41,7 @@ const getRoomTitle = (room, type, name, username, statusText, theme) => (type ==
)
: (
<View style={styles.roomTitleRow}>
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} key='room-info-type' status={room.visitor?.status} />
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} teamMain={room.teamMain} key='room-info-type' status={room.visitor?.status} />
<Text testID='room-info-view-name' style={[styles.roomTitle, { color: themes[theme].titleText }]} key='room-info-name'>{RocketChat.getRoomTitle(room)}</Text>
</View>
)

View File

@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
import { StyleSheet } from 'react-native';
import { HeaderBackButton } from '@react-navigation/stack';
import { themes } from '../../../constants/colors';
import Avatar from '../../../containers/Avatar';
import { themes } from '../../constants/colors';
import Avatar from '../../containers/Avatar';
const styles = StyleSheet.create({
avatar: {

View File

@ -3,10 +3,10 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { dequal } from 'dequal';
import * as HeaderButton from '../../../containers/HeaderButton';
import database from '../../../lib/database';
import { getUserSelector } from '../../../selectors/login';
import { logEvent, events } from '../../../utils/log';
import * as HeaderButton from '../../containers/HeaderButton';
import database from '../../lib/database';
import { getUserSelector } from '../../selectors/login';
import { logEvent, events } from '../../utils/log';
class RightButtonsContainer extends Component {
static propTypes = {
@ -15,6 +15,7 @@ class RightButtonsContainer extends Component {
rid: PropTypes.string,
t: PropTypes.string,
tmid: PropTypes.string,
teamId: PropTypes.bool,
navigation: PropTypes.object,
isMasterDetail: PropTypes.bool,
toggleFollowThread: PropTypes.func
@ -109,6 +110,21 @@ class RightButtonsContainer extends Component {
});
}
goTeamChannels = () => {
logEvent(events.ROOM_GO_TEAM_CHANNELS);
const {
navigation, isMasterDetail, teamId
} = this.props;
if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', {
screen: 'TeamChannelsView',
params: { teamId }
});
} else {
navigation.navigate('TeamChannelsView', { teamId });
}
}
goThreadsView = () => {
logEvent(events.ROOM_GO_THREADS);
const {
@ -146,7 +162,9 @@ class RightButtonsContainer extends Component {
const {
isFollowingThread, tunread, tunreadUser, tunreadGroup
} = this.state;
const { t, tmid, threadsEnabled } = this.props;
const {
t, tmid, threadsEnabled, teamId
} = this.props;
if (t === 'l') {
return null;
}
@ -163,6 +181,13 @@ class RightButtonsContainer extends Component {
}
return (
<HeaderButton.Container>
{teamId ? (
<HeaderButton.Item
iconName='channel-public'
onPress={this.goTeamChannels}
testID='room-view-header-team-channels'
/>
) : null}
{threadsEnabled ? (
<HeaderButton.Item
iconName='threads'

View File

@ -29,7 +29,9 @@ import styles from './styles';
import log, { logEvent, events } from '../../utils/log';
import EventEmitter from '../../utils/events';
import I18n from '../../i18n';
import RoomHeaderView, { RightButtons, LeftButtons } from './Header';
import RoomHeader from '../../containers/RoomHeader';
import LeftButtons from './LeftButtons';
import RightButtons from './RightButtons';
import StatusBar from '../../containers/StatusBar';
import Separator from './Separator';
import { themes } from '../../constants/colors';
@ -317,13 +319,23 @@ class RoomView extends React.Component {
}
const subtitle = room?.topic;
const t = room?.t;
const teamMain = room?.teamMain;
const teamId = room?.teamId;
const { id: userId, token } = user;
const avatar = room?.name;
const visitor = room?.visitor;
if (!room?.rid) {
return;
}
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: tmid ? 1 : 2 });
let numIconsRight = 2;
if (tmid) {
numIconsRight = 1;
} else if (teamId) {
numIconsRight = 3;
}
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight });
navigation.setOptions({
headerShown: true,
headerTitleAlign: 'left',
@ -347,24 +359,27 @@ class RoomView extends React.Component {
/>
),
headerTitle: () => (
<RoomHeaderView
<RoomHeader
rid={rid}
prid={prid}
tmid={tmid}
title={title}
teamMain={teamMain}
parentTitle={parentTitle}
subtitle={subtitle}
type={t}
roomUserId={roomUserId}
visitor={visitor}
isGroupChat={isGroupChat}
goRoomActionsView={this.goRoomActionsView}
onPress={this.goRoomActionsView}
testID={`room-view-title-${ title }`}
/>
),
headerRight: () => (
<RightButtons
rid={rid}
tmid={tmid}
teamId={teamId}
t={t}
navigation={navigation}
toggleFollowThread={this.toggleFollowThread}

View File

@ -68,6 +68,7 @@ const CHATS_HEADER = 'Chats';
const UNREAD_HEADER = 'Unread';
const FAVORITES_HEADER = 'Favorites';
const DISCUSSIONS_HEADER = 'Discussions';
const TEAMS_HEADER = 'Teams';
const CHANNELS_HEADER = 'Channels';
const DM_HEADER = 'Direct_Messages';
const GROUPS_HEADER = 'Private_Groups';
@ -77,6 +78,8 @@ const QUERY_SIZE = 20;
const filterIsUnread = s => (s.unread > 0 || s.tunread?.length > 0 || s.alert) && !s.hideUnreadStatus;
const filterIsFavorite = s => s.f;
const filterIsOmnichannel = s => s.t === 'l';
const filterIsTeam = s => s.teamMain;
const filterIsDiscussion = s => s.prid;
const shouldUpdateProps = [
'searchText',
@ -475,10 +478,12 @@ class RoomsListView extends React.Component {
// type
if (groupByType) {
const discussions = chats.filter(s => s.prid);
const channels = chats.filter(s => s.t === 'c' && !s.prid);
const privateGroup = chats.filter(s => s.t === 'p' && !s.prid);
const direct = chats.filter(s => s.t === 'd' && !s.prid);
const teams = chats.filter(s => filterIsTeam(s));
const discussions = chats.filter(s => filterIsDiscussion(s));
const channels = chats.filter(s => s.t === 'c' && !filterIsDiscussion(s) && !filterIsTeam(s));
const privateGroup = chats.filter(s => s.t === 'p' && !filterIsDiscussion(s) && !filterIsTeam(s));
const direct = chats.filter(s => s.t === 'd' && !filterIsDiscussion(s) && !filterIsTeam(s));
tempChats = this.addRoomsGroup(teams, TEAMS_HEADER, tempChats);
tempChats = this.addRoomsGroup(discussions, DISCUSSIONS_HEADER, tempChats);
tempChats = this.addRoomsGroup(channels, CHANNELS_HEADER, tempChats);
tempChats = this.addRoomsGroup(privateGroup, GROUPS_HEADER, tempChats);

View File

@ -0,0 +1,369 @@
import React from 'react';
import { Keyboard } from 'react-native';
import PropTypes from 'prop-types';
import { Q } from '@nozbe/watermelondb';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
import { connect } from 'react-redux';
import { FlatList } from 'react-native-gesture-handler';
import { HeaderBackButton } from '@react-navigation/stack';
import StatusBar from '../containers/StatusBar';
import RoomHeader from '../containers/RoomHeader';
import { withTheme } from '../theme';
import SearchHeader from './ThreadMessagesView/SearchHeader';
import log, { events, logEvent } from '../utils/log';
import database from '../lib/database';
import { getUserSelector } from '../selectors/login';
import { getHeaderTitlePosition } from '../containers/Header';
import * as HeaderButton from '../containers/HeaderButton';
import BackgroundContainer from '../containers/BackgroundContainer';
import SafeAreaView from '../containers/SafeAreaView';
import ActivityIndicator from '../containers/ActivityIndicator';
import RoomItem, { ROW_HEIGHT } from '../presentation/RoomItem';
import RocketChat from '../lib/rocketchat';
import { withDimensions } from '../dimensions';
import { isIOS } from '../utils/deviceInfo';
import { themes } from '../constants/colors';
import debounce from '../utils/debounce';
import { showErrorAlert } from '../utils/info';
import { goRoom } from '../utils/goRoom';
import I18n from '../i18n';
const API_FETCH_COUNT = 50;
const getItemLayout = (data, index) => ({
length: data.length,
offset: ROW_HEIGHT * index,
index
});
const keyExtractor = item => item._id;
class TeamChannelsView extends React.Component {
static propTypes = {
route: PropTypes.object,
navigation: PropTypes.object,
isMasterDetail: PropTypes.bool,
insets: PropTypes.object,
theme: PropTypes.string,
useRealName: PropTypes.bool,
width: PropTypes.number,
StoreLastMessage: PropTypes.bool
}
constructor(props) {
super(props);
this.teamId = props.route.params?.teamId;
this.state = {
loading: true,
loadingMore: false,
data: [],
total: -1,
isSearching: false,
searchText: '',
search: []
};
this.loadTeam();
}
componentDidMount() {
this.load();
}
loadTeam = async() => {
const db = database.active;
try {
const subCollection = db.get('subscriptions');
this.teamChannels = await subCollection.query(
Q.where('team_id', Q.eq(this.teamId))
);
this.team = this.teamChannels?.find(channel => channel.teamMain);
this.setHeader();
if (!this.team) {
throw new Error();
}
} catch {
const { navigation } = this.props;
navigation.pop();
showErrorAlert(I18n.t('Team_not_found'));
}
}
load = debounce(async() => {
const {
loadingMore, total, data, search, isSearching, searchText
} = this.state;
const length = isSearching ? search.length : data.length;
if (loadingMore || length === total) {
return;
}
this.setState({ loadingMore: true });
try {
const result = await RocketChat.getTeamListRoom({
teamId: this.teamId,
offset: length,
count: API_FETCH_COUNT,
type: 'all',
filter: searchText
});
if (result.success) {
const newState = {
loading: false,
loadingMore: false,
total: result.total
};
const rooms = result.rooms.map((room) => {
const record = this.teamChannels?.find(c => c.rid === room._id);
return record ?? room;
});
if (isSearching) {
newState.search = [...search, ...rooms];
} else {
newState.data = [...data, ...rooms];
}
this.setState(newState);
} else {
this.setState({ loading: false, loadingMore: false });
}
} catch (e) {
log(e);
this.setState({ loading: false, loadingMore: false });
}
}, 300)
getHeader = () => {
const { isSearching } = this.state;
const {
navigation, isMasterDetail, insets, theme
} = this.props;
const { team } = this;
if (!team) {
return;
}
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 1 });
if (isSearching) {
return {
headerTitleAlign: 'left',
headerLeft: () => (
<HeaderButton.Container left>
<HeaderButton.Item
iconName='close'
onPress={this.onCancelSearchPress}
/>
</HeaderButton.Container>
),
headerTitle: () => <SearchHeader onSearchChangeText={this.onSearchChangeText} />,
headerTitleContainerStyle: {
left: headerTitlePosition.left,
right: headerTitlePosition.right
},
headerRight: () => null
};
}
const options = {
headerShown: true,
headerTitleAlign: 'left',
headerTitleContainerStyle: {
left: headerTitlePosition.left,
right: headerTitlePosition.right
},
headerTitle: () => (
<RoomHeader
title={RocketChat.getRoomTitle(team)}
subtitle={team.topic}
type={team.t}
onPress={this.goRoomActionsView}
teamMain
/>
)
};
if (isMasterDetail) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
} else {
options.headerLeft = () => (
<HeaderBackButton
labelVisible={false}
onPress={() => navigation.pop()}
tintColor={themes[theme].headerTintColor}
/>
);
}
options.headerRight = () => (
<HeaderButton.Container>
<HeaderButton.Item iconName='search' onPress={this.onSearchPress} />
</HeaderButton.Container>
);
return options;
}
setHeader = () => {
const { navigation } = this.props;
const options = this.getHeader();
navigation.setOptions(options);
}
onSearchPress = () => {
logEvent(events.TC_SEARCH);
this.setState({ isSearching: true }, () => this.setHeader());
}
onSearchChangeText = debounce((searchText) => {
this.setState({
searchText, search: [], loading: !!searchText, loadingMore: false, total: -1
}, () => {
if (searchText) {
this.load();
}
});
}, 300)
onCancelSearchPress = () => {
logEvent(events.TC_CANCEL_SEARCH);
const { isSearching } = this.state;
if (!isSearching) {
return;
}
Keyboard.dismiss();
this.setState({ isSearching: false, search: [] }, () => {
this.setHeader();
});
};
goRoomActionsView = (screen) => {
logEvent(events.TC_GO_ACTIONS);
const { team } = this;
const {
navigation, isMasterDetail
} = this.props;
if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', {
screen: screen ?? 'RoomActionsView',
params: {
rid: team.rid, t: team.t, room: team, showCloseModal: false
}
});
} else {
navigation.navigate('RoomActionsView', {
rid: team.rid, t: team.t, room: team
});
}
}
getRoomTitle = item => RocketChat.getRoomTitle(item)
getRoomAvatar = item => RocketChat.getRoomAvatar(item)
onPressItem = debounce(async(item) => {
logEvent(events.TC_GO_ROOM);
const { navigation, isMasterDetail } = this.props;
try {
let params = {};
if (item.rid) {
params = item;
} else {
const { room } = await RocketChat.getRoomInfo(item._id);
params = {
rid: item._id, name: RocketChat.getRoomTitle(room), joinCodeRequired: room.joinCodeRequired, t: room.t, teamId: room.teamId
};
}
if (isMasterDetail) {
navigation.pop();
}
goRoom({ item: params, isMasterDetail, navigationMethod: navigation.push });
} catch (e) {
// do nothing
}
}, 1000, true);
renderItem = ({ item }) => {
const {
StoreLastMessage,
useRealName,
theme,
width
} = this.props;
return (
<RoomItem
item={item}
theme={theme}
type={item.t}
showLastMessage={StoreLastMessage}
onPress={this.onPressItem}
width={width}
useRealName={useRealName}
getRoomTitle={this.getRoomTitle}
getRoomAvatar={this.getRoomAvatar}
swipeEnabled={false}
/>
);
};
renderFooter = () => {
const { loadingMore } = this.state;
const { theme } = this.props;
if (loadingMore) {
return <ActivityIndicator theme={theme} />;
}
return null;
}
renderScroll = () => {
const {
loading, data, search, isSearching, searchText
} = this.state;
if (loading) {
return <BackgroundContainer loading />;
}
if (isSearching && !search.length) {
return <BackgroundContainer text={searchText ? I18n.t('No_team_channels_found') : ''} />;
}
if (!isSearching && !data.length) {
return <BackgroundContainer text={I18n.t('No_team_channels_found')} />;
}
return (
<FlatList
data={isSearching ? search : data}
extraData={isSearching ? search : data}
keyExtractor={keyExtractor}
renderItem={this.renderItem}
getItemLayout={getItemLayout}
removeClippedSubviews={isIOS}
keyboardShouldPersistTaps='always'
onEndReached={() => this.load()}
onEndReachedThreshold={0.5}
ListFooterComponent={this.renderFooter}
/>
);
};
render() {
console.count(`${ this.constructor.name }.render calls`);
return (
<SafeAreaView testID='team-channels-view'>
<StatusBar />
{this.renderScroll()}
</SafeAreaView>
);
}
}
const mapStateToProps = state => ({
baseUrl: state.server.server,
user: getUserSelector(state),
useRealName: state.settings.UI_Use_Real_Name,
isMasterDetail: state.app.isMasterDetail,
StoreLastMessage: state.settings.Store_Last_Message
});
export default connect(mapStateToProps)(withDimensions(withSafeAreaInsets(withTheme(TeamChannelsView))));

View File

@ -28,7 +28,7 @@ import * as List from '../../containers/List';
import Dropdown from './Dropdown';
import DropdownItemHeader from './Dropdown/DropdownItemHeader';
import { FILTER } from './filters';
import NoDataFound from './NoDataFound';
import BackgroundContainer from '../../containers/BackgroundContainer';
import { isIOS } from '../../utils/deviceInfo';
import { getBadgeColor, makeThreadName } from '../../utils/room';
import { getHeaderTitlePosition } from '../../containers/Header';
@ -463,7 +463,7 @@ class ThreadMessagesView extends React.Component {
return (
<>
{this.renderHeader()}
<NoDataFound text={text} />
<BackgroundContainer text={text} />
</>
);
}

View File

@ -32,7 +32,7 @@ describe('Broadcast room', () => {
await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000);
await waitFor(element(by.id(`room-view-title-broadcast${ data.random }`))).toBeVisible().withTimeout(60000);
await element(by.id('room-view-header-actions')).tap();
await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000);
await element(by.id('room-actions-info')).tap();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);

View File

@ -15,7 +15,7 @@ async function navigateToRoom() {
}
async function navigateToRoomActions() {
await element(by.id('room-view-header-actions')).tap();
await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000);
}
@ -39,7 +39,7 @@ describe('Join public room', () => {
// Render - Header
describe('Header', async() => {
it('should have actions button ', async() => {
await expect(element(by.id('room-view-header-actions'))).toBeVisible();
await expect(element(by.id('room-header'))).toBeVisible();
});
});

View File

@ -16,7 +16,7 @@ async function navigateToRoom() {
}
async function navigateToRoomActions() {
await element(by.id('room-view-header-actions')).tap();
await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000);
}

View File

@ -30,7 +30,7 @@ describe('Room screen', () => {
// Render - Header
describe('Header', async() => {
it('should have actions button ', async() => {
await expect(element(by.id('room-view-header-actions'))).toExist();
await expect(element(by.id('room-header'))).toExist();
});
it('should have threads button ', async() => {

View File

@ -16,7 +16,7 @@ async function navigateToRoomActions(type) {
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000);
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000);
await element(by.id('room-view-header-actions')).tap();
await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
}
@ -218,7 +218,7 @@ describe('Room actions screen', () => {
await starMessage('messageToStar')
//Back into Room Actions
await element(by.id('room-view-header-actions')).tap();
await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
//Go to starred messages
@ -246,7 +246,7 @@ describe('Room actions screen', () => {
await pinMessage('messageToPin')
//Back into Room Actions
await element(by.id('room-view-header-actions')).tap();
await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
await element(by.type('UIScrollView')).atIndex(1).scrollTo('bottom');
await waitFor(element(by.id('room-actions-pinned'))).toExist();
@ -270,7 +270,7 @@ describe('Room actions screen', () => {
await mockMessage('messageToFind');
//Back into Room Actions
await element(by.id('room-view-header-actions')).tap();
await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
await element(by.id('room-actions-search')).tap();
@ -499,7 +499,7 @@ describe('Room actions screen', () => {
});
it('should navigate to direct message', async() => {
await element(by.id('room-view-header-actions')).tap();
await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
await element(by.id('room-actions-members')).tap();
await waitFor(element(by.id('room-members-view'))).toExist().withTimeout(2000);

View File

@ -74,8 +74,8 @@ describe('Discussion', () => {
describe('Check RoomActionsView render', async() => {
it('should navigete to RoomActionsView', async() => {
await waitFor(element(by.id('room-view-header-actions'))).toBeVisible().withTimeout(5000);
await element(by.id('room-view-header-actions')).tap();
await waitFor(element(by.id('room-header'))).toBeVisible().withTimeout(5000);
await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000);
});

View File

@ -30,7 +30,7 @@ describe('Threads', () => {
// Render - Header
describe('Header', async() => {
it('should have actions button ', async() => {
await expect(element(by.id('room-view-header-actions'))).toExist();
await expect(element(by.id('room-header'))).toExist();
});
it('should have threads button ', async() => {
@ -105,8 +105,8 @@ describe('Threads', () => {
const messageText = 'threadonly';
await mockMessage(messageText);
await tapBack();
await waitFor(element(by.id('room-view-header-actions').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-view-header-actions').and(by.label(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000);
await waitFor(element(by.id('room-header').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-header').and(by.label(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000);
await sleep(500) //TODO: Find a better way to wait for the animation to finish and the messagebox-input to be available and usable :(
await waitFor(element(by.label(`${ data.random }${ messageText }`)).atIndex(0)).toNotExist().withTimeout(2000);
});
@ -118,8 +118,8 @@ describe('Threads', () => {
await element(by.id('messagebox-send-to-channel')).tap();
await element(by.id('messagebox-send-message')).tap();
await tapBack();
await waitFor(element(by.id('room-view-header-actions').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-view-header-actions').and(by.label(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000);
await waitFor(element(by.id('room-header').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-header').and(by.label(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000);
await sleep(500) //TODO: Find a better way to wait for the animation to finish and the messagebox-input to be available and usable :(
await waitFor(element(by.label(messageText)).atIndex(0)).toExist().withTimeout(2000);
});
@ -133,8 +133,8 @@ describe('Threads', () => {
await element(by.id('messagebox-send-to-channel')).tap();
await element(by.id('messagebox-send-message')).tap();
await tapBack();
await waitFor(element(by.id('room-view-header-actions').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-view-header-actions').and(by.label(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000);
await waitFor(element(by.id('room-header').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-header').and(by.label(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000);
await sleep(500) //TODO: Find a better way to wait for the animation to finish and the messagebox-input to be available and usable :(
await element(by.id(`message-thread-replied-on-${ thread }`)).tap();

View File

@ -17,7 +17,7 @@ async function navigateToRoomInfo(type) {
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000);
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000);
await element(by.id('room-view-header-actions')).tap();
await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
await element(by.id('room-actions-info')).tap();
await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000);

View File

@ -1,73 +0,0 @@
import React from 'react';
import { ScrollView, View, StyleSheet } from 'react-native';
import { HeaderBackButton } from '@react-navigation/stack';
import HeaderComponent from '../../app/views/RoomView/Header/Header';
// import { CustomHeaderButtons, Item } from '../../app/containers/HeaderButton';
import StoriesSeparator from './StoriesSeparator';
import { isIOS } from '../../app/utils/deviceInfo';
import { themes } from '../../app/constants/colors';
let _theme = 'light';
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
height: isIOS ? 44 : 56,
borderTopWidth: 1,
borderBottomWidth: 1,
marginVertical: 6
}
});
const Header = props => (
<View style={[styles.container, { backgroundColor: themes[_theme].headerBackground }]}>
<HeaderBackButton />
<HeaderComponent
title='test'
type='d'
width={375}
height={480}
theme={_theme}
{...props}
/>
{/* not working because we use withTheme */}
{/* <CustomHeaderButtons>
<Item title='thread' iconName='thread' />
</CustomHeaderButtons>
<CustomHeaderButtons>
<Item title='more' iconName='menu' />
</CustomHeaderButtons> */}
</View>
);
// eslint-disable-next-line react/prop-types
export default ({ theme }) => {
_theme = theme;
return (
<ScrollView style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
<StoriesSeparator title='Basic' theme={theme} />
<Header />
<StoriesSeparator title='Types' theme={theme} />
<Header type='d' />
<Header type='c' />
<Header type='p' />
<Header type='discussion' />
<Header type='thread' />
<StoriesSeparator title='Typing' theme={theme} />
<Header usersTyping={['diego.mello']} />
<Header usersTyping={['diego.mello', 'rocket.cat']} />
<Header usersTyping={['diego.mello', 'rocket.cat', 'detoxrn']} />
<StoriesSeparator title='Title scroll' theme={theme} />
<Header title='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' />
<Header
title='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
usersTyping={['diego.mello', 'rocket.cat', 'detoxrn']}
/>
</ScrollView>
);
};

View File

@ -14,6 +14,8 @@ import Markdown from './Markdown';
import './HeaderButtons';
import './UnreadBadge';
import '../../app/views/ThreadMessagesView/Item.stories.js';
import '../../app/containers/BackgroundContainer/index.stories.js';
import '../../app/containers/RoomHeader/RoomHeader.stories.js';
import Avatar from './Avatar';
// import RoomViewHeader from './RoomViewHeader';