diff --git a/android/app/src/main/res/drawable-hdpi/ic_info_outline_black_24dp.png b/android/app/src/main/res/drawable-hdpi/ic_info_outline_black_24dp.png new file mode 100644 index 0000000..4b5ab06 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_info_outline_black_24dp.png differ diff --git a/android/app/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png b/android/app/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000..c7b1113 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png differ diff --git a/android/app/src/main/res/drawable-hdpi/ic_textsms_black_24dp.png b/android/app/src/main/res/drawable-hdpi/ic_textsms_black_24dp.png new file mode 100644 index 0000000..cf38e73 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_textsms_black_24dp.png differ diff --git a/android/app/src/main/res/drawable-hdpi/ic_textsms_white_24dp.png b/android/app/src/main/res/drawable-hdpi/ic_textsms_white_24dp.png new file mode 100644 index 0000000..12a1ef8 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_textsms_white_24dp.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_info_outline_black_24dp.png b/android/app/src/main/res/drawable-mdpi/ic_info_outline_black_24dp.png new file mode 100644 index 0000000..e0c9fe0 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_info_outline_black_24dp.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_info_outline_white_24dp.png b/android/app/src/main/res/drawable-mdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000..353e064 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_info_outline_white_24dp.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_textsms_black_24dp.png b/android/app/src/main/res/drawable-mdpi/ic_textsms_black_24dp.png new file mode 100644 index 0000000..b4b9862 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_textsms_black_24dp.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_textsms_white_24dp.png b/android/app/src/main/res/drawable-mdpi/ic_textsms_white_24dp.png new file mode 100644 index 0000000..0ef0ad9 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_textsms_white_24dp.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_info_outline_black_24dp.png b/android/app/src/main/res/drawable-xhdpi/ic_info_outline_black_24dp.png new file mode 100644 index 0000000..b706f0d Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_info_outline_black_24dp.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png b/android/app/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000..c571b2e Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_textsms_black_24dp.png b/android/app/src/main/res/drawable-xhdpi/ic_textsms_black_24dp.png new file mode 100644 index 0000000..873d57e Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_textsms_black_24dp.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_textsms_white_24dp.png b/android/app/src/main/res/drawable-xhdpi/ic_textsms_white_24dp.png new file mode 100644 index 0000000..9818088 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_textsms_white_24dp.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_info_outline_black_24dp.png b/android/app/src/main/res/drawable-xxhdpi/ic_info_outline_black_24dp.png new file mode 100644 index 0000000..3847a9f Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_info_outline_black_24dp.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_24dp.png b/android/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000..c41a5fc Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_24dp.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_textsms_black_24dp.png b/android/app/src/main/res/drawable-xxhdpi/ic_textsms_black_24dp.png new file mode 100644 index 0000000..cbdb1dd Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_textsms_black_24dp.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_textsms_white_24dp.png b/android/app/src/main/res/drawable-xxhdpi/ic_textsms_white_24dp.png new file mode 100644 index 0000000..a3d45a5 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_textsms_white_24dp.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_info_outline_black_24dp.png b/android/app/src/main/res/drawable-xxxhdpi/ic_info_outline_black_24dp.png new file mode 100644 index 0000000..c1e2a03 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_info_outline_black_24dp.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png b/android/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png new file mode 100644 index 0000000..3a82cab Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_textsms_black_24dp.png b/android/app/src/main/res/drawable-xxxhdpi/ic_textsms_black_24dp.png new file mode 100644 index 0000000..b64d02a Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_textsms_black_24dp.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_textsms_white_24dp.png b/android/app/src/main/res/drawable-xxxhdpi/ic_textsms_white_24dp.png new file mode 100644 index 0000000..4f23ef5 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_textsms_white_24dp.png differ diff --git a/app/api/gitter.js b/app/api/gitter.js index 55d4dd0..1a2f6f2 100644 --- a/app/api/gitter.js +++ b/app/api/gitter.js @@ -33,7 +33,7 @@ export function roomMessagesBefore(token, id, limit, beforeId) { } export function sendMessage(token, roomId, text) { - return callApi(`/rooms/${roomId}/chatMessages`, token, { + return callApi(`rooms/${roomId}/chatMessages`, token, { method: 'POST', body: JSON.stringify({ text @@ -42,7 +42,7 @@ export function sendMessage(token, roomId, text) { } export function joinRoom(token, uri) { - return callApi(`/rooms`, token, { + return callApi(`rooms`, token, { method: 'POST', body: JSON.stringify({ uri @@ -51,7 +51,7 @@ export function joinRoom(token, uri) { } export function changeFavoriteStatus(token, userId, roomId, status) { - return callApi(`/user/${userId}/rooms/${roomId}`, token, { + return callApi(`user/${userId}/rooms/${roomId}`, token, { method: 'PUT', body: JSON.stringify({ favourite: status @@ -60,19 +60,19 @@ export function changeFavoriteStatus(token, userId, roomId, status) { } export function leaveRoom(token, roomId, userId) { - return callApi(`/rooms/${roomId}/users/${userId}`, token, { + return callApi(`rooms/${roomId}/users/${userId}`, token, { method: 'DELETE' }) } export function markAllAsRead(token, roomId, userId) { - return callApi(`/user/${userId}/rooms/${roomId}/unreadItems/all`, token, { + return callApi(`user/${userId}/rooms/${roomId}/unreadItems/all`, token, { method: 'DELETE' }) } export function updateMessage(token, roomId, messageId, text) { - return callApi(`/rooms/${roomId}/chatMessages/${messageId}`, token, { + return callApi(`rooms/${roomId}/chatMessages/${messageId}`, token, { method: 'PUT', body: JSON.stringify({ text @@ -96,6 +96,29 @@ export function getUser(token, username) { return callApi(`users/${username}`, token) } +export function getRepoInfo(token, repoName) { + const url = `repo-info?repo=${repoName}` + return fetch(`${apiUrl}/${url}`, { + method: 'get', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }) +} + +export function getRoomUsers(token, roomId) { + return callApi(`rooms/${roomId}/users`, token) +} + +export function getRoomUsersWithSkip(token, roomId, skip) { + return callApi(`rooms/${roomId}/users?skip=${skip}`, token) +} + +export function searchRoomUsers(token, roomId, query) { + return callApi(`rooms/${roomId}/users?q=${query}`, token) +} + /** * Private functions @@ -110,7 +133,7 @@ function callApi(endpoint, token, options = {method: 'get'}) { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } - }).then(res => res.json()) + }).then(res => {console.log(res); return res.text()}).then(res => JSON.parse(res)) } // GET https://gitter.im/api/v1/user/555e610f15522ed4b3e0c169/suggestedRooms // GET https://gitter.im/api/v1/repo-info?repo=dev-ua%2Freactjs diff --git a/app/components/CustomSearch.js b/app/components/CustomSearch.js new file mode 100644 index 0000000..d8f594c --- /dev/null +++ b/app/components/CustomSearch.js @@ -0,0 +1,70 @@ +import React, { + Component, + PropTypes, + TextInput, + Image, + View +} from 'react-native' +import s from '../styles/components/CustomSearchStyles' +import Button from './Button' + +class CustomSearch extends Component { + constructor(props) { + super(props) + + this.focus = this.focus.bind(this) + this.blur = this.blur.bind(this) + } + + focus() { + this.refs.textInput.focus() + } + + blur() { + this.refs.textInput.blur() + } + + render() { + const {value, onChange, onBackPress, onClearPress} = this.props + return ( + + + + + + {!!value && value.length !== 0 && ( + + )} + + ) + } +} + +CustomSearch.propTypes = { + value: PropTypes.string, + onChange: PropTypes.func, + onBackPress: PropTypes.func, + onClearPress: PropTypes.func +} + +export default CustomSearch diff --git a/app/components/Divider.js b/app/components/Divider.js new file mode 100644 index 0000000..ee76a29 --- /dev/null +++ b/app/components/Divider.js @@ -0,0 +1,28 @@ +import React, { + PropTypes, + View +} from 'react-native'; + +const Divider = ({inset, style, light}) => { + return ( + + ) +} + +Divider.defaultProps = { + light: true +} + +Divider.propTypes = { + inset: PropTypes.bool, + style: PropTypes.object, + light: PropTypes.bool +} + +export default Divider diff --git a/app/components/Drawer/ChannelListItem.js b/app/components/Drawer/ChannelListItem.js index 4816e43..6034595 100644 --- a/app/components/Drawer/ChannelListItem.js +++ b/app/components/Drawer/ChannelListItem.js @@ -1,6 +1,6 @@ import React, { PropTypes, - TouchableHighlight, + TouchableNativeFeedback, View, Text } from 'react-native' @@ -20,16 +20,16 @@ const ChannelListItem = ({ : createGhAvatarLink(name.split('/')[0], 200) const itemStyles = activeRoom === id - ? {backgroundColor: colors.raspberry, color: colors.white, elevation: 2} - : {backgroundColor: colors.white, elevation: 0} + ? {backgroundColor: colors.androidGray, color: colors.raspberry} + : {backgroundColor: colors.white} return ( - } - + ) } diff --git a/app/components/Drawer/ChannelListSection.js b/app/components/Drawer/ChannelListSection.js index 2bcd948..4385f7e 100644 --- a/app/components/Drawer/ChannelListSection.js +++ b/app/components/Drawer/ChannelListSection.js @@ -1,13 +1,12 @@ import React, {PropTypes, View, Text} from 'react-native' import s from '../../styles/screens/Drawer/ChannelListSectionStyles' import ChannelListItem from './ChannelListItem' +import Heading from '../Heading' const ChannelListSection = ({name, items, onRoomPress, activeRoom, onLongRoomPress}) => { return ( - - {name} - + {items && items.map(item => ( { +const FailedToLoad = ({onRetry, message}) => { return ( {message} onPress()}> + onPress={() => onRetry()}> Retry diff --git a/app/components/Heading.js b/app/components/Heading.js new file mode 100644 index 0000000..1301277 --- /dev/null +++ b/app/components/Heading.js @@ -0,0 +1,36 @@ +import React, { + PropTypes, + View, + Text +} from 'react-native' + +const Heading = ({text, color, styles}) => { + return ( + + + {text} + + + ) +} + +Heading.defaultProps = { + color: 'black' +} + +Heading.propTypes = { + text: PropTypes.string.isRequired, + color: PropTypes.string, + styles: PropTypes.object +} + +export default Heading diff --git a/app/components/Room/Message.js b/app/components/Room/Message.js index f1d0cde..e9b99a9 100644 --- a/app/components/Room/Message.js +++ b/app/components/Room/Message.js @@ -90,11 +90,11 @@ class Message extends Component { render() { const {fromUser, sending, failed, readBy, isCollapsed, - text, status, onUsernamePress, onUserAvatarPress} = this.props + text, status, onUsernamePress, onUserAvatarPress, sent} = this.props const opacity = sending === true ? 0.4 : 1 const backgroundColor = failed === true ? 'rgba(255, 0, 0, 0.2)' : 'transparent' - const readStatusOpacity = readBy === 0 ? 0 : 0.6 + const readStatusOpacity = readBy === 0 || ['sending...', 'failed'].indexOf(sent) !== -1 ? 0 : 0.6 if (!!status) { return ( diff --git a/app/components/RoomInfo/RepoInfo.js b/app/components/RoomInfo/RepoInfo.js new file mode 100644 index 0000000..d43a107 --- /dev/null +++ b/app/components/RoomInfo/RepoInfo.js @@ -0,0 +1,87 @@ +import React, { + PropTypes, + View, + Text +} from 'react-native' +import s from '../../styles/screens/RoomInfo/RepoInfoStyles' + +import Avatar from '../Avatar' +import Divider from '../Divider' +import Heading from '../Heading' +import Button from '../Button' +import ParsedText from '../ParsedText' + +const RepoInfo = ({name, owner, description, open_issues_count, + stargazers_count, watchers_count, html_url, handleUrlPress, onStatItemPress}) => { + return ( + + + + + {name} + by {owner.login} + + + + {!!description && ( + + + + + + + + )} + + + + + + + + + + + ) +} + +RepoInfo.propTypes = { + name: PropTypes.string, + owner: PropTypes.object, + description: PropTypes.string, + open_issues_count: PropTypes.number, + stargazers_count: PropTypes.number, + watchers_count: PropTypes.number, + html_url: PropTypes.string, + handleUrlPress: PropTypes.func, + onStatItemPress: PropTypes.func +} + +export default RepoInfo diff --git a/app/components/RoomInfo/RoomInfo.js b/app/components/RoomInfo/RoomInfo.js new file mode 100644 index 0000000..24697b7 --- /dev/null +++ b/app/components/RoomInfo/RoomInfo.js @@ -0,0 +1,45 @@ +import React, { + PropTypes, + View, + Text +} from 'react-native' +import s from '../../styles/screens/RoomInfo/RoomInfoStyles' + +import {createGhAvatarLink} from '../../utils/links' +import channelNameAndOwner from '../../utils/channelNameAndOwner' + +import Avatar from '../Avatar' +import Divider from '../Divider' + +const RoomInfo = ({name}) => { + const avatarSrc = createGhAvatarLink(name.split('/')[0], 200) + const channel = channelNameAndOwner(name) + + return ( + + + + {!channel.name + ? ( + + {channel.owner} + + ) : ( + + {channel.name} + by {channel.owner} + + )} + + + + ) +} + +RoomInfo.propTypes = { + name: PropTypes.string +} + +export default RoomInfo diff --git a/app/components/RoomInfo/RoomUsers.js b/app/components/RoomInfo/RoomUsers.js new file mode 100644 index 0000000..76154ec --- /dev/null +++ b/app/components/RoomInfo/RoomUsers.js @@ -0,0 +1,83 @@ +import React, { + PropTypes, + View, + Text, + TouchableOpacity +} from 'react-native' +import s from '../../styles/screens/RoomInfo/RoomUserStyles' + +import Avatar from '../Avatar' +import Heading from '../Heading' +import Button from '../Button' + +const RoomUsers = ({ids, entities, onPress, userCount, onAllUsersPress, oneToOne}) => { + const displayUserHeader = oneToOne === true ? 'People' : `People (${userCount})` + let content = [] + + if (ids.length >= 30) { + for (let i = 0; i < 30; i++) { + const id = ids[i] + content.push( + onPress(id, entities[id].username)} + id={id}> + + + + + ) + } + } else { + content = ids.map(id => ( + onPress(id, entities[id].username)} + id={id}> + + + + + )) + } + return ( + + + + {content} + + {!oneToOne && ( + + + + + )} + + ) +} + +RoomUsers.propTypes = { + ids: PropTypes.array, + entities: PropTypes.object, + onPress: PropTypes.func, + userCount: PropTypes.number, + onAllUsersPress: PropTypes.func, + oneToOne: PropTypes.bool +} + +export default RoomUsers diff --git a/app/components/RoomInfo/UserInfo.js b/app/components/RoomInfo/UserInfo.js new file mode 100644 index 0000000..eda55af --- /dev/null +++ b/app/components/RoomInfo/UserInfo.js @@ -0,0 +1,33 @@ +import React, { + PropTypes, + View, + Text +} from 'react-native' +import s from '../../styles/screens/RoomInfo/RoomInfoStyles' + +import Avatar from '../Avatar' +import Divider from '../Divider' + +const RoomInfo = ({name, user}) => { + return ( + + + + + {name} + @{user.username} + + + + + ) +} + +RoomInfo.propTypes = { + name: PropTypes.string, + user: PropTypes.object +} + +export default RoomInfo diff --git a/app/components/RoomUsers/RoomUserItem.js b/app/components/RoomUsers/RoomUserItem.js new file mode 100644 index 0000000..1ba7f9a --- /dev/null +++ b/app/components/RoomUsers/RoomUserItem.js @@ -0,0 +1,35 @@ +import React, { + PropTypes, + View, + Text +} from 'react-native' +import s from '../../styles/screens/RoomUsers/RoomUserItemStyles' + +import Avatar from '../Avatar' +import Button from '../Button' + +const RoomUserItem = ({onUserItemPress, id, username, displayName, avatarUrlSmall}) => { + return ( + + ) +} + +RoomUserItem.propTypes = { + onUserItemPress: PropTypes.func, + id: PropTypes.string, + username: PropTypes.string, + displayName: PropTypes.string, + avatarUrlSmall: PropTypes.string +} + +export default RoomUserItem diff --git a/app/components/RoomUsers/RoomUsersList.js b/app/components/RoomUsers/RoomUsersList.js new file mode 100644 index 0000000..1564ab3 --- /dev/null +++ b/app/components/RoomUsers/RoomUsersList.js @@ -0,0 +1,55 @@ +import React, { + Component, + PropTypes, + ListView, + View +} from 'react-native' + +import RoomUserItem from './RoomUserItem' + +export default class RoomUsersList extends Component { + constructor(props) { + super(props) + + this.renderRow = this.renderRow.bind(this) + } + + renderRow(rowData, rowId) { + const {onItemPress, onUserItemPress} = this.props + + return ( + + ) + } + + render() { + const {listViewData} = this.props + + if (!listViewData) { + return + } + + return ( + this.renderRow(rowData, rowId)} /> + ) + } +} + +RoomUsersList.propTypes = { + listViewData: PropTypes.object, + onItemPress: PropTypes.func, + onEndReached: PropTypes.func, + onUserItemPress: PropTypes.func +} diff --git a/app/components/RoomUsers/RoomUsersSearchResult.js b/app/components/RoomUsers/RoomUsersSearchResult.js new file mode 100644 index 0000000..42d6455 --- /dev/null +++ b/app/components/RoomUsers/RoomUsersSearchResult.js @@ -0,0 +1,52 @@ +import React, { + PropTypes, + View, + Text, + ScrollView +} from 'react-native' +import s from '../../styles/screens/RoomUsers/RoomUsersSearchResultStyles' + +import RoomUserItem from './RoomUserItem' +import Loading from '../Loading' + +const RoomUsersSearchResult = ({resultItems, onUserItemPress, isLoading}) => { + if (isLoading) { + return ( + + + + + + ) + } + + if (resultItems.length === 0) { + return ( + + + No result + + + ) + } + + return ( + + + {resultItems.map(item => ( + + ))} + + + ) +} + +RoomUsersSearchResult.propTypes = { + resultItems: PropTypes.array, + onUserItemPress: PropTypes.func, + isLoading: PropTypes.bool +} + +export default RoomUsersSearchResult diff --git a/app/components/User/UserInfo.js b/app/components/User/UserInfo.js index 5fa554e..04fac24 100644 --- a/app/components/User/UserInfo.js +++ b/app/components/User/UserInfo.js @@ -58,7 +58,7 @@ const UserInfo = ({id, company, location, email, profile, has_gitter_login, styles={[s.button, s.chatPrivately]} onPress={() => onChatPrivatelyPress(id)}> Chat privately diff --git a/app/modules/app.js b/app/modules/app.js index 7021ac3..fc5c29c 100644 --- a/app/modules/app.js +++ b/app/modules/app.js @@ -50,6 +50,7 @@ export function init() { dispatch(Navigation.resetTo({name: 'home'})) // dispatch(Navigation.resetTo({name: 'user', userId: '52ce7f4eed5ab0b3bf053782', username: 'blia'})) // dispatch(Navigation.resetTo({name: 'room', roomId: '56a41e0fe610378809bde160'})) + // dispatch(Navigation.resetTo({name: 'roomUsers', roomId: '56a41e0fe610378809bde160'})) } catch (error) { dispatch({ type: INITIALIZED, error }) dispatch(Navigation.goAndReplace({name: 'login'})) diff --git a/app/modules/index.js b/app/modules/index.js index f6c1391..b29d191 100644 --- a/app/modules/index.js +++ b/app/modules/index.js @@ -1,23 +1,27 @@ import {combineReducers} from 'redux' +import ui from './ui' import app from './app' import auth from './auth' import rooms from './rooms' import users from './users' import viewer from './viewer' import search from './search' +import roomInfo from './roomInfo' import messages from './messages' import settings from './settings' import realtime from './realtime' import navigation from './navigation' const rootReducer = combineReducers({ + ui, app, auth, rooms, users, viewer, search, + roomInfo, messages, settings, realtime, diff --git a/app/modules/roomInfo.js b/app/modules/roomInfo.js new file mode 100644 index 0000000..9e42408 --- /dev/null +++ b/app/modules/roomInfo.js @@ -0,0 +1,92 @@ +import * as Api from '../api/gitter' + +export const REPO_INFO = 'roomInfo/REPO_INFO' +export const REPO_INFO_OK = 'roomInfo/REPO_INFO_OK' +export const REPO_INFO_ERROR = 'roomInfo/REPO_INFO_ERROR' +export const ROOM_INFO = 'roomInfo/ROOM_INFO' +export const CLEAR_ERROR = 'roomInfo/CLEAR_ERROR' + +export function getRoomInfo(repoName, roomId) { + return async (dispatch, getState) => { + const {token} = getState().auth + const room = getState().rooms.rooms[roomId] + + if (room.githubType !== 'REPO') { + dispatch({type: ROOM_INFO, payload: room}) + } else { + dispatch({type: REPO_INFO, repoName}) + + try { + const res = await Api.getRepoInfo(token, repoName) + const resText = await res.text() + + if (resText.length > 0) { + const payload = JSON.parse(resText) + dispatch({type: REPO_INFO_OK, payload, repoName}) + } + } catch (error) { + dispatch({type: REPO_INFO_ERROR, error}) + } + } + } +} + +export function clearRoomInfoError() { + return { + type: CLEAR_ERROR + } +} + +const initialState = { + isFetching: false, + ids: [], + entities: {}, + isError: false, + error: {} +} + +export default function roomInfo(state = initialState, action) { + switch (action.type) { + case REPO_INFO: + return {...state, + isFetching: true + } + + case REPO_INFO_OK: { + const {payload, repoName} = action + return {...state, + isFetching: false, + ids: state.ids.concat(repoName), + entities: {...state.entities, + [repoName]: payload + } + } + } + + case ROOM_INFO: + return {...state, + isFetching: false, + ids: state.ids.concat(action.payload.name), + entities: {...state.entities, + [action.payload.name]: action.payload + } + } + + case CLEAR_ERROR: + return {...state, + isFetching: false, + isError: false, + error: {} + } + + case REPO_INFO_ERROR: + return {...state, + isFetching: false, + isError: true, + error: action.error + } + + default: + return state + } +} diff --git a/app/modules/rooms.js b/app/modules/rooms.js index 64cd3fb..a057101 100644 --- a/app/modules/rooms.js +++ b/app/modules/rooms.js @@ -250,7 +250,8 @@ export default function rooms(state = initialState, action) { } case CURRENT_USER_ROOMS_RECEIVED: { - const normalized = normalize(action.payload) + const sorted = action.payload.sort((item1, item2) => Date.parse(item2.lastAccessTime) - Date.parse(item1.lastAccessTime)) + const normalized = normalize(sorted) return {...state, isLoading: false, ids: state.ids.concat(normalized.ids), diff --git a/app/modules/search.js b/app/modules/search.js index fe51b92..d7547c7 100644 --- a/app/modules/search.js +++ b/app/modules/search.js @@ -12,6 +12,9 @@ export const SEARCH_ROOMS = 'search/SEARCH_ROOMS' export const SEARCH_ROOMS_OK = 'search/SEARCH_ROOMS_OK' export const SEARCH_ROOMS_FAILED = 'search/SEARCH_ROOMS_FAILED' export const CLEAR_SEARCH = 'search/CLEAR_SEARCH' +export const SEARCH_ROOM_USERS = 'search/SEARCH_ROOM_USERS' +export const SEARCH_ROOM_USERS_OK = 'search/SEARCH_ROOM_USERS_OK' +export const SEARCH_ROOM_USERS_FAILED = 'search/SEARCH_ROOM_USERS_FAILED' /** * Actions @@ -64,6 +67,20 @@ export function searchRooms(query) { } } +export function searchRoomUsers(roomId, query) { + return async (dispatch, getState) => { + const {token} = getState().auth + dispatch({type: SEARCH_ROOM_USERS, roomId, query}) + + try { + const payload = await Api.searchRoomUsers(token, roomId, query) + dispatch({type: SEARCH_ROOM_USERS_OK, payload}) + } catch (error) { + dispatch({type: SEARCH_ROOM_USERS_FAILED, error}) + } + } +} + /** * Reducer */ @@ -71,9 +88,11 @@ export function searchRooms(query) { const initialState = { isLoadingUsers: false, isLoadingRooms: false, + isLoadingRoomUser: false, inputValue: '', usersResult: [], roomsResult: [], + roomUsersResult: [], error: false, errors: {} } @@ -98,6 +117,11 @@ export default function search(state = initialState, action) { isLoadingRooms: true } + case SEARCH_ROOM_USERS: + return {...state, + isLoadingRoomUser: true + } + case SEARCH_USERS_OK: return {...state, isLoadingUsers: false, @@ -110,6 +134,13 @@ export default function search(state = initialState, action) { roomsResult: action.payload } + case SEARCH_ROOM_USERS_OK: + return {...state, + isLoadingRoomUser: false, + roomUsersResult: action.payload + } + + case SEARCH_ROOM_USERS_FAILED: case SEARCH_USERS_FAILED: case SEARCH_ROOMS_FAILED: return {...state, diff --git a/app/modules/settings.js b/app/modules/settings.js index 62cf61c..0c56d39 100644 --- a/app/modules/settings.js +++ b/app/modules/settings.js @@ -1,5 +1,6 @@ const initialState = { - limit: 30 + limit: 30, + usersLimit: 30 } export default function settings(state = initialState, action) { diff --git a/app/modules/ui.js b/app/modules/ui.js new file mode 100644 index 0000000..92440f4 --- /dev/null +++ b/app/modules/ui.js @@ -0,0 +1,25 @@ +export const CHANGE_ROOM_INFO_DRAWER_STATE = 'ui/RoomInfo/CHANGE_ROOM_INFO_DRAWER_STATE' + +export function changeRoomInfoDrawerState(state) { + return { + type: CHANGE_ROOM_INFO_DRAWER_STATE, + state + } +} + +const initialState = { + roomInfoDrawerState: 'close' +} + +export default function ui(state = initialState, action) { + switch (action.type) { + + case CHANGE_ROOM_INFO_DRAWER_STATE: + return {...state, + roomInfoDrawerState: action.state + } + + default: + return state + } +} diff --git a/app/modules/users.js b/app/modules/users.js index af3f9e1..9b0cb9b 100644 --- a/app/modules/users.js +++ b/app/modules/users.js @@ -1,4 +1,5 @@ import * as Api from '../api/gitter' +import normalize from '../utils/normalize' import {joinUserRoom} from './rooms' import * as Navigation from './navigation' import _ from 'lodash' @@ -13,6 +14,15 @@ export const USER_FAILED = 'users/USER_FAILED' export const CHAT_PRIVATELY = 'users/CHAT_PRIVATELY' export const CHAT_PRIVATELY_OK = 'users/CHAT_PRIVATELY_OK' export const CHAT_PRIVATELY_FAILED = 'users/CHAT_PRIVATELY_FAILED' +export const ROOM_USERS = 'users/ROOM_USERS' +export const ROOM_USERS_OK = 'users/ROOM_USERS_OK' +export const ROOM_USERS_FAILED = 'users/ROOM_USERS_FAILED' +export const PREPARE_LIST_VIEW = 'users/PREPARE_LIST_VIEW' +export const ROOM_USERS_WITH_SKIP = 'users/ROOM_USERS_WITH_SKIP' +export const ROOM_USERS_WITH_SKIP_OK = 'users/ROOM_USERS_WITH_SKIP_OK' +export const ROOM_USERS_WITH_SKIP_ERROR = 'users/ROOM_USERS_WITH_SKIP_ERROR' +export const NO_MORE_USERS_TO_LOAD = 'users/NO_MORE_USERS_TO_LOAD' + /** * Actions @@ -53,6 +63,55 @@ export function chatPrivately(userId) { } } +export function roomUsers(roomId) { + return async (dispatch, getState) => { + const {token} = getState().auth + dispatch({type: ROOM_USERS, roomId}) + + try { + const payload = await Api.getRoomUsers(token, roomId) + dispatch({type: ROOM_USERS_OK, payload, roomId}) + } catch (error) { + dispatch({type: ROOM_USERS_FAILED, error, roomId}) + } + } +} + +export function prepareListView(roomId, dataSource) { + return { + type: PREPARE_LIST_VIEW, dataSource, roomId + } +} + +export function roomUsersWithSkip(roomId) { + return async (dispatch, getState) => { + if (getState().users.noMore[roomId] === true) { + return + } + + const {token} = getState().auth + const {usersLimit} = getState().settings + const {ids} = getState().users.byRoom[roomId] + const skip = ids.length + + dispatch({type: ROOM_USERS_WITH_SKIP, roomId, skip}) + try { + const payload = await Api.getRoomUsersWithSkip(token, roomId, skip) + + if (payload.length === 0) { + dispatch({type: NO_MORE_USERS_TO_LOAD, roomId}) + } else { + dispatch({type: ROOM_USERS_WITH_SKIP_OK, roomId, payload}) + if (payload.length < usersLimit) { + dispatch({type: NO_MORE_USERS_TO_LOAD, roomId}) + } + } + } catch (error) { + dispatch({type: ROOM_USERS_WITH_SKIP_ERROR, error}) + } + } +} + /** * Reducer @@ -60,8 +119,23 @@ export function chatPrivately(userId) { const initialState = { isLoadingUser: false, + isLoadingUsers: false, ids: [], entities: {}, + byRoom: { + // [roomId]: { + // ids: [], + // entities: {} + // } + }, + listView: { + // [roomId]: { + // dataSource, + // rowIds, + // data + // } + }, + noMore: {}, error: false, errors: {} } @@ -73,6 +147,11 @@ export default function users(state = initialState, action) { isLoadingUser: true } + case ROOM_USERS: + return {...state, + isLoadingUsers: true + } + case USER_OK: { const {payload} = action return {...state, @@ -84,9 +163,86 @@ export default function users(state = initialState, action) { } } + case ROOM_USERS_OK: { + const {payload, roomId} = action + const {ids, entities} = normalize(payload) + return {...state, + isLoadingUsers: false, + byRoom: {...state.byRoom, + [roomId]: { + ids, + entities + } + } + } + } + + case PREPARE_LIST_VIEW: { + const {roomId, dataSource} = action + const data = [] + const rowIds = [] + const {entities, ids} = state.byRoom[roomId] + + for (let i = 0; i < ids.length && i < 30; i++) { + data.push(entities[ids[i]]) + rowIds.push(data.length - 1) + } + + return {...state, + listView: {...state.listView, + [roomId]: { + dataSource: dataSource.cloneWithRows(data, rowIds), + data, + rowIds + } + } + } + } + + case ROOM_USERS_WITH_SKIP_OK: { + const {roomId, payload} = action + const listView = state.listView[roomId] + const byRoom = state.byRoom[roomId] + const data = [].concat(listView.data) + const rowIds = [].concat(listView.rowIds) + + const {ids, entities} = normalize(payload) + + for (let i = 0; i < ids.length && i < 30; i++) { + data.push(entities[ids[i]]) + rowIds.push(data.length - 1) + } + + return {...state, + byRoom: {...state.byRoom, + [roomId]: { + ids: byRoom.ids.concat(ids), + entities: Object.assign({}, byRoom.entities, entities) + } + }, + listView: {...state.listView, + [roomId]: { + dataSource: state.listView[roomId].dataSource.cloneWithRows(data, rowIds), + data, + rowIds + } + } + } + } + + case NO_MORE_USERS_TO_LOAD: + return {...state, + noMore: {...state.noMore, + [action.roomId]: true + } + } + + case ROOM_USERS_WITH_SKIP_ERROR: + case ROOM_USERS_FAILED: case USER_FAILED: return {...state, isLoadingUser: false, + isLoadingUsers: false, error: true, errors: action.error } diff --git a/app/screens/Drawer.js b/app/screens/Drawer.js index 1d6d03b..931cec4 100644 --- a/app/screens/Drawer.js +++ b/app/screens/Drawer.js @@ -76,7 +76,7 @@ class Drawer extends Component { const {user, ids} = this.props return ( - + {ids.length === 0 ? diff --git a/app/screens/RoomInfoScreen.js b/app/screens/RoomInfoScreen.js new file mode 100644 index 0000000..df26782 --- /dev/null +++ b/app/screens/RoomInfoScreen.js @@ -0,0 +1,173 @@ +import React, { + PropTypes, + Component, + ScrollView, + Linking, + View +} from 'react-native' +import {connect} from 'react-redux' +import s from '../styles/screens/RoomInfo/RoomInfoScreenStyles' +import {THEMES} from '../constants' +const {colors} = THEMES.gitterDefault +import _ from 'lodash' + +import * as Navigation from '../modules/navigation' +import {clearRoomInfoError, getRoomInfo} from '../modules/roomInfo' +import {roomUsers} from '../modules/users' + +import Loading from '../components/Loading' +import FailedToLoad from '../components/FailedToLoad' +import RoomInfo from '../components/RoomInfo/RoomInfo' +import RepoInfo from '../components/RoomInfo/RepoInfo' +import UserInfo from '../components/RoomInfo/UserInfo' +import RoomUsers from '../components/RoomInfo/RoomUsers' + + +class RoomInfoScreen extends Component { + constructor(props) { + super(props) + this.renderInfo = this.renderInfo.bind(this) + this.renderUsers = this.renderUsers.bind(this) + this.refetchData = this.refetchData.bind(this) + this.handleUserPress = this.handleUserPress.bind(this) + this.handleUrlPress = this.handleUrlPress.bind(this) + this.handleStatItemPress = this.handleStatItemPress.bind(this) + } + + componentDidMount() { + const {dispatch} = this.props + dispatch(clearRoomInfoError()) + } + + componentWillReceiveProps(nextProps) { + if (_.isEqual(this.props, nextProps)) { + return + } + + const {rooms, roomInfo, route: {roomId}, dispatch, roomInfoDrawerState, users} = nextProps + const room = rooms[roomId] + if (!!room && roomInfoDrawerState === 'open' && !roomInfo[room.name]) { + dispatch(getRoomInfo(room.name, roomId)) + } + if (!!room && roomInfoDrawerState === 'open' && !users[roomId]) { + dispatch(roomUsers(roomId)) + } + } + + handleUserPress(userId, username) { + const {dispatch} = this.props + dispatch(Navigation.goTo({name: 'user', userId, username})) + } + + handleUrlPress(url) { + Linking.openURL(url) + } + + handleStatItemPress(url, type) { + Linking.openURL(`${url}/${type}`) + } + + handleAllUsersPress() { + const {dispatch, route: {roomId}} = this.props + dispatch(Navigation.goTo({name: 'roomUsers', roomId})) + } + + refetchData() { + const {rooms, route: {roomId}, dispatch} = this.props + const room = rooms[roomId] + dispatch(clearRoomInfoError()) + dispatch(getRoomInfo(room.name, roomId)) + } + + renderInfo() { + const {rooms, roomInfo, route: {roomId}} = this.props + + if (rooms[roomId].githubType === 'REPO') { + return ( + + ) + } + + if (rooms[roomId].githubType === 'ONETOONE') { + return ( + + ) + } + return ( + + ) + } + + renderUsers() { + const {users, rooms, route: {roomId}} = this.props + return ( + + ) + } + + + render() { + const {users, rooms, roomInfo, route: {roomId}, isError} = this.props + const room = rooms[roomId] + if (isError) { + return ( + + + + ) + } + + if (!room || !roomInfo[room.name] || !users[roomId]) { + return ( + + + + ) + } + + return ( + + + {this.renderInfo()} + {this.renderUsers()} + + + ) + } +} + +RoomInfoScreen.propTypes = { + dispatch: PropTypes.func, + drawer: PropTypes.element, + route: PropTypes.object, + roomInfo: PropTypes.object, + rooms: PropTypes.object, + roomInfoDrawerState: PropTypes.string, + isError: PropTypes.bool, + users: PropTypes.object +} + +function mapStateToProps(state) { + return { + roomInfo: state.roomInfo.entities, + rooms: state.rooms.rooms, + roomInfoDrawerState: state.ui.roomInfoDrawerState, + isError: state.roomInfo.isError, + users: state.users.byRoom + } +} + +export default connect(mapStateToProps)(RoomInfoScreen) diff --git a/app/screens/RoomScreen.js b/app/screens/RoomScreen.js index 23b3ac5..3ba0f88 100644 --- a/app/screens/RoomScreen.js +++ b/app/screens/RoomScreen.js @@ -2,6 +2,7 @@ import React, { Component, PropTypes, InteractionManager, + DrawerLayoutAndroid, ToolbarAndroid, ToastAndroid, Clipboard, @@ -27,8 +28,11 @@ import { updateMessage, clearError as clearMessagesError } from '../modules/messages' +import {changeRoomInfoDrawerState} from '../modules/ui' import * as Navigation from '../modules/navigation' +import RoomInfoScreen from './RoomInfoScreen' + import Loading from '../components/Loading' import MessagesList from '../components/Room/MessagesList' import SendMessageField from '../components/Room/SendMessageField' @@ -39,6 +43,8 @@ import FailedToLoad from '../components/FailedToLoad' class Room extends Component { constructor(props) { super(props) + this.roomInfoDrawer = null + this.renderToolbar = this.renderToolbar.bind(this) this.renderListView = this.renderListView.bind(this) this.prepareDataSources = this.prepareDataSources.bind(this) @@ -53,6 +59,7 @@ class Room extends Component { this.handleCopyToClipboard = this.handleCopyToClipboard.bind(this) this.handleUsernamePress = this.handleUsernamePress.bind(this) this.handleUserAvatarPress = this.handleUserAvatarPress.bind(this) + this.renderRoomInfo = this.renderRoomInfo.bind(this) this.state = { textInputValue: '', @@ -65,7 +72,7 @@ class Room extends Component { this.prepareDataSources() const {activeRoom, rooms, route: { roomId }, dispatch, listViewData} = this.props // dispatch(subscribeToChatMessages(roomId)) - + dispatch(changeRoomInfoDrawerState('close')) InteractionManager.runAfterInteractions(() => { dispatch(clearMessagesError()) if (activeRoom !== roomId) { @@ -222,9 +229,12 @@ class Room extends Component { handleToolbarActionSelected(index) { const {dispatch, route: {roomId}} = this.props if (index === 0) { - dispatch(changeFavoriteStatus(roomId)) + this.roomInfoDrawer.openDrawer() } if (index === 1) { + dispatch(changeFavoriteStatus(roomId)) + } + if (index === 2) { this.leaveRoom() } } @@ -276,6 +286,11 @@ class Room extends Component { if (!!room && room.roomMember) { if (room.hasOwnProperty('favourite')) { actions = [{ + title: 'Open room info', + icon: require('image!ic_info_outline_white_24dp'), + show: 'always' + }, + { title: 'Remove from favorite', show: 'never' }, @@ -285,6 +300,11 @@ class Room extends Component { }] } else { actions = [{ + title: 'Open room info', + icon: require('image!ic_info_outline_white_24dp'), + show: 'always' + }, + { title: 'Add to favorite', show: 'never' }, @@ -343,7 +363,7 @@ class Room extends Component { return ( + onRetry={this.onRetryFetchingMessages.bind(this)} /> ) } return ( @@ -358,8 +378,18 @@ class Room extends Component { ) } + renderRoomInfo() { + const {route} = this.props + return ( + + ) + } + render() { - const {rooms, listViewData, route, isLoadingMessages, isLoadingMore, getMessagesError} = this.props + const {rooms, listViewData, route, isLoadingMessages, + isLoadingMore, getMessagesError, dispatch} = this.props if (getMessagesError && !rooms[route.roomId]) { return ( @@ -382,11 +412,21 @@ class Room extends Component { return ( - {this.renderToolbar()} - {isLoadingMore ? this.renderLoadingMore() : null} - {isLoadingMessages ? this.renderLoading() : this.renderListView()} - {getMessagesError || isLoadingMessages || _.has(listView, 'data') && - listView.data.length === 0 ? null : this.renderBottom()} + this.roomInfoDrawer = component} + style={{backgroundColor: 'white'}} + drawerWidth={300} + onDrawerOpen={() => dispatch(changeRoomInfoDrawerState('open'))} + onDrawerClose={() => dispatch(changeRoomInfoDrawerState('close'))} + drawerPosition={DrawerLayoutAndroid.positions.Right} + renderNavigationView={this.renderRoomInfo} + keyboardDismissMode="on-drag"> + {this.renderToolbar()} + {isLoadingMore ? this.renderLoadingMore() : null} + {isLoadingMessages ? this.renderLoading() : this.renderListView()} + {getMessagesError || isLoadingMessages || _.has(listView, 'data') && + listView.data.length === 0 ? null : this.renderBottom()} + ) } @@ -405,12 +445,14 @@ Room.propTypes = { entities: PropTypes.object, hasNoMore: PropTypes.object, currentUser: PropTypes.object, - getMessagesError: PropTypes.bool + getMessagesError: PropTypes.bool, + roomInfoDrawerState: PropTypes.string } function mapStateToProps(state) { const {listView, isLoading, isLoadingMore, byRoom, hasNoMore, entities} = state.messages const {activeRoom, rooms} = state.rooms + const {roomInfoDrawerState} = state.ui return { activeRoom, rooms, @@ -421,7 +463,8 @@ function mapStateToProps(state) { isLoadingMore, byRoom, hasNoMore, - currentUser: state.viewer.user + currentUser: state.viewer.user, + roomInfoDrawerState } } diff --git a/app/screens/RoomUsersScreen.js b/app/screens/RoomUsersScreen.js new file mode 100644 index 0000000..a6826fd --- /dev/null +++ b/app/screens/RoomUsersScreen.js @@ -0,0 +1,144 @@ +import React, { + Component, + PropTypes, + View, + ListView +} from 'react-native' +import s from '../styles/screens/RoomUsers/RoomUsersScreenStyles' +import {connect} from 'react-redux' +import _ from 'lodash' + +import * as Navigation from '../modules/navigation' +import {searchRoomUsers} from '../modules/search' +import {prepareListView, roomUsersWithSkip} from '../modules/users' + +import CustomSearch from '../components/CustomSearch' +import RoomUsersSearchResult from '../components/RoomUsers/RoomUsersSearchResult' +import RoomUsersList from '../components/RoomUsers/RoomUsersList' + +class RoomUsersScreen extends Component { + constructor(props) { + super(props) + + this.handleChange = this.handleChange.bind(this) + this.handleBackPress = this.handleBackPress.bind(this) + this.handleClearPress = this.handleClearPress.bind(this) + this.renderSearch = this.renderSearch.bind(this) + this.searchRequest = _.debounce(this.searchRequest.bind(this), 400) + this.renderSearchResult = this.renderSearchResult.bind(this) + this.prepareDataSources = this.prepareDataSources.bind(this) + this.handleUserItemPress = this.handleUserItemPress.bind(this) + this.onEndReached = this.onEndReached.bind(this) + + this.state = { + value: '' + } + } + + componentDidMount() { + this.prepareDataSources() + } + + onEndReached() { + const {dispatch, route: {roomId}} = this.props + dispatch(roomUsersWithSkip(roomId)) + } + + handleChange(event) { + this.setState({value: event.nativeEvent.text}) + this.searchRequest(event.nativeEvent.text) + } + + handleClearPress() { + this.setState({value: ''}) + } + + handleBackPress() { + const {dispatch} = this.props + dispatch(Navigation.goBack()) + } + + handleUserItemPress(id, username) { + const {dispatch} = this.props + dispatch(Navigation.goTo({name: 'user', userId: id, username})) + } + + prepareDataSources() { + const {listViewData, route: {roomId}, dispatch} = this.props + if (!listViewData[roomId]) { + const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => !_.isEqual(r1, r2)}) + dispatch(prepareListView(roomId, ds.cloneWithRows([]))) + } + } + + searchRequest(query) { + const {dispatch, route: {roomId}} = this.props + + if (!query.trim()) { + return + } + + dispatch(searchRoomUsers(roomId, query)) + } + + renderSearch() { + return ( + + ) + } + + renderSearchResult() { + const {roomUsersResult, isLoading} = this.props + return ( + + ) + } + + renderUserList() { + const {listViewData, route: {roomId}} = this.props + return ( + + ) + } + + render() { + return ( + + {this.renderSearch()} + + {!!this.state.value ? this.renderSearchResult() : this.renderUserList()} + + + ) + } +} + +RoomUsersScreen.propTypes = { + dispatch: PropTypes.func, + roomUsersResult: PropTypes.array, + route: PropTypes.object, + isLoading: PropTypes.bool, + listViewData: PropTypes.object +} + +function mapStateToProps(state) { + const {roomUsersResult, isLoadingRoomUser} = state.search + return { + roomUsersResult, + isLoading: isLoadingRoomUser, + listViewData: state.users.listView + } +} + +export default connect(mapStateToProps)(RoomUsersScreen) diff --git a/app/screens/index.js b/app/screens/index.js index 7b76b0b..8b20dd7 100644 --- a/app/screens/index.js +++ b/app/screens/index.js @@ -10,6 +10,7 @@ import {init} from '../modules/app' import {connect} from 'react-redux' import * as Navigation from '../modules/navigation' import {selectRoom} from '../modules/rooms' +import {changeRoomInfoDrawerState} from '../modules/ui' import LaunchScreen from './LaunchScreen' import LoginScreen from './LoginScreen' @@ -20,6 +21,7 @@ import RoomScreen from './RoomScreen' import SearchScreen from './SearchScreen' import UserScreen from './UserScreen' import Drawer from './Drawer' +import RoomUsersScreen from './RoomUsersScreen' // this need for passing navigator instance to navigation module export let nav @@ -94,6 +96,8 @@ class App extends Component { setTimeout(() => { if (current.name === 'room' && route.name === 'room') { dispatch(Navigation.goAndReplace(route)) + } else if (route.name === 'room') { + dispatch(Navigation.resetWithStack([{name: 'home'}, route])) } else { dispatch(Navigation.goTo(route)) } @@ -153,6 +157,13 @@ class App extends Component { return ( ) + + case 'roomUsers': + return ( + + ) + default: return null } @@ -170,7 +181,7 @@ class App extends Component { const {navigation} = this.props // const initialRoute = {name: 'launch'} // const initialRoute = {name: 'room', roomId: '56a41e0fe610378809bde160'} - const drawerLockMode = ['launch', 'login', 'loginByToken', 'user'].indexOf(navigation.current.name) === -1 + const drawerLockMode = ['launch', 'login', 'loginByToken'].indexOf(navigation.current.name) === -1 ? 'unlocked' : 'locked-closed' @@ -198,12 +209,15 @@ class App extends Component { App.propTypes = { dispatch: PropTypes.func, - navigation: PropTypes.object + navigation: PropTypes.object, + roomInfoDrawerState: PropTypes.string } function mapStateToProps(state) { + const {roomInfoDrawerState} = state.ui return { - navigation: state.navigation + navigation: state.navigation, + roomInfoDrawerState } } diff --git a/app/styles/components/CustomSearchStyles.js b/app/styles/components/CustomSearchStyles.js new file mode 100644 index 0000000..40c5b1d --- /dev/null +++ b/app/styles/components/CustomSearchStyles.js @@ -0,0 +1,33 @@ +import {StyleSheet} from 'react-native' +const height = 48 + +const styles = StyleSheet.create({ + container: { + height, + flexDirection: 'row', + alignItems: 'center', + margin: 8, + backgroundColor: 'white', + elevation: 2, + borderRadius: 2 + }, + button: { + elevation: 0, + width: 56, + height + }, + buttonIcon: { + width: 25, + height: 25, + opacity: 0.6 + }, + innerContainer: { + flex: 1 + }, + textInput: { + backgroundColor: 'white', + fontSize: 18 + } +}) + +export default styles diff --git a/app/styles/components/ParsedTextStyles.js b/app/styles/components/ParsedTextStyles.js index d3cc240..cc548b8 100644 --- a/app/styles/components/ParsedTextStyles.js +++ b/app/styles/components/ParsedTextStyles.js @@ -17,6 +17,9 @@ const styles = StyleSheet.create({ }, emoji: { fontSize: 14 + }, + text: { + fontSize: 14 } }) diff --git a/app/styles/screens/Drawer/ChannelListItemStyles.js b/app/styles/screens/Drawer/ChannelListItemStyles.js index 50bb68b..a77505d 100644 --- a/app/styles/screens/Drawer/ChannelListItemStyles.js +++ b/app/styles/screens/Drawer/ChannelListItemStyles.js @@ -5,7 +5,7 @@ const styles = StyleSheet.create({ flex: 1, flexDirection: 'row', alignItems: 'center', - paddingLeft: 20, + paddingLeft: 16, height: 50, backgroundColor: 'white' }, diff --git a/app/styles/screens/Drawer/ChannelListSectionStyles.js b/app/styles/screens/Drawer/ChannelListSectionStyles.js index c835359..87a8b6c 100644 --- a/app/styles/screens/Drawer/ChannelListSectionStyles.js +++ b/app/styles/screens/Drawer/ChannelListSectionStyles.js @@ -2,16 +2,9 @@ import {StyleSheet} from 'react-native' const styles = StyleSheet.create({ container: { - height: 50, - justifyContent: 'center' }, heading: { - fontSize: 18, - color: 'black', - marginLeft: 20 - }, - itemSection: { - elevation: 4 + marginBottom: 16 } }) diff --git a/app/styles/screens/Drawer/DrawerStyles.js b/app/styles/screens/Drawer/DrawerStyles.js index a2b0ea6..8fdf320 100644 --- a/app/styles/screens/Drawer/DrawerStyles.js +++ b/app/styles/screens/Drawer/DrawerStyles.js @@ -2,7 +2,8 @@ import {StyleSheet} from 'react-native' const styles = StyleSheet.create({ container: { - flex: 1 + flex: 1, + backgroundColor: 'white' } }) diff --git a/app/styles/screens/Drawer/DrawerUserInfoStyles.js b/app/styles/screens/Drawer/DrawerUserInfoStyles.js index 92a070c..14c621f 100644 --- a/app/styles/screens/Drawer/DrawerUserInfoStyles.js +++ b/app/styles/screens/Drawer/DrawerUserInfoStyles.js @@ -10,10 +10,10 @@ const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'center', alignItems: 'center', - elevation: 6 + elevation: 4 }, info: { - marginLeft: 20, + marginLeft: 16, width: 165 }, displayName: { diff --git a/app/styles/screens/Room/SendMessageFieldStyles.js b/app/styles/screens/Room/SendMessageFieldStyles.js index 49768e9..f891a38 100644 --- a/app/styles/screens/Room/SendMessageFieldStyles.js +++ b/app/styles/screens/Room/SendMessageFieldStyles.js @@ -16,7 +16,8 @@ const style = StyleSheet.create({ flex: 1 }, textInput: { - backgroundColor: 'white' + backgroundColor: 'white', + fontSize: 14 }, button: { height: button, diff --git a/app/styles/screens/RoomInfo/RepoInfoStyles.js b/app/styles/screens/RoomInfo/RepoInfoStyles.js new file mode 100644 index 0000000..6f7d9e9 --- /dev/null +++ b/app/styles/screens/RoomInfo/RepoInfoStyles.js @@ -0,0 +1,39 @@ +import {StyleSheet} from 'react-native' + +const styles = StyleSheet.create({ + container: { + flex: 1 + }, + header: { + flexDirection: 'row', + paddingHorizontal: 16, + paddingVertical: 24 + }, + headerTextContainer: { + marginLeft: 16, + flexDirection: 'column' + }, + name: { + }, + itemContainer: { + paddingHorizontal: 16, + paddingVertical: 8 + }, + description: { + + }, + statContainer: { + flexDirection: 'row', + justifyContent: 'space-around' + }, + statItemContainer: { + alignItems: 'center', + width: 75 + }, + button: { + height: 48, + elevation: 0 + } +}) + +export default styles diff --git a/app/styles/screens/RoomInfo/RoomInfoScreenStyles.js b/app/styles/screens/RoomInfo/RoomInfoScreenStyles.js new file mode 100644 index 0000000..0343108 --- /dev/null +++ b/app/styles/screens/RoomInfo/RoomInfoScreenStyles.js @@ -0,0 +1,16 @@ +import {StyleSheet} from 'react-native' + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: 'white' + }, + tabs: { + + }, + tabsContainer: { + flex: 1 + } +}) + +export default styles diff --git a/app/styles/screens/RoomInfo/RoomInfoStyles.js b/app/styles/screens/RoomInfo/RoomInfoStyles.js new file mode 100644 index 0000000..eb1cfc7 --- /dev/null +++ b/app/styles/screens/RoomInfo/RoomInfoStyles.js @@ -0,0 +1,20 @@ +import {StyleSheet} from 'react-native' + +const styles = StyleSheet.create({ + container: { + flex: 1 + }, + header: { + flexDirection: 'row', + paddingHorizontal: 16, + paddingVertical: 24 + }, + headerTextContainer: { + marginLeft: 16, + flexDirection: 'column' + }, + name: { + } +}) + +export default styles diff --git a/app/styles/screens/RoomInfo/RoomUserStyles.js b/app/styles/screens/RoomInfo/RoomUserStyles.js new file mode 100644 index 0000000..57807e5 --- /dev/null +++ b/app/styles/screens/RoomInfo/RoomUserStyles.js @@ -0,0 +1,38 @@ +import {StyleSheet} from 'react-native' +import {THEMES} from '../../../constants' +const {colors} = THEMES.gitterDefault + +const styles = StyleSheet.create({ + container: { + flex: 1 + }, + usersContainer: { + flex: 1, + padding: 16, + alignItems: 'center', + flexWrap: 'wrap', + flexDirection: 'row' + }, + itemContainer: { + margin: 2 + }, + buttonsGroup: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + marginLeft: 16, + marginRight: 16, + marginBottom: 16 + }, + button: { + width: 128, + height: 32, + elevation: 2, + backgroundColor: colors.blue + }, + primaryButton: { + backgroundColor: colors.primaryButton + } +}) + +export default styles diff --git a/app/styles/screens/RoomUsers/RoomUserItemStyles.js b/app/styles/screens/RoomUsers/RoomUserItemStyles.js new file mode 100644 index 0000000..95767cf --- /dev/null +++ b/app/styles/screens/RoomUsers/RoomUserItemStyles.js @@ -0,0 +1,21 @@ +import {StyleSheet} from 'react-native' + +const styles = StyleSheet.create({ + button: { + elevation: 0, + flexDirection: 'row', + height: 70, + justifyContent: 'flex-start', + paddingLeft: 18, + paddingVertical: 16 + }, + userInfo: { + paddingLeft: 18 + }, + displayName: { + fontSize: 18, + color: 'black' + } +}) + +export default styles diff --git a/app/styles/screens/RoomUsers/RoomUsersScreenStyles.js b/app/styles/screens/RoomUsers/RoomUsersScreenStyles.js new file mode 100644 index 0000000..bfc984c --- /dev/null +++ b/app/styles/screens/RoomUsers/RoomUsersScreenStyles.js @@ -0,0 +1,16 @@ +import {StyleSheet} from 'react-native' + +const styles = StyleSheet.create({ + container: { + flex: 1 + }, + bottomContainer: { + flex: 1, + borderRadius: 2, + elevation: 2, + backgroundColor: 'white', + margin: 8 + } +}) + +export default styles diff --git a/app/styles/screens/RoomUsers/RoomUsersSearchResultStyles.js b/app/styles/screens/RoomUsers/RoomUsersSearchResultStyles.js new file mode 100644 index 0000000..9d86dfe --- /dev/null +++ b/app/styles/screens/RoomUsers/RoomUsersSearchResultStyles.js @@ -0,0 +1,19 @@ +import {StyleSheet} from 'react-native' + +const styles = StyleSheet.create({ + container: { + flex: 1 + }, + loading: { + height: 150 + }, + noResultContainer: { + paddingTop: 50 + }, + noResult: { + textAlign: 'center', + fontSize: 24 + } +}) + +export default styles diff --git a/app/utils/channelNameAndOwner.js b/app/utils/channelNameAndOwner.js new file mode 100644 index 0000000..a1bcf60 --- /dev/null +++ b/app/utils/channelNameAndOwner.js @@ -0,0 +1,4 @@ +export default function channelNameAndOwner(str) { + const [owner, name] = str.split(/\/(.*)/) + return {owner, name} +} diff --git a/package.json b/package.json index d138e72..86f6f5a 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "lodash": "^4.2.1", "moment": "^2.11.2", "react": "^0.14.7", - "react-native": "^0.22.0-rc4", + "react-native": "^0.22.0", "react-native-extra-dimensions-android": "^0.17.0", "react-native-invertible-scroll-view": "^0.2.0", "react-native-parsed-text": "0.0.11",