[NEW] Rooms list layout (#413)

* RoomsListView layout

* Rooms list layout

* Sort component

* Header icons

* Default header colors

* Add server dropdown

* Close sort dropdown if server dropdown will open

* UserItem

* Room type icon

* Search working

* Tests updated

* Android layout

* Using realm queries instead of array iterates

* Animation duration

* Fixed render bug
This commit is contained in:
Diego Mello 2018-08-31 13:46:33 -03:00 committed by GitHub
parent 9f9a9b71a4
commit dc6d60b28e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
191 changed files with 3170 additions and 1439 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -201,7 +201,7 @@ dependencies {
implementation project(':reactnativenotifications')
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:27.1.0"
implementation "com.android.support:support-v4:27.1.0"
implementation "com.android.support:support-v4:27.1.+"
implementation 'com.android.support:customtabs:27.1.0'
implementation 'com.android.support:design:27.1.0'
implementation "com.facebook.react:react-native:+" // From node_modules

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

View File

@ -24,14 +24,22 @@ export const LOGIN = createRequestTypes('LOGIN', [
'OPEN',
'CLOSE',
'SET_SERVICES',
'REMOVE_SERVICES'
'REMOVE_SERVICES',
'SET_PREFERENCE'
]);
export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [
...defaultTypes,
'INIT'
]);
export const USER = createRequestTypes('USER', ['SET']);
export const ROOMS = createRequestTypes('ROOMS', [...defaultTypes, 'SET_SEARCH']);
export const ROOMS = createRequestTypes('ROOMS', [
...defaultTypes,
'SET_SEARCH',
'CLOSE_SERVER_DROPDOWN',
'TOGGLE_SERVER_DROPDOWN',
'CLOSE_SORT_DROPDOWN',
'TOGGLE_SORT_DROPDOWN'
]);
export const ROOM = createRequestTypes('ROOM', [
'ADD_USER_TYPING',
'REMOVE_USER_TYPING',

View File

@ -151,3 +151,10 @@ export function removeLoginServices() {
type: types.LOGIN.REMOVE_SERVICES
};
}
export function setPreference(preference) {
return {
type: types.LOGIN.SET_PREFERENCE,
preference
};
}

View File

@ -26,3 +26,27 @@ export function setSearch(searchText) {
searchText
};
}
export function closeServerDropdown() {
return {
type: types.ROOMS.CLOSE_SERVER_DROPDOWN
};
}
export function toggleServerDropdown() {
return {
type: types.ROOMS.TOGGLE_SERVER_DROPDOWN
};
}
export function closeSortDropdown() {
return {
type: types.ROOMS.CLOSE_SORT_DROPDOWN
};
}
export function toggleSortDropdown() {
return {
type: types.ROOMS.TOGGLE_SORT_DROPDOWN
};
}

View File

@ -71,6 +71,9 @@ export default {
Message_TimeFormat: {
type: 'valueAsString'
},
Site_Name: {
type: 'valueAsString'
},
Site_Url: {
type: 'valueAsString'
},

View File

@ -38,7 +38,7 @@ export default class Avatar extends React.PureComponent {
text: '',
size: 25,
type: 'd',
borderRadius: 2,
borderRadius: 4,
forceInitials: false
};
state = { showInitials: true };

View File

@ -1,36 +1,33 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { Image, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
const styles = StyleSheet.create({
type: {
marginRight: 5,
style: {
marginRight: 7,
marginTop: 3
}
});
const RoomTypeIcon = ({ type, size }) => {
const RoomTypeIcon = ({ type, size, style }) => {
if (!type) {
return null;
}
const icon = {
c: 'pound',
p: 'lock',
l: 'account',
d: 'at'
}[type];
return <Icon name={icon} size={size} style={styles.type} />;
if (type === 'c') {
return <Image source={{ uri: 'subscription_hashtag' }} style={[styles.style, style, { width: size, height: size }]} />;
}
return <Image source={{ uri: 'subscription_lock' }} style={[styles.style, style, { width: size, height: size }]} />;
};
RoomTypeIcon.propTypes = {
type: PropTypes.string,
size: PropTypes.number
size: PropTypes.number,
style: PropTypes.object
};
RoomTypeIcon.defaultProps = {
size: 15
size: 10
};
export default RoomTypeIcon;

View File

@ -68,7 +68,6 @@ export default class Markdown extends React.Component {
hardbreak: () => null,
blocklink: () => null,
image: node => (
// TODO: should use Image component
<Image key={node.key} style={styles.inlineImage} source={{ uri: node.attributes.src }} />
),
...rules

View File

@ -76,6 +76,8 @@ export default {
'error-user-registration-secret': 'User registration is only allowed via Secret URL',
'error-you-are-last-owner': 'You are the last owner. Please set new owner before leaving the room.',
Actions: 'Actions',
activity: 'activity',
Activity: 'Activity',
Add_Reaction: 'Add Reaction',
Add_Server: 'Add Server',
Add_user: 'Add user',
@ -85,6 +87,7 @@ export default {
All_users_in_the_channel_can_write_new_messages: 'All users in the channel can write new messages',
All: 'All',
Allow_Reactions: 'Allow Reactions',
Alphabetical: 'Alphabetical',
and_more: 'and more',
and: 'and',
announcement: 'announcement',
@ -108,6 +111,7 @@ export default {
Cancel: 'Cancel',
changing_avatar: 'changing avatar',
Channel_Name: 'Channel Name',
Channels: 'Channels',
Chats: 'Chats',
Close: 'Close',
Close_emoji_selector: 'Close emoji selector',
@ -133,6 +137,7 @@ export default {
description: 'description',
Description: 'Description',
Disable_notifications: 'Disable notifications',
Direct_Messages: 'Direct Messages',
Do_you_really_want_to_key_this_room_question_mark: 'Do you really want to {{key}} this room?',
edit: 'edit',
Edit: 'Edit',
@ -141,6 +146,7 @@ export default {
Enable_notifications: 'Enable notifications',
Everyone_can_access_this_channel: 'Everyone can access this channel',
Error_uploading: 'Error uploading',
Favorites: 'Favorites',
Files: 'Files',
Finish_recording: 'Finish recording',
For_your_security_you_must_enter_your_current_password_to_continue: 'For your security, you must enter your current password to continue',
@ -148,6 +154,8 @@ export default {
Forgot_password_If_this_email_is_registered: 'If this email is registered, we\'ll send instructions on how to reset your password. If you do not receive an email shortly, please come back and try again.',
Forgot_password: 'Forgot password',
Forgot_Password: 'Forgot Password',
Group_by_favorites: 'Group by favorites',
Group_by_type: 'Group by type',
Has_joined_the_channel: 'Has joined the channel',
Has_left_the_channel: 'Has left the channel',
I_have_an_account: 'I have an account',
@ -161,6 +169,7 @@ export default {
last_message: 'last message',
Leave_channel: 'Leave channel',
leave: 'leave',
Livechat: 'Livechat',
Loading_messages_ellipsis: 'Loading messages...',
Login: 'Login',
Logout: 'Logout',
@ -180,6 +189,7 @@ export default {
My_servers: 'My servers',
N_online_members: '{{n}} online members',
N_person_reacted: '{{n}} people reacted',
name: 'name',
Name: 'Name',
New_in_RocketChat_question_mark: 'New in Rocket.Chat?',
New_Message: 'New Message',
@ -216,6 +226,7 @@ export default {
Preferences_saved: 'Preferences saved!',
Privacy_Policy: ' Privacy Policy',
Private_Channel: 'Private Channel',
Private_Groups: 'Private Groups',
Private: 'Private',
Profile_saved_successfully: 'Profile saved successfully!',
Profile: 'Profile',
@ -257,6 +268,7 @@ export default {
Send: 'Send',
Send_audio_message: 'Send audio message',
Send_message: 'Send message',
Server: 'Server',
Servers: 'Servers',
Settings: 'Settings',
Settings_succesfully_changed: 'Settings succesfully changed!',
@ -268,6 +280,7 @@ export default {
snippeted: 'snippeted',
Snippets: 'Snippets',
Some_field_is_invalid_or_empty: 'Some field is invalid or empty',
Sorting_by: 'Sorting by {{key}}',
Star_room: 'Star room',
Star: 'Star',
Starred_Messages: 'Starred Messages',
@ -296,6 +309,8 @@ export default {
unmuted: 'unmuted',
Unpin: 'Unpin',
unread_messages: 'unread messages',
Unread: 'Unread',
Unread_on_top: 'Unread on top',
Unstar: 'Unstar',
Uploading: 'Uploading',
User_added_by: 'User {{userAdded}} added by {{userBy}}',

View File

@ -11,18 +11,36 @@ const getLastUpdate = () => {
return setting && setting._updatedAt;
};
function updateServer(param) {
database.databases.serversDB.write(() => {
database.databases.serversDB.create('servers', { id: this.ddp.url, ...param }, true);
});
}
export default async function() {
try {
const lastUpdate = getLastUpdate();
const result = await (!lastUpdate ? this.ddp.call('public-settings/get') : this.ddp.call('public-settings/get', new Date(lastUpdate)));
const data = result.update || result || [];
const filteredSettings = this._prepareSettings(this._filterSettings(result.update || result));
const filteredSettings = this._prepareSettings(this._filterSettings(data));
InteractionManager.runAfterInteractions(() =>
database.write(() =>
filteredSettings.forEach(setting =>
database.create('settings', { ...setting, _updatedAt: new Date() }, true))));
filteredSettings.forEach((setting) => {
database.create('settings', { ...setting, _updatedAt: new Date() }, true);
if (setting._id === 'Site_Name') {
updateServer.call(this, { name: setting.valueAsString });
}
})));
reduxStore.dispatch(actions.addSettings(this.parseSettings(filteredSettings)));
const iconSetting = data.find(item => item._id === 'Assets_favicon_512');
if (iconSetting) {
const iconURL = `${ this.ddp.url }/${ iconSetting.value.url || iconSetting.value.defaultUrl }`;
updateServer.call(this, { iconURL });
}
} catch (e) {
log('getSettings', e);
}

View File

@ -9,6 +9,8 @@ const serversSchema = {
primaryKey: 'id',
properties: {
id: 'string',
name: { type: 'string', optional: true },
iconURL: { type: 'string', optional: true },
current: 'bool'
}
};

View File

@ -140,7 +140,8 @@ const RocketChat = {
user = { ...user, ...userInfo.user };
}
RocketChat.registerPushToken(user.id);
return reduxStore.dispatch(loginSuccess(user));
reduxStore.dispatch(loginSuccess(user));
this.ddp.subscribe('userData');
} catch (e) {
log('rocketchat.loginSuccess', e);
}
@ -468,7 +469,7 @@ const RocketChat = {
log('rocketchat.logout', e);
}
}
database.deleteAll();
// database.deleteAll();
AsyncStorage.removeItem(TOKEN_KEY);
AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`);
},

View File

@ -1,91 +1,101 @@
import React from 'react';
import moment from 'moment';
import PropTypes from 'prop-types';
import { View, Text, StyleSheet, ViewPropTypes } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { View, Text, StyleSheet, Image, Platform } from 'react-native';
import { connect } from 'react-redux';
import { emojify } from 'react-emojione';
import Avatar from '../containers/Avatar';
import Status from '../containers/status';
import Touch from '../utils/touch/index'; //eslint-disable-line
import Markdown from '../containers/message/Markdown';
import RoomTypeIcon from '../containers/RoomTypeIcon';
import I18n from '../i18n';
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
paddingHorizontal: 16,
paddingVertical: 12,
alignItems: 'center',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#ddd'
alignItems: 'center'
},
number: {
minWidth: 25,
centerContainer: {
flex: 1,
height: '100%',
marginRight: 4
},
title: {
flex: 1,
fontSize: 18,
color: '#0C0D0F',
fontWeight: '400',
marginRight: 5,
paddingTop: 0,
paddingBottom: 0
},
alert: {
fontWeight: '600'
},
row: {
flex: 1,
flexDirection: 'row',
alignItems: 'flex-start'
},
titleContainer: {
width: '100%',
marginTop: Platform.OS === 'ios' ? 5 : 2,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center'
},
date: {
fontSize: 14,
color: '#9EA2A8',
fontWeight: 'normal',
paddingTop: 0,
paddingBottom: 0
},
updateAlert: {
color: '#1D74F5'
},
unreadNumberContainer: {
minWidth: 23,
padding: 3,
borderRadius: 4,
backgroundColor: '#1d74f5',
backgroundColor: '#1D74F5',
alignItems: 'center',
justifyContent: 'center'
},
unreadNumberText: {
color: '#fff',
overflow: 'hidden',
fontSize: 14,
paddingVertical: 4,
paddingHorizontal: 5,
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center'
},
roomNameView: {
flex: 1,
height: '100%',
marginLeft: 16,
marginRight: 4
},
roomName: {
flex: 1,
fontSize: 18,
color: '#444',
marginRight: 8
},
alert: {
fontWeight: 'bold'
},
favorite: {
// backgroundColor: '#eee'
},
row: {
// width: '100%',
flex: 1,
flexDirection: 'row',
alignItems: 'center'
// justifyContent: 'flex-end'
},
firstRow: {
width: '100%',
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center'
},
update: {
fontSize: 10,
color: '#888',
alignItems: 'center',
justifyContent: 'center'
},
updateAlert: {
color: '#1d74f5'
fontWeight: '500',
letterSpacing: 0.56
},
status: {
position: 'absolute',
bottom: -3,
right: -3,
borderWidth: 3,
borderColor: '#fff'
},
type: {
marginRight: 5,
borderRadius: 10,
width: 10,
height: 10,
marginRight: 7,
marginTop: 3
},
disclosureContainer: {
height: '100%',
marginLeft: 6,
marginRight: 9,
alignItems: 'center',
justifyContent: 'center'
},
disclosureIndicator: {
width: 20,
height: 20
},
emptyDisclosureAndroid: {
width: 15
},
markdownText: {
flex: 1,
color: '#9EA2A8',
fontSize: 15,
fontWeight: 'normal'
}
});
@ -103,9 +113,9 @@ const renderNumber = (unread, userMentions) => {
}
return (
<Text style={styles.number}>
{ unread }
</Text>
<View style={styles.unreadNumberContainer}>
<Text style={styles.unreadNumberText}>{ unread }</Text>
</View>
);
};
@ -131,13 +141,13 @@ export default class RoomItem extends React.Component {
onLongPress: PropTypes.func,
username: PropTypes.string,
avatarSize: PropTypes.number,
statusStyle: ViewPropTypes.style,
testID: PropTypes.string
testID: PropTypes.string,
height: PropTypes.number
}
static defaultProps = {
showLastMessage: true,
avatarSize: 46
avatarSize: 48
}
shouldComponentUpdate(nextProps) {
const oldlastMessage = this.props.lastMessage;
@ -151,11 +161,9 @@ export default class RoomItem extends React.Component {
}
return attrs.some(key => nextProps[key] !== this.props[key]);
}
get icon() {
const {
type, name, id, avatarSize, statusStyle
} = this.props;
return (<Avatar text={name} size={avatarSize} type={type}>{type === 'd' ? <Status style={[styles.status, statusStyle]} id={id} /> : null }</Avatar>);
get avatar() {
const { type, name, avatarSize } = this.props;
return <Avatar text={name} size={avatarSize} type={type} style={{ marginHorizontal: 15 }} />;
}
get lastMessage() {
@ -178,19 +186,17 @@ export default class RoomItem extends React.Component {
prefix = `${ lastMessage.u.username }: `;
}
const msg = `${ prefix }${ lastMessage.msg.replace(/[\n\t\r]/igm, '') }`;
const maxChars = 35;
return `${ msg.slice(0, maxChars) }${ msg.replace(/:[a-z0-9]+:/gi, ':::').length > maxChars ? '...' : '' }`;
let msg = `${ prefix }${ lastMessage.msg.replace(/[\n\t\r]/igm, '') }`;
msg = emojify(msg, { output: 'unicode' });
return msg;
}
get type() {
const icon = {
c: 'pound',
p: 'lock',
l: 'account',
d: 'at'
}[this.props.type];
return <Icon name={icon} size={15} style={styles.type} />;
const { type, id } = this.props;
if (type === 'd') {
return <Status style={[styles.status]} id={id} />;
}
return <RoomTypeIcon type={type} />;
}
formatDate = date => moment(date).calendar(null, {
@ -200,9 +206,20 @@ export default class RoomItem extends React.Component {
sameElse: 'MMM D'
})
renderDisclosureIndicator = () => {
if (Platform.OS === 'ios') {
return (
<View style={styles.disclosureContainer}>
<Image source={{ uri: 'disclosure_indicator' }} style={styles.disclosureIndicator} />
</View>
);
}
return <View style={styles.emptyDisclosureAndroid} />;
}
render() {
const {
favorite, unread, userMentions, name, _updatedAt, alert, type, testID
favorite, unread, userMentions, name, _updatedAt, alert, testID, height
} = this.props;
const date = this.formatDate(_updatedAt);
@ -232,43 +249,22 @@ export default class RoomItem extends React.Component {
accessibilityTraits='selected'
testID={testID}
>
<View style={[styles.container, favorite && styles.favorite]}>
{this.icon}
<View style={styles.roomNameView}>
<View style={styles.firstRow}>
<RoomTypeIcon type={type} />
<Text style={[styles.roomName, alert && styles.alert]} ellipsizeMode='tail' numberOfLines={1}>{ name }</Text>
{_updatedAt ? <Text style={[styles.update, alert && styles.updateAlert]} ellipsizeMode='tail' numberOfLines={1}>{ date }</Text> : null}
<View style={[styles.container, favorite && styles.favorite, height && { height }]}>
{this.avatar}
<View style={styles.centerContainer}>
<View style={styles.titleContainer}>
{this.type}
<Text style={[styles.title, alert && styles.alert]} ellipsizeMode='tail' numberOfLines={1}>{ name }</Text>
{_updatedAt ? <Text style={[styles.date, alert && styles.updateAlert]} ellipsizeMode='tail' numberOfLines={1}>{ date }</Text> : null}
</View>
<View style={styles.row}>
<Markdown
msg={this.lastMessage}
style={{
root: {
flex: 1
}
}}
rules={{
mention: node => (
<Text key={node.key}>
@{node.content}
</Text>
),
hashtag: node => (
<Text key={node.key}>
#{node.content}
</Text>
),
link: (node, children) => (
<Text key={node.key}>
{children}
</Text>
)
}}
/>
<Text style={styles.markdownText} numberOfLines={2}>
{this.lastMessage}
</Text>
{renderNumber(unread, userMentions)}
</View>
</View>
{this.renderDisclosureIndicator()}
</View>
</Touch>
);

View File

@ -0,0 +1,57 @@
import React from 'react';
import { Text, View, StyleSheet, Platform } from 'react-native';
import PropTypes from 'prop-types';
import Avatar from '../containers/Avatar';
import Touch from '../utils/touch';
const styles = StyleSheet.create({
button: {
height: 54
},
container: {
flexDirection: 'row'
},
avatar: {
marginHorizontal: 15,
marginVertical: 12
},
textContainer: {
flex: 1,
flexDirection: 'column'
},
name: {
fontSize: 18,
color: '#0C0D0F',
marginTop: Platform.OS === 'ios' ? 6 : 3,
marginBottom: 1
},
username: {
fontSize: 14,
color: '#9EA2A8'
}
});
const UserItem = ({
name, username, onPress, testID, onLongPress
}) => (
<Touch onPress={onPress} onLongPress={onLongPress} style={styles.button} testID={testID}>
<View style={styles.container}>
<Avatar text={username} size={30} type='d' style={styles.avatar} />
<View style={styles.textContainer}>
<Text style={styles.name}>{name}</Text>
<Text style={styles.username}>@{username}</Text>
</View>
</View>
</Touch>
);
UserItem.propTypes = {
name: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
testID: PropTypes.string.isRequired,
onLongPress: PropTypes.func
};
export default UserItem;

View File

@ -1,48 +0,0 @@
import PushNotification from 'react-native-push-notification';
import { AsyncStorage } from 'react-native';
import EJSON from 'ejson';
import { NavigationActions } from './Navigation';
const handleNotification = (notification) => {
if (notification.userInteraction) {
const {
rid, name, sender, type
} = EJSON.parse(notification.ejson || notification.data.ejson);
NavigationActions.push({
screen: 'RoomView',
passProps: { rid, name: type === 'd' ? sender.username : name }
});
}
};
PushNotification.configure({
// (optional) Called when Token is generated (iOS and Android)
async onRegister({ token }) {
AsyncStorage.setItem('pushId', token);
},
// (required) Called when a remote or local notification is opened or received
onNotification: handleNotification,
// ANDROID ONLY: GCM Sender ID (optional - not required for local notifications, but is need to receive remote push notifications)
senderID: '673693445664',
// IOS ONLY (optional): default: all - Permissions to register.
permissions: {
alert: true,
badge: true,
sound: true
},
// Should the initial notification be popped automatically
// default: true
popInitialNotification: true,
/**
* (optional) default: true
* - Specified if permissions (ios) and token (android and ios) will requested or not,
* - if not, you must call PushNotificationsHandler.requestPermissions() later
*/
requestPermissions: true
});

View File

@ -28,7 +28,10 @@ export default function login(state = initialState, action) {
...state,
isFetching: false,
isAuthenticated: true,
user: { ...state.user, ...action.user },
user: {
...state.user,
...action.user
},
token: action.user.token,
failure: false,
error: ''
@ -130,6 +133,20 @@ export default function login(state = initialState, action) {
...state,
services: {}
};
case types.LOGIN.SET_PREFERENCE:
return {
...state,
user: {
...state.user,
settings: {
...state.user.settings,
preferences: {
...state.user.settings.preferences,
...action.preference
}
}
}
};
default:
return state;
}

View File

@ -3,7 +3,10 @@ import * as types from '../actions/actionsTypes';
const initialState = {
isFetching: false,
failure: false,
searchText: ''
searchText: '',
showServerDropdown: false,
closeServerDropdown: false,
showSortDropdown: false
};
export default function login(state = initialState, action) {
@ -30,6 +33,26 @@ export default function login(state = initialState, action) {
...state,
searchText: action.searchText
};
case types.ROOMS.CLOSE_SERVER_DROPDOWN:
return {
...state,
closeServerDropdown: !state.closeServerDropdown
};
case types.ROOMS.TOGGLE_SERVER_DROPDOWN:
return {
...state,
showServerDropdown: !state.showServerDropdown
};
case types.ROOMS.CLOSE_SORT_DROPDOWN:
return {
...state,
closeSortDropdown: !state.closeSortDropdown
};
case types.ROOMS.TOGGLE_SORT_DROPDOWN:
return {
...state,
showSortDropdown: !state.showSortDropdown
};
default:
return state;
}

Some files were not shown because too many files have changed in this diff Show More