[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 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

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', '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