[NEW] Rooms list layout ()

* 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
__tests__/__snapshots__
android/app
app

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 project(':reactnativenotifications')
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:27.1.0" 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:customtabs:27.1.0'
implementation 'com.android.support:design:27.1.0' implementation 'com.android.support:design:27.1.0'
implementation "com.facebook.react:react-native:+" // From node_modules implementation "com.facebook.react:react-native:+" // From node_modules

Binary file not shown.

After

(image error) Size: 266 B

Binary file not shown.

After

(image error) Size: 602 B

Binary file not shown.

After

(image error) Size: 508 B

Binary file not shown.

After

(image error) Size: 132 B

Binary file not shown.

After

(image error) Size: 1.1 KiB

Binary file not shown.

After

(image error) Size: 575 B

Binary file not shown.

After

(image error) Size: 1.2 KiB

Binary file not shown.

Before

(image error) Size: 28 KiB

Binary file not shown.

After

(image error) Size: 2.7 KiB

Binary file not shown.

After

(image error) Size: 867 B

Binary file not shown.

After

(image error) Size: 722 B

Binary file not shown.

After

(image error) Size: 883 B

Binary file not shown.

After

(image error) Size: 942 B

Binary file not shown.

After

(image error) Size: 897 B

Binary file not shown.

After

(image error) Size: 174 B

Binary file not shown.

After

(image error) Size: 335 B

Binary file not shown.

Before

(image error) Size: 55 KiB

Binary file not shown.

After

(image error) Size: 172 B

Binary file not shown.

After

(image error) Size: 384 B

Binary file not shown.

After

(image error) Size: 370 B

Binary file not shown.

After

(image error) Size: 114 B

Binary file not shown.

After

(image error) Size: 689 B

Binary file not shown.

After

(image error) Size: 454 B

Binary file not shown.

After

(image error) Size: 769 B

Binary file not shown.

Before

(image error) Size: 16 KiB

Binary file not shown.

After

(image error) Size: 1.8 KiB

Binary file not shown.

After

(image error) Size: 591 B

Binary file not shown.

After

(image error) Size: 396 B

Binary file not shown.

After

(image error) Size: 568 B

Binary file not shown.

After

(image error) Size: 597 B

Binary file not shown.

After

(image error) Size: 563 B

Binary file not shown.

After

(image error) Size: 118 B

Binary file not shown.

After

(image error) Size: 198 B

Binary file not shown.

After

(image error) Size: 299 B

Binary file not shown.

After

(image error) Size: 830 B

Binary file not shown.

After

(image error) Size: 550 B

Binary file not shown.

After

(image error) Size: 138 B

Binary file not shown.

After

(image error) Size: 1.5 KiB

Binary file not shown.

After

(image error) Size: 761 B

Binary file not shown.

After

(image error) Size: 1.7 KiB

Binary file not shown.

Before

(image error) Size: 48 KiB

Binary file not shown.

After

(image error) Size: 3.6 KiB

Binary file not shown.

After

(image error) Size: 1.0 KiB

Binary file not shown.

After

(image error) Size: 715 B

Binary file not shown.

After

(image error) Size: 1.1 KiB

Binary file not shown.

After

(image error) Size: 1.3 KiB

Binary file not shown.

After

(image error) Size: 1.1 KiB

Binary file not shown.

After

(image error) Size: 147 B

Binary file not shown.

After

(image error) Size: 351 B

Binary file not shown.

After

(image error) Size: 431 B

Binary file not shown.

After

(image error) Size: 1.2 KiB

Binary file not shown.

After

(image error) Size: 783 B

Binary file not shown.

After

(image error) Size: 157 B

Binary file not shown.

After

(image error) Size: 2.4 KiB

Binary file not shown.

After

(image error) Size: 916 B

Binary file not shown.

After

(image error) Size: 2.6 KiB

Binary file not shown.

Before

(image error) Size: 66 KiB

Binary file not shown.

After

(image error) Size: 5.4 KiB

Binary file not shown.

After

(image error) Size: 1.5 KiB

Binary file not shown.

After

