Room actions (#231)
* Layout * Empty starred list * Favorite room * Pinned messages * fix last messages * fix date on pinned messages
This commit is contained in:
parent
bb5e29fdc7
commit
b1bb815b07
|
@ -41,8 +41,8 @@ jobs:
|
|||
- image: circleci/android:api-26-alpha
|
||||
|
||||
environment:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"
|
||||
JVM_OPTS: -Xmx2048m
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError"
|
||||
JVM_OPTS: -Xmx4096m
|
||||
TERM: dumb
|
||||
BASH_ENV: "~/.nvm/nvm.sh"
|
||||
|
||||
|
|
|
@ -89,6 +89,8 @@ export const SERVER = createRequestTypes('SERVER', [
|
|||
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT', 'DISCONNECT_BY_USER']);
|
||||
export const LOGOUT = 'LOGOUT'; // logout is always success
|
||||
export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'REQUEST']);
|
||||
export const STARRED_MESSAGES = createRequestTypes('STARRED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGE_RECEIVED', 'MESSAGE_UNSTARRED']);
|
||||
export const PINNED_MESSAGES = createRequestTypes('PINNED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGE_RECEIVED', 'MESSAGE_UNPINNED']);
|
||||
|
||||
export const INCREMENT = 'INCREMENT';
|
||||
export const DECREMENT = 'DECREMENT';
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import * as types from './actionsTypes';
|
||||
|
||||
export function openPinnedMessages(rid) {
|
||||
return {
|
||||
type: types.PINNED_MESSAGES.OPEN,
|
||||
rid
|
||||
};
|
||||
}
|
||||
|
||||
export function closePinnedMessages() {
|
||||
return {
|
||||
type: types.PINNED_MESSAGES.CLOSE
|
||||
};
|
||||
}
|
||||
|
||||
export function pinnedMessageReceived(message) {
|
||||
return {
|
||||
type: types.PINNED_MESSAGES.MESSAGE_RECEIVED,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
export function pinnedMessageUnpinned(messageId) {
|
||||
return {
|
||||
type: types.PINNED_MESSAGES.MESSAGE_UNPINNED,
|
||||
messageId
|
||||
};
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import * as types from './actionsTypes';
|
||||
|
||||
export function openStarredMessages(rid) {
|
||||
return {
|
||||
type: types.STARRED_MESSAGES.OPEN,
|
||||
rid
|
||||
};
|
||||
}
|
||||
|
||||
export function closeStarredMessages() {
|
||||
return {
|
||||
type: types.STARRED_MESSAGES.CLOSE
|
||||
};
|
||||
}
|
||||
|
||||
export function starredMessageReceived(message) {
|
||||
return {
|
||||
type: types.STARRED_MESSAGES.MESSAGE_RECEIVED,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
export function starredMessageUnstarred(messageId) {
|
||||
return {
|
||||
type: types.STARRED_MESSAGES.MESSAGE_UNSTARRED,
|
||||
messageId
|
||||
};
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, TouchableHighlight, Text, TouchableOpacity, Vibration } from 'react-native';
|
||||
import { View, TouchableHighlight, Text, TouchableOpacity, Vibration, ViewPropTypes } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import moment from 'moment';
|
||||
|
@ -33,17 +33,18 @@ import styles from './styles';
|
|||
export default class Message extends React.Component {
|
||||
static propTypes = {
|
||||
item: PropTypes.object.isRequired,
|
||||
reactions: PropTypes.object.isRequired,
|
||||
reactions: PropTypes.any.isRequired,
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
Message_TimeFormat: PropTypes.string.isRequired,
|
||||
message: PropTypes.object.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
editing: PropTypes.bool,
|
||||
actionsShow: PropTypes.func,
|
||||
errorActionsShow: PropTypes.func,
|
||||
customEmojis: PropTypes.object,
|
||||
toggleReactionPicker: PropTypes.func,
|
||||
onReactionPress: PropTypes.func
|
||||
onReactionPress: PropTypes.func,
|
||||
style: ViewPropTypes.style,
|
||||
onLongPress: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -73,7 +74,7 @@ export default class Message extends React.Component {
|
|||
}
|
||||
|
||||
onLongPress() {
|
||||
this.props.actionsShow(this.parseMessage());
|
||||
this.props.onLongPress(this.parseMessage());
|
||||
}
|
||||
|
||||
onErrorPress() {
|
||||
|
@ -222,7 +223,7 @@ export default class Message extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
item, message, editing, baseUrl, customEmojis
|
||||
item, message, editing, baseUrl, customEmojis, style
|
||||
} = this.props;
|
||||
const username = item.alias || item.u.username;
|
||||
const isEditing = message._id === item._id && editing;
|
||||
|
@ -235,7 +236,7 @@ export default class Message extends React.Component {
|
|||
disabled={this.isDeleted() || this.hasError()}
|
||||
underlayColor='#FFFFFF'
|
||||
activeOpacity={0.3}
|
||||
style={[styles.message, isEditing ? styles.editing : null]}
|
||||
style={[styles.message, isEditing ? styles.editing : null, style]}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
>
|
||||
<View style={styles.flex}>
|
||||
|
|
|
@ -4,9 +4,12 @@ import { StackNavigator, DrawerNavigator } from 'react-navigation';
|
|||
import Sidebar from '../../containers/Sidebar';
|
||||
import RoomsListView from '../../views/RoomsListView';
|
||||
import RoomView from '../../views/RoomView';
|
||||
import RoomActionsView from '../../views/RoomActionsView';
|
||||
import CreateChannelView from '../../views/CreateChannelView';
|
||||
import SelectUsersView from '../../views/SelectUsersView';
|
||||
import NewServerView from '../../views/NewServerView';
|
||||
import StarredMessagesView from '../../views/StarredMessagesView';
|
||||
import PinnedMessagesView from '../../views/PinnedMessagesView';
|
||||
|
||||
const AuthRoutes = StackNavigator(
|
||||
{
|
||||
|
@ -33,6 +36,27 @@ const AuthRoutes = StackNavigator(
|
|||
navigationOptions: {
|
||||
title: 'New server'
|
||||
}
|
||||
},
|
||||
RoomActions: {
|
||||
screen: RoomActionsView,
|
||||
navigationOptions: {
|
||||
title: 'Actions',
|
||||
headerTintColor: '#292E35'
|
||||
}
|
||||
},
|
||||
StarredMessages: {
|
||||
screen: StarredMessagesView,
|
||||
navigationOptions: {
|
||||
title: 'Starred Messages',
|
||||
headerTintColor: '#292E35'
|
||||
}
|
||||
},
|
||||
PinnedMessages: {
|
||||
screen: PinnedMessagesView,
|
||||
navigationOptions: {
|
||||
title: 'Pinned Messages',
|
||||
headerTintColor: '#292E35'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createStore, applyMiddleware } from 'redux';
|
||||
import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import logger from 'redux-logger';
|
||||
import { composeWithDevTools } from 'remote-redux-devtools';
|
||||
|
@ -13,14 +13,15 @@ if (__DEV__) {
|
|||
/* eslint-disable global-require */
|
||||
const reduxImmutableStateInvariant = require('redux-immutable-state-invariant').default();
|
||||
|
||||
enhacers = composeWithDevTools(
|
||||
const devComposer = composeWithDevTools({ hostname: 'localhost', port: 8000 });
|
||||
enhacers = devComposer(
|
||||
applyAppStateListener(),
|
||||
applyMiddleware(reduxImmutableStateInvariant),
|
||||
applyMiddleware(sagaMiddleware),
|
||||
applyMiddleware(logger)
|
||||
);
|
||||
} else {
|
||||
enhacers = composeWithDevTools(
|
||||
enhacers = compose(
|
||||
applyAppStateListener(),
|
||||
applyMiddleware(sagaMiddleware)
|
||||
);
|
||||
|
|
|
@ -82,6 +82,7 @@ const subscriptionSchema = {
|
|||
// groupMentions: 0,
|
||||
roomUpdatedAt: { type: 'date', optional: true },
|
||||
ro: { type: 'bool', optional: true },
|
||||
lastOpen: { type: 'date', optional: true },
|
||||
lastMessage: { type: 'messages', optional: true }
|
||||
}
|
||||
};
|
||||
|
|
|
@ -13,6 +13,8 @@ import { someoneTyping, roomMessageReceived } from '../actions/room';
|
|||
import { setUser } from '../actions/login';
|
||||
import { disconnect, disconnect_by_user, connectSuccess, connectFailure } from '../actions/connect';
|
||||
import { requestActiveUser } from '../actions/activeUsers';
|
||||
import { starredMessageReceived, starredMessageUnstarred } from '../actions/starredMessages';
|
||||
import { pinnedMessageReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages';
|
||||
import Ddp from './ddp';
|
||||
|
||||
export { Accounts } from 'react-native-meteor';
|
||||
|
@ -145,6 +147,30 @@ const RocketChat = {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.ddp.on('rocketchat_starred_message', (ddpMessage) => {
|
||||
if (ddpMessage.msg === 'added') {
|
||||
const message = ddpMessage.fields;
|
||||
message._id = ddpMessage.id;
|
||||
const starredMessage = this._buildMessage(message);
|
||||
return reduxStore.dispatch(starredMessageReceived(starredMessage));
|
||||
}
|
||||
if (ddpMessage.msg === 'removed') {
|
||||
return reduxStore.dispatch(starredMessageUnstarred(ddpMessage.id));
|
||||
}
|
||||
});
|
||||
|
||||
this.ddp.on('rocketchat_pinned_message', (ddpMessage) => {
|
||||
if (ddpMessage.msg === 'added') {
|
||||
const message = ddpMessage.fields;
|
||||
message._id = ddpMessage.id;
|
||||
const pinnedMessage = this._buildMessage(message);
|
||||
return reduxStore.dispatch(pinnedMessageReceived(pinnedMessage));
|
||||
}
|
||||
if (ddpMessage.msg === 'removed') {
|
||||
return reduxStore.dispatch(pinnedMessageUnpinned(ddpMessage.id));
|
||||
}
|
||||
});
|
||||
}).catch(console.log);
|
||||
},
|
||||
|
||||
|
@ -272,9 +298,7 @@ const RocketChat = {
|
|||
_buildMessage(message) {
|
||||
message.status = messagesStatus.SENT;
|
||||
normalizeMessage(message);
|
||||
if (message.urls) {
|
||||
message.urls = RocketChat._parseUrls(message.urls);
|
||||
}
|
||||
message.urls = message.urls ? RocketChat._parseUrls(message.urls) : [];
|
||||
// loadHistory returns message.starred as object
|
||||
// stream-room-messages returns message.starred as an array
|
||||
message.starred = message.starred && (Array.isArray(message.starred) ? message.starred.length > 0 : !!message.starred);
|
||||
|
@ -358,8 +382,15 @@ const RocketChat = {
|
|||
createDirectMessage(username) {
|
||||
return call('createDirectMessage', username);
|
||||
},
|
||||
readMessages(rid) {
|
||||
return call('readMessages', rid);
|
||||
async readMessages(rid) {
|
||||
const ret = await call('readMessages', rid);
|
||||
|
||||
const [subscription] = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
database.write(() => {
|
||||
subscription.lastOpen = new Date();
|
||||
});
|
||||
|
||||
return ret;
|
||||
},
|
||||
joinRoom(rid) {
|
||||
return call('joinRoom', rid);
|
||||
|
@ -610,6 +641,9 @@ const RocketChat = {
|
|||
},
|
||||
setReaction(emoji, messageId) {
|
||||
return call('setReaction', emoji, messageId);
|
||||
},
|
||||
toggleFavorite(rid, f) {
|
||||
return call('toggleFavorite', rid, !f);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ import app from './app';
|
|||
import permissions from './permissions';
|
||||
import customEmojis from './customEmojis';
|
||||
import activeUsers from './activeUsers';
|
||||
import starredMessages from './starredMessages';
|
||||
import pinnedMessages from './pinnedMessages';
|
||||
|
||||
export default combineReducers({
|
||||
settings,
|
||||
|
@ -26,5 +28,7 @@ export default combineReducers({
|
|||
rooms,
|
||||
permissions,
|
||||
customEmojis,
|
||||
activeUsers
|
||||
activeUsers,
|
||||
starredMessages,
|
||||
pinnedMessages
|
||||
});
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import { PINNED_MESSAGES } from '../actions/actionsTypes';
|
||||
|
||||
const initialState = {
|
||||
messages: []
|
||||
};
|
||||
|
||||
export default function server(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case PINNED_MESSAGES.MESSAGE_RECEIVED:
|
||||
return {
|
||||
...state,
|
||||
messages: [...state.messages, action.message]
|
||||
};
|
||||
case PINNED_MESSAGES.MESSAGE_UNPINNED:
|
||||
return {
|
||||
...state,
|
||||
messages: state.messages.filter(message => message._id !== action.messageId)
|
||||
};
|
||||
case PINNED_MESSAGES.CLOSE:
|
||||
return initialState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { STARRED_MESSAGES } from '../actions/actionsTypes';
|
||||
|
||||
const initialState = {
|
||||
messages: []
|
||||
};
|
||||
|
||||
export default function server(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case STARRED_MESSAGES.MESSAGE_RECEIVED:
|
||||
return {
|
||||
...state,
|
||||
messages: [...state.messages, action.message]
|
||||
};
|
||||
case STARRED_MESSAGES.MESSAGE_UNSTARRED:
|
||||
return {
|
||||
...state,
|
||||
messages: state.messages.filter(message => message._id !== action.messageId)
|
||||
};
|
||||
case STARRED_MESSAGES.CLOSE:
|
||||
return initialState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -9,6 +9,8 @@ import createChannel from './createChannel';
|
|||
import init from './init';
|
||||
import state from './state';
|
||||
import activeUsers from './activeUsers';
|
||||
import starredMessages from './starredMessages';
|
||||
import pinnedMessages from './pinnedMessages';
|
||||
|
||||
const root = function* root() {
|
||||
yield all([
|
||||
|
@ -21,7 +23,9 @@ const root = function* root() {
|
|||
messages(),
|
||||
selectServer(),
|
||||
state(),
|
||||
activeUsers()
|
||||
activeUsers(),
|
||||
starredMessages(),
|
||||
pinnedMessages()
|
||||
]);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { take, takeLatest } from 'redux-saga/effects';
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
||||
const watchPinnedMessagesRoom = function* watchPinnedMessagesRoom({ rid }) {
|
||||
const sub = yield RocketChat.subscribe('pinnedMessages', rid, 50);
|
||||
yield take(types.PINNED_MESSAGES.CLOSE);
|
||||
sub.unsubscribe().catch(e => alert(e));
|
||||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeLatest(types.PINNED_MESSAGES.OPEN, watchPinnedMessagesRoom);
|
||||
};
|
||||
export default root;
|
|
@ -0,0 +1,14 @@
|
|||
import { take, takeLatest } from 'redux-saga/effects';
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
||||
const watchStarredMessagesRoom = function* watchStarredMessagesRoom({ rid }) {
|
||||
const sub = yield RocketChat.subscribe('starredMessages', rid, 50);
|
||||
yield take(types.STARRED_MESSAGES.CLOSE);
|
||||
sub.unsubscribe().catch(e => alert(e));
|
||||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeLatest(types.STARRED_MESSAGES.OPEN, watchStarredMessagesRoom);
|
||||
};
|
||||
export default root;
|
|
@ -0,0 +1,111 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, Text, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
|
||||
import { openPinnedMessages, closePinnedMessages } from '../../actions/pinnedMessages';
|
||||
import styles from './styles';
|
||||
import Message from '../../containers/message';
|
||||
import { togglePinRequest } from '../../actions/messages';
|
||||
|
||||
const PIN_INDEX = 0;
|
||||
const CANCEL_INDEX = 1;
|
||||
const options = ['Unpin', 'Cancel'];
|
||||
|
||||
@connect(
|
||||
state => ({
|
||||
messages: state.pinnedMessages.messages,
|
||||
user: state.login.user,
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||
}),
|
||||
dispatch => ({
|
||||
openPinnedMessages: rid => dispatch(openPinnedMessages(rid)),
|
||||
closePinnedMessages: () => dispatch(closePinnedMessages()),
|
||||
togglePinRequest: message => dispatch(togglePinRequest(message))
|
||||
})
|
||||
)
|
||||
export default class PinnedMessagesView extends React.PureComponent {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
messages: PropTypes.array,
|
||||
user: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
openPinnedMessages: PropTypes.func,
|
||||
closePinnedMessages: PropTypes.func,
|
||||
togglePinRequest: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
message: {}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.openPinnedMessages(this.props.navigation.state.params.rid);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.closePinnedMessages();
|
||||
}
|
||||
|
||||
onLongPress = (message) => {
|
||||
this.setState({ message });
|
||||
this.actionSheet.show();
|
||||
}
|
||||
|
||||
handleActionPress = (actionIndex) => {
|
||||
switch (actionIndex) {
|
||||
case PIN_INDEX:
|
||||
this.props.togglePinRequest(this.state.message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
renderEmpty = () => (
|
||||
<View style={styles.listEmptyContainer}>
|
||||
<Text>No pinned messages</Text>
|
||||
</View>
|
||||
)
|
||||
|
||||
renderItem = ({ item }) => (
|
||||
<Message
|
||||
item={item}
|
||||
style={styles.message}
|
||||
reactions={item.reactions}
|
||||
user={this.props.user}
|
||||
baseUrl={this.props.baseUrl}
|
||||
Message_TimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||
onLongPress={this.onLongPress}
|
||||
/>
|
||||
)
|
||||
|
||||
render() {
|
||||
if (this.props.messages.length === 0) {
|
||||
return this.renderEmpty();
|
||||
}
|
||||
return (
|
||||
[
|
||||
<FlatList
|
||||
key='pinned-messages-view-list'
|
||||
data={this.props.messages}
|
||||
renderItem={this.renderItem}
|
||||
style={styles.list}
|
||||
keyExtractor={item => item._id}
|
||||
/>,
|
||||
<ActionSheet
|
||||
key='pinned-messages-view-action-sheet'
|
||||
ref={o => this.actionSheet = o}
|
||||
title='Actions'
|
||||
options={options}
|
||||
cancelButtonIndex={CANCEL_INDEX}
|
||||
onPress={this.handleActionPress}
|
||||
/>
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
export default StyleSheet.create({
|
||||
list: {
|
||||
flex: 1,
|
||||
backgroundColor: '#ffffff'
|
||||
},
|
||||
message: {
|
||||
transform: [{ scaleY: 1 }]
|
||||
},
|
||||
listEmptyContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#ffffff'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,175 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, SectionList, Text, StyleSheet } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import styles from './styles';
|
||||
import Avatar from '../../containers/Avatar';
|
||||
import Touch from '../../utils/touch';
|
||||
import database from '../../lib/realm';
|
||||
|
||||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||
}))
|
||||
export default class RoomActionsView extends React.PureComponent {
|
||||
static propTypes = {
|
||||
baseUrl: PropTypes.string,
|
||||
navigation: PropTypes.object
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { rid } = props.navigation.state.params;
|
||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
this.state = {
|
||||
sections: [],
|
||||
room: {}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.updateRoom();
|
||||
this.updateSections();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.rooms.addListener(this.updateRoom);
|
||||
}
|
||||
|
||||
updateRoom = () => {
|
||||
const [room] = this.rooms;
|
||||
this.setState({ room });
|
||||
this.props.navigation.setParams({
|
||||
f: room.f
|
||||
});
|
||||
this.updateSections();
|
||||
}
|
||||
|
||||
updateSections = () => {
|
||||
const { rid, t } = this.state.room;
|
||||
const sections = [{
|
||||
data: [{ icon: 'ios-star', name: 'USER' }],
|
||||
renderItem: this.renderRoomInfo
|
||||
}, {
|
||||
data: [
|
||||
{ icon: 'ios-call-outline', name: 'Voice call' },
|
||||
{ icon: 'ios-videocam-outline', name: 'Video call' }
|
||||
],
|
||||
renderItem: this.renderItem
|
||||
}, {
|
||||
data: [
|
||||
{ icon: 'ios-attach', name: 'Files' },
|
||||
{ icon: 'ios-at-outline', name: 'Mentions' },
|
||||
{
|
||||
icon: 'ios-star-outline',
|
||||
name: 'Starred',
|
||||
route: 'StarredMessages',
|
||||
params: { rid }
|
||||
},
|
||||
{ icon: 'ios-search', name: 'Search' },
|
||||
{ icon: 'ios-share-outline', name: 'Share' },
|
||||
{
|
||||
icon: 'ios-pin',
|
||||
name: 'Pinned',
|
||||
route: 'PinnedMessages',
|
||||
params: { rid }
|
||||
},
|
||||
{ icon: 'ios-code', name: 'Snippets' },
|
||||
{ icon: 'ios-notifications-outline', name: 'Notifications preferences' }
|
||||
],
|
||||
renderItem: this.renderItem
|
||||
}];
|
||||
if (t === 'd') {
|
||||
sections.push({
|
||||
data: [
|
||||
{ icon: 'ios-volume-off', name: 'Mute user' },
|
||||
{ icon: 'block', name: 'Block user', type: 'danger' }
|
||||
],
|
||||
renderItem: this.renderItem
|
||||
});
|
||||
} else if (t === 'c' || t === 'p') {
|
||||
sections[2].data.unshift({ icon: 'ios-people', name: 'Members', description: '42 members' });
|
||||
sections.push({
|
||||
data: [
|
||||
{ icon: 'ios-volume-off', name: 'Mute channel' },
|
||||
{ icon: 'block', name: 'Leave channel', type: 'danger' }
|
||||
],
|
||||
renderItem: this.renderItem
|
||||
});
|
||||
}
|
||||
this.setState({ sections });
|
||||
}
|
||||
|
||||
renderRoomInfo = ({ item }) => this.renderTouchableItem([
|
||||
<Avatar
|
||||
key='avatar'
|
||||
text={this.state.room.name}
|
||||
size={50}
|
||||
style={StyleSheet.flatten(styles.avatar)}
|
||||
baseUrl={this.props.baseUrl}
|
||||
type={this.state.room.t}
|
||||
/>,
|
||||
<View key='name' style={styles.roomTitleContainer}>
|
||||
<Text style={styles.roomTitle}>{this.state.room.fname}</Text>
|
||||
<Text style={styles.roomDescription}>@{this.state.room.name}</Text>
|
||||
</View>,
|
||||
<Icon key='icon' name='ios-arrow-forward' size={20} style={styles.sectionItemIcon} color='#cbced1' />
|
||||
], item)
|
||||
|
||||
renderTouchableItem = (subview, item) => (
|
||||
<Touch
|
||||
onPress={() => item.route && this.props.navigation.navigate(item.route, item.params)}
|
||||
underlayColor='#FFFFFF'
|
||||
activeOpacity={0.5}
|
||||
accessibilityLabel={item.name}
|
||||
accessibilityTraits='button'
|
||||
>
|
||||
<View style={styles.sectionItem}>
|
||||
{subview}
|
||||
</View>
|
||||
</Touch>
|
||||
)
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
if (item.type === 'danger') {
|
||||
const subview = [
|
||||
<MaterialIcon key='icon' name={item.icon} size={20} style={[styles.sectionItemIcon, styles.textColorDanger]} />,
|
||||
<Text key='name' style={[styles.sectionItemName, styles.textColorDanger]}>{ item.name }</Text>
|
||||
];
|
||||
return this.renderTouchableItem(subview, item);
|
||||
}
|
||||
const subview = [
|
||||
<Icon key='left-icon' name={item.icon} size={24} style={styles.sectionItemIcon} />,
|
||||
<Text key='name' style={styles.sectionItemName}>{ item.name }</Text>,
|
||||
item.description && <Text key='description' style={styles.sectionItemDescription}>{ item.description }</Text>,
|
||||
<Icon key='right-icon' name='ios-arrow-forward' size={20} style={styles.sectionItemIcon} color='#cbced1' />
|
||||
];
|
||||
return this.renderTouchableItem(subview, item);
|
||||
}
|
||||
|
||||
renderSectionSeparator = (data) => {
|
||||
if (!data.trailingItem) {
|
||||
if (!data.trailingSection) {
|
||||
return <View style={styles.sectionSeparatorBorder} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<View style={[styles.sectionSeparator, data.leadingSection && styles.sectionSeparatorBorder]} />
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SectionList
|
||||
style={styles.container}
|
||||
stickySectionHeadersEnabled={false}
|
||||
sections={this.state.sections}
|
||||
SectionSeparatorComponent={this.renderSectionSeparator}
|
||||
keyExtractor={(item, index) => index}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: '#F6F7F9'
|
||||
},
|
||||
headerButton: {
|
||||
backgroundColor: 'transparent',
|
||||
height: 44,
|
||||
width: 44,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
sectionItem: {
|
||||
backgroundColor: '#ffffff',
|
||||
paddingVertical: 10,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
sectionItemIcon: {
|
||||
width: 45,
|
||||
textAlign: 'center'
|
||||
},
|
||||
sectionItemName: {
|
||||
flex: 1
|
||||
},
|
||||
sectionItemDescription: {
|
||||
color: '#cbced1'
|
||||
},
|
||||
sectionSeparator: {
|
||||
height: 10,
|
||||
backgroundColor: '#F6F7F9'
|
||||
},
|
||||
sectionSeparatorBorder: {
|
||||
borderColor: '#EBEDF1',
|
||||
borderTopWidth: 1
|
||||
},
|
||||
textColorDanger: {
|
||||
color: '#f5455c'
|
||||
},
|
||||
avatar: {
|
||||
marginHorizontal: 10
|
||||
},
|
||||
roomTitleContainer: {
|
||||
flex: 1
|
||||
},
|
||||
roomTitle: {
|
||||
fontSize: 16
|
||||
},
|
||||
roomDescription: {
|
||||
fontSize: 12,
|
||||
color: '#cbced1'
|
||||
}
|
||||
});
|
|
@ -5,11 +5,14 @@ import PropTypes from 'prop-types';
|
|||
import { connect } from 'react-redux';
|
||||
import { HeaderBackButton } from 'react-navigation';
|
||||
|
||||
import RocketChat from '../../../lib/rocketchat';
|
||||
import realm from '../../../lib/realm';
|
||||
import Avatar from '../../../containers/Avatar';
|
||||
import { STATUS_COLORS } from '../../../constants/colors';
|
||||
import styles from './styles';
|
||||
import { closeRoom } from '../../../actions/room';
|
||||
import Touch from '../../../utils/touch';
|
||||
|
||||
|
||||
@connect(state => ({
|
||||
user: state.login.user,
|
||||
|
@ -108,9 +111,25 @@ export default class RoomHeaderView extends React.PureComponent {
|
|||
|
||||
renderRight = () => (
|
||||
<View style={styles.right}>
|
||||
<Touch
|
||||
onPress={() => RocketChat.toggleFavorite(this.room[0].rid, this.room[0].f)}
|
||||
accessibilityLabel='Star room'
|
||||
accessibilityTraits='button'
|
||||
underlayColor='#FFFFFF'
|
||||
activeOpacity={0.5}
|
||||
>
|
||||
<View style={styles.headerButton}>
|
||||
<Icon
|
||||
name={`${ Platform.OS === 'ios' ? 'ios' : 'md' }-star${ this.room[0].f ? '' : '-outline' }`}
|
||||
color='#f6c502'
|
||||
size={24}
|
||||
backgroundColor='transparent'
|
||||
/>
|
||||
</View>
|
||||
</Touch>
|
||||
<TouchableOpacity
|
||||
style={styles.headerButton}
|
||||
onPress={() => {}}
|
||||
onPress={() => this.props.navigation.navigate('RoomActions', { rid: this.room[0].rid })}
|
||||
accessibilityLabel='Room actions'
|
||||
accessibilityTraits='button'
|
||||
>
|
||||
|
|
|
@ -42,7 +42,7 @@ export default StyleSheet.create({
|
|||
headerButton: {
|
||||
backgroundColor: 'transparent',
|
||||
height: 44,
|
||||
width: 44,
|
||||
width: 40,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import equal from 'deep-equal';
|
|||
import { List } from './ListView';
|
||||
import * as actions from '../../actions';
|
||||
import { openRoom, setLastOpen } from '../../actions/room';
|
||||
import { editCancel, toggleReactionPicker } from '../../actions/messages';
|
||||
import { editCancel, toggleReactionPicker, actionsShow } from '../../actions/messages';
|
||||
import database from '../../lib/realm';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import Message from '../../containers/message';
|
||||
|
@ -35,7 +35,8 @@ import styles from './styles';
|
|||
openRoom: room => dispatch(openRoom(room)),
|
||||
editCancel: () => dispatch(editCancel()),
|
||||
setLastOpen: date => dispatch(setLastOpen(date)),
|
||||
toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
|
||||
toggleReactionPicker: message => dispatch(toggleReactionPicker(message)),
|
||||
actionsShow: actionMessage => dispatch(actionsShow(actionMessage))
|
||||
})
|
||||
)
|
||||
export default class RoomView extends React.Component {
|
||||
|
@ -51,8 +52,9 @@ export default class RoomView extends React.Component {
|
|||
Message_TimeFormat: PropTypes.string,
|
||||
loading: PropTypes.bool,
|
||||
actionMessage: PropTypes.object,
|
||||
toggleReactionPicker: PropTypes.func.isRequired
|
||||
// layoutAnimation: PropTypes.instanceOf(Date)
|
||||
toggleReactionPicker: PropTypes.func.isRequired,
|
||||
// layoutAnimation: PropTypes.instanceOf(Date),
|
||||
actionsShow: PropTypes.func
|
||||
};
|
||||
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
|
@ -124,6 +126,10 @@ export default class RoomView extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
onMessageLongPress = (message) => {
|
||||
this.props.actionsShow(message);
|
||||
}
|
||||
|
||||
onReactionPress = (shortname, messageId) => {
|
||||
if (!messageId) {
|
||||
RocketChat.setReaction(shortname, this.props.actionMessage._id);
|
||||
|
@ -158,6 +164,7 @@ export default class RoomView extends React.Component {
|
|||
Message_TimeFormat={this.props.Message_TimeFormat}
|
||||
user={this.props.user}
|
||||
onReactionPress={this.onReactionPress}
|
||||
onLongPress={this.onMessageLongPress}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, Text, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
|
||||
import { openStarredMessages, closeStarredMessages } from '../../actions/starredMessages';
|
||||
import styles from './styles';
|
||||
import Message from '../../containers/message';
|
||||
import { toggleStarRequest } from '../../actions/messages';
|
||||
|
||||
const STAR_INDEX = 0;
|
||||
const CANCEL_INDEX = 1;
|
||||
const options = ['Unstar', 'Cancel'];
|
||||
|
||||
@connect(
|
||||
state => ({
|
||||
messages: state.starredMessages.messages,
|
||||
user: state.login.user,
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||
}),
|
||||
dispatch => ({
|
||||
openStarredMessages: rid => dispatch(openStarredMessages(rid)),
|
||||
closeStarredMessages: () => dispatch(closeStarredMessages()),
|
||||
toggleStarRequest: message => dispatch(toggleStarRequest(message))
|
||||
})
|
||||
)
|
||||
export default class StarredMessagesView extends React.PureComponent {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
messages: PropTypes.array,
|
||||
user: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
openStarredMessages: PropTypes.func,
|
||||
closeStarredMessages: PropTypes.func,
|
||||
toggleStarRequest: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
message: {}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.openStarredMessages(this.props.navigation.state.params.rid);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.closeStarredMessages();
|
||||
}
|
||||
|
||||
onLongPress = (message) => {
|
||||
this.setState({ message });
|
||||
this.actionSheet.show();
|
||||
}
|
||||
|
||||
handleActionPress = (actionIndex) => {
|
||||
switch (actionIndex) {
|
||||
case STAR_INDEX:
|
||||
this.props.toggleStarRequest(this.state.message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
renderEmpty = () => (
|
||||
<View style={styles.listEmptyContainer}>
|
||||
<Text>No starred messages</Text>
|
||||
</View>
|
||||
)
|
||||
|
||||
renderItem = ({ item }) => (
|
||||
<Message
|
||||
item={item}
|
||||
style={styles.message}
|
||||
reactions={item.reactions}
|
||||
user={this.props.user}
|
||||
baseUrl={this.props.baseUrl}
|
||||
Message_TimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||
onLongPress={this.onLongPress}
|
||||
/>
|
||||
)
|
||||
|
||||
render() {
|
||||
if (this.props.messages.length === 0) {
|
||||
return this.renderEmpty();
|
||||
}
|
||||
return (
|
||||
[
|
||||
<FlatList
|
||||
key='starred-messages-view-list'
|
||||
data={this.props.messages}
|
||||
renderItem={this.renderItem}
|
||||
style={styles.list}
|
||||
keyExtractor={item => item._id}
|
||||
/>,
|
||||
<ActionSheet
|
||||
key='starred-messages-view-action-sheet'
|
||||
ref={o => this.actionSheet = o}
|
||||
title='Actions'
|
||||
options={options}
|
||||
cancelButtonIndex={CANCEL_INDEX}
|
||||
onPress={this.handleActionPress}
|
||||
/>
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
export default StyleSheet.create({
|
||||
list: {
|
||||
flex: 1,
|
||||
backgroundColor: '#ffffff'
|
||||
},
|
||||
message: {
|
||||
transform: [{ scaleY: 1 }]
|
||||
},
|
||||
listEmptyContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#ffffff'
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue