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