(image error) Size: 1.1 KiB

Binary file not shown.

After

(image error) Size: 1.7 KiB

Binary file not shown.

After

(image error) Size: 2.0 KiB

Binary file not shown.

After

(image error) Size: 1.6 KiB

Binary file not shown.

After

(image error) Size: 187 B

Binary file not shown.

After

(image error) Size: 484 B

Binary file not shown.

After

(image error) Size: 542 B

Binary file not shown.

After

(image error) Size: 1.6 KiB

Binary file not shown.

After

(image error) Size: 920 B

Binary file not shown.

After

(image error) Size: 191 B

Binary file not shown.

After

(image error) Size: 3.2 KiB

Binary file not shown.

After

(image error) Size: 1.3 KiB

Binary file not shown.

After

(image error) Size: 3.4 KiB

Binary file not shown.

Before

(image error) Size: 84 KiB

Binary file not shown.

After

(image error) Size: 7.5 KiB

Binary file not shown.

After

(image error) Size: 1.9 KiB

Binary file not shown.

After

(image error) Size: 1.5 KiB

Binary file not shown.

After

(image error) Size: 2.3 KiB

Binary file not shown.

After

(image error) Size: 2.6 KiB

Binary file not shown.

After

(image error) Size: 2.0 KiB

Binary file not shown.

After

(image error) Size: 227 B

Binary file not shown.

After

(image error) Size: 658 B

View File

@ -24,14 +24,22 @@ export const LOGIN = createRequestTypes('LOGIN', [
'OPEN', 'OPEN',
'CLOSE', 'CLOSE',
'SET_SERVICES', 'SET_SERVICES',
'REMOVE_SERVICES' 'REMOVE_SERVICES',
'SET_PREFERENCE'
]); ]);
export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [ export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [
...defaultTypes, ...defaultTypes,
'INIT' 'INIT'
]); ]);
export const USER = createRequestTypes('USER', ['SET']); 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', [ export const ROOM = createRequestTypes('ROOM', [
'ADD_USER_TYPING', 'ADD_USER_TYPING',
'REMOVE_USER_TYPING', 'REMOVE_USER_TYPING',

View File

@ -151,3 +151,10 @@ export function removeLoginServices() {
type: types.LOGIN.REMOVE_SERVICES 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 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: { Message_TimeFormat: {
type: 'valueAsString' type: 'valueAsString'
}, },
Site_Name: {
type: 'valueAsString'
},
Site_Url: { Site_Url: {
type: 'valueAsString' type: 'valueAsString'
}, },

View File

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

View File

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

View File

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

View File

@ -76,6 +76,8 @@ export default {
'error-user-registration-secret': 'User registration is only allowed via Secret URL', '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.', 'error-you-are-last-owner': 'You are the last owner. Please set new owner before leaving the room.',
Actions: 'Actions', Actions: 'Actions',
activity: 'activity',
Activity: 'Activity',
Add_Reaction: 'Add Reaction', Add_Reaction: 'Add Reaction',
Add_Server: 'Add Server', Add_Server: 'Add Server',
Add_user: 'Add user', 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_users_in_the_channel_can_write_new_messages: 'All users in the channel can write new messages',
All: 'All', All: 'All',
Allow_Reactions: 'Allow Reactions', Allow_Reactions: 'Allow Reactions',
Alphabetical: 'Alphabetical',
and_more: 'and more', and_more: 'and more',
and: 'and', and: 'and',
announcement: 'announcement', announcement: 'announcement',
@ -108,6 +111,7 @@ export default {
Cancel: 'Cancel', Cancel: 'Cancel',
changing_avatar: 'changing avatar', changing_avatar: 'changing avatar',
Channel_Name: 'Channel Name', Channel_Name: 'Channel Name',
Channels: 'Channels',
Chats: 'Chats', Chats: 'Chats',
Close: 'Close', Close: 'Close',
Close_emoji_selector: 'Close emoji selector', Close_emoji_selector: 'Close emoji selector',
@ -133,6 +137,7 @@ export default {
description: 'description', description: 'description',
Description: 'Description', Description: 'Description',
Disable_notifications: 'Disable notifications', 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?', Do_you_really_want_to_key_this_room_question_mark: 'Do you really want to {{key}} this room?',
edit: 'edit', edit: 'edit',
Edit: 'Edit', Edit: 'Edit',
@ -141,6 +146,7 @@ export default {
Enable_notifications: 'Enable notifications', Enable_notifications: 'Enable notifications',
Everyone_can_access_this_channel: 'Everyone can access this channel', Everyone_can_access_this_channel: 'Everyone can access this channel',
Error_uploading: 'Error uploading', Error_uploading: 'Error uploading',
Favorites: 'Favorites',
Files: 'Files', Files: 'Files',
Finish_recording: 'Finish recording', 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', 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_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',
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_joined_the_channel: 'Has joined the channel',
Has_left_the_channel: 'Has left the channel', Has_left_the_channel: 'Has left the channel',
I_have_an_account: 'I have an account', I_have_an_account: 'I have an account',
@ -161,6 +169,7 @@ export default {
last_message: 'last message', last_message: 'last message',
Leave_channel: 'Leave channel', Leave_channel: 'Leave channel',
leave: 'leave', leave: 'leave',
Livechat: 'Livechat',
Loading_messages_ellipsis: 'Loading messages...', Loading_messages_ellipsis: 'Loading messages...',
Login: 'Login', Login: 'Login',
Logout: 'Logout', Logout: 'Logout',
@ -180,6 +189,7 @@ export default {
My_servers: 'My servers', My_servers: 'My servers',
N_online_members: '{{n}} online members', N_online_members: '{{n}} online members',
N_person_reacted: '{{n}} people reacted', N_person_reacted: '{{n}} people reacted',
name: 'name',
Name: 'Name', Name: 'Name',
New_in_RocketChat_question_mark: 'New in Rocket.Chat?', New_in_RocketChat_question_mark: 'New in Rocket.Chat?',
New_Message: 'New Message', New_Message: 'New Message',
@ -216,6 +226,7 @@ export default {
Preferences_saved: 'Preferences saved!', Preferences_saved: 'Preferences saved!',
Privacy_Policy: ' Privacy Policy', Privacy_Policy: ' Privacy Policy',
Private_Channel: 'Private Channel', Private_Channel: 'Private Channel',
Private_Groups: 'Private Groups',
Private: 'Private', Private: 'Private',
Profile_saved_successfully: 'Profile saved successfully!', Profile_saved_successfully: 'Profile saved successfully!',
Profile: 'Profile', Profile: 'Profile',
@ -257,6 +268,7 @@ export default {
Send: 'Send', Send: 'Send',
Send_audio_message: 'Send audio message', Send_audio_message: 'Send audio message',
Send_message: 'Send message', Send_message: 'Send message',
Server: 'Server',
Servers: 'Servers', Servers: 'Servers',
Settings: 'Settings', Settings: 'Settings',
Settings_succesfully_changed: 'Settings succesfully changed!', Settings_succesfully_changed: 'Settings succesfully changed!',
@ -268,6 +280,7 @@ export default {
snippeted: 'snippeted', snippeted: 'snippeted',
Snippets: 'Snippets', Snippets: 'Snippets',
Some_field_is_invalid_or_empty: 'Some field is invalid or empty', Some_field_is_invalid_or_empty: 'Some field is invalid or empty',
Sorting_by: 'Sorting by {{key}}',
Star_room: 'Star room', Star_room: 'Star room',
Star: 'Star', Star: 'Star',
Starred_Messages: 'Starred Messages', Starred_Messages: 'Starred Messages',
@ -296,6 +309,8 @@ export default {
unmuted: 'unmuted', unmuted: 'unmuted',
Unpin: 'Unpin', Unpin: 'Unpin',
unread_messages: 'unread messages', unread_messages: 'unread messages',
Unread: 'Unread',
Unread_on_top: 'Unread on top',
Unstar: 'Unstar', Unstar: 'Unstar',
Uploading: 'Uploading', Uploading: 'Uploading',
User_added_by: 'User {{userAdded}} added by {{userBy}}', User_added_by: 'User {{userAdded}} added by {{userBy}}',

View File

@ -11,18 +11,36 @@ const getLastUpdate = () => {
return setting && setting._updatedAt; 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() { 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 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(() => InteractionManager.runAfterInteractions(() =>
database.write(() => database.write(() =>
filteredSettings.forEach(setting => filteredSettings.forEach((setting) => {
database.create('settings', { ...setting, _updatedAt: new Date() }, true)))); 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))); 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) { } catch (e) {
log('getSettings', e); log('getSettings', e);
} }

View File

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

View File

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

View File

@ -1,91 +1,101 @@
import React from 'react'; import React from 'react';
import moment from 'moment'; import moment from 'moment';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { View, Text, StyleSheet, ViewPropTypes } from 'react-native'; import { View, Text, StyleSheet, Image, Platform } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { emojify } from 'react-emojione';
import Avatar from '../containers/Avatar'; import Avatar from '../containers/Avatar';
import Status from '../containers/status'; import Status from '../containers/status';
import Touch from '../utils/touch/index'; //eslint-disable-line import Touch from '../utils/touch/index'; //eslint-disable-line
import Markdown from '../containers/message/Markdown';
import RoomTypeIcon from '../containers/RoomTypeIcon'; import RoomTypeIcon from '../containers/RoomTypeIcon';
import I18n from '../i18n'; import I18n from '../i18n';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flexDirection: 'row', flexDirection: 'row',
paddingHorizontal: 16, alignItems: 'center'
paddingVertical: 12,
alignItems: 'center',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#ddd'
}, },
number: { centerContainer: {
minWidth: 25, 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, borderRadius: 4,
backgroundColor: '#1d74f5', backgroundColor: '#1D74F5',
alignItems: 'center',
justifyContent: 'center'
},
unreadNumberText: {
color: '#fff', color: '#fff',
overflow: 'hidden', overflow: 'hidden',
fontSize: 14, fontSize: 14,
paddingVertical: 4, fontWeight: '500',
paddingHorizontal: 5, letterSpacing: 0.56
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'
}, },
status: { status: {
position: 'absolute', borderRadius: 10,
bottom: -3, width: 10,
right: -3, height: 10,
borderWidth: 3, marginRight: 7,
borderColor: '#fff'
},
type: {
marginRight: 5,
marginTop: 3 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 ( return (
<Text style={styles.number}> <View style={styles.unreadNumberContainer}>
{ unread } <Text style={styles.unreadNumberText}>{ unread }</Text>
</Text> </View>
); );
}; };
@ -131,13 +141,13 @@ export default class RoomItem extends React.Component {
onLongPress: PropTypes.func, onLongPress: PropTypes.func,
username: PropTypes.string, username: PropTypes.string,
avatarSize: PropTypes.number, avatarSize: PropTypes.number,
statusStyle: ViewPropTypes.style, testID: PropTypes.string,
testID: PropTypes.string height: PropTypes.number
} }
static defaultProps = { static defaultProps = {
showLastMessage: true, showLastMessage: true,
avatarSize: 46 avatarSize: 48
} }
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
const oldlastMessage = this.props.lastMessage; 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]); return attrs.some(key => nextProps[key] !== this.props[key]);
} }
get icon() { get avatar() {
const { const { type, name, avatarSize } = this.props;
type, name, id, avatarSize, statusStyle return <Avatar text={name} size={avatarSize} type={type} style={{ marginHorizontal: 15 }} />;
} = this.props;
return (<Avatar text={name} size={avatarSize} type={type}>{type === 'd' ? <Status style={[styles.status, statusStyle]} id={id} /> : null }</Avatar>);
} }
get lastMessage() { get lastMessage() {
@ -178,19 +186,17 @@ export default class RoomItem extends React.Component {
prefix = `${ lastMessage.u.username }: `; prefix = `${ lastMessage.u.username }: `;
} }
const msg = `${ prefix }${ lastMessage.msg.replace(/[\n\t\r]/igm, '') }`; let msg = `${ prefix }${ lastMessage.msg.replace(/[\n\t\r]/igm, '') }`;
const maxChars = 35; msg = emojify(msg, { output: 'unicode' });
return `${ msg.slice(0, maxChars) }${ msg.replace(/:[a-z0-9]+:/gi, ':::').length > maxChars ? '...' : '' }`; return msg;
} }
get type() { get type() {
const icon = { const { type, id } = this.props;
c: 'pound', if (type === 'd') {
p: 'lock', return <Status style={[styles.status]} id={id} />;
l: 'account', }
d: 'at' return <RoomTypeIcon type={type} />;
}[this.props.type];
return <Icon name={icon} size={15} style={styles.type} />;
} }
formatDate = date => moment(date).calendar(null, { formatDate = date => moment(date).calendar(null, {
@ -200,9 +206,20 @@ export default class RoomItem extends React.Component {
sameElse: 'MMM D' 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() { render() {
const { const {
favorite, unread, userMentions, name, _updatedAt, alert, type, testID favorite, unread, userMentions, name, _updatedAt, alert, testID, height
} = this.props; } = this.props;
const date = this.formatDate(_updatedAt); const date = this.formatDate(_updatedAt);
@ -232,43 +249,22 @@ export default class RoomItem extends React.Component {
accessibilityTraits='selected' accessibilityTraits='selected'
testID={testID} testID={testID}
> >
<View style={[styles.container, favorite && styles.favorite]}> <View style={[styles.container, favorite && styles.favorite, height && { height }]}>
{this.icon} {this.avatar}
<View style={styles.roomNameView}> <View style={styles.centerContainer}>
<View style={styles.firstRow}> <View style={styles.titleContainer}>
<RoomTypeIcon type={type} /> {this.type}
<Text style={[styles.roomName, alert && styles.alert]} ellipsizeMode='tail' numberOfLines={1}>{ name }</Text> <Text style={[styles.title, alert && styles.alert]} ellipsizeMode='tail' numberOfLines={1}>{ name }</Text>
{_updatedAt ? <Text style={[styles.update, alert && styles.updateAlert]} ellipsizeMode='tail' numberOfLines={1}>{ date }</Text> : null} {_updatedAt ? <Text style={[styles.date, alert && styles.updateAlert]} ellipsizeMode='tail' numberOfLines={1}>{ date }</Text> : null}
</View> </View>
<View style={styles.row}> <View style={styles.row}>
<Markdown <Text style={styles.markdownText} numberOfLines={2}>
msg={this.lastMessage} {this.lastMessage}
style={{ </Text>
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>
)
}}
/>
{renderNumber(unread, userMentions)} {renderNumber(unread, userMentions)}
</View> </View>
</View> </View>
{this.renderDisclosureIndicator()}
</View> </View>
</Touch> </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, ...state,
isFetching: false, isFetching: false,
isAuthenticated: true, isAuthenticated: true,
user: { ...state.user, ...action.user }, user: {
...state.user,
...action.user
},
token: action.user.token, token: action.user.token,
failure: false, failure: false,
error: '' error: ''
@ -130,6 +133,20 @@ export default function login(state = initialState, action) {
...state, ...state,
services: {} services: {}
}; };
case types.LOGIN.SET_PREFERENCE:
return {
...state,
user: {
...state.user,
settings: {
...state.user.settings,
preferences: {
...state.user.settings.preferences,
...action.preference
}
}
}
};
default: default:
return state; return state;
} }

View File

@ -3,7 +3,10 @@ import * as types from '../actions/actionsTypes';
const initialState = { const initialState = {
isFetching: false, isFetching: false,
failure: false, failure: false,
searchText: '' searchText: '',
showServerDropdown: false,
closeServerDropdown: false,
showSortDropdown: false
}; };
export default function login(state = initialState, action) { export default function login(state = initialState, action) {
@ -30,6 +33,26 @@ export default function login(state = initialState, action) {
...state, ...state,
searchText: action.searchText 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: default:
return state; return state;
} }

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