Show imagem attachments (#20)
* attach and offline search * Update serverNew.js
This commit is contained in:
parent
94bcab0d76
commit
2d9bbb87ae
|
@ -7,8 +7,12 @@ import Markdown from 'react-native-easy-markdown';
|
|||
import moment from 'moment';
|
||||
|
||||
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
|
||||
import Card from './message/card';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
content: {
|
||||
flexGrow: 1
|
||||
},
|
||||
message: {
|
||||
padding: 12,
|
||||
paddingTop: 6,
|
||||
|
@ -68,7 +72,9 @@ export default class Message extends React.PureComponent {
|
|||
baseUrl: PropTypes.string.isRequired,
|
||||
Message_TimeFormat: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
attachments() {
|
||||
return this.props.item.attachments.length ? <Card data={this.props.item.attachments[0]} /> : null;
|
||||
}
|
||||
render() {
|
||||
const { item } = this.props;
|
||||
|
||||
|
@ -102,13 +108,14 @@ export default class Message extends React.PureComponent {
|
|||
<Text style={styles.avatarInitials}>{initials}</Text>
|
||||
<CachedImage style={styles.avatar} source={{ uri: avatar }} />
|
||||
</View>
|
||||
<View style={styles.texts}>
|
||||
<View style={[styles.content]}>
|
||||
<View style={styles.usernameView}>
|
||||
<Text onPress={this._onPress} style={styles.username}>
|
||||
{username}
|
||||
</Text>
|
||||
{aliasUsername}<Text style={styles.time}>{time}</Text>
|
||||
</View>
|
||||
{this.attachments()}
|
||||
<Markdown>
|
||||
{msg}
|
||||
</Markdown>
|
||||
|
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
|||
import { View, TextInput, StyleSheet } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
import RNFetchBlob from 'react-native-fetch-blob';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -63,7 +62,7 @@ export default class MessageBox extends React.PureComponent {
|
|||
};
|
||||
|
||||
ImagePicker.showImagePicker(options, (response) => {
|
||||
console.log('Response = ', response);
|
||||
// console.log('Response = ', response);
|
||||
|
||||
if (response.didCancel) {
|
||||
console.log('User cancelled image picker');
|
||||
|
@ -76,29 +75,10 @@ export default class MessageBox extends React.PureComponent {
|
|||
name: response.fileName,
|
||||
size: response.fileSize,
|
||||
type: response.type || 'image/jpeg',
|
||||
rid: this.props.rid,
|
||||
// description: '',
|
||||
store: 'Uploads'
|
||||
};
|
||||
|
||||
RocketChat.ufsCreate(fileInfo).then((result) => {
|
||||
RNFetchBlob.fetch('POST', result.url, {
|
||||
'Content-Type': 'application/octet-stream'
|
||||
}, response.data).then(() => {
|
||||
RocketChat.ufsComplete(result.fileId, fileInfo.store, result.token)
|
||||
.then((completeRresult) => {
|
||||
RocketChat.sendFileMessage(completeRresult.rid, {
|
||||
_id: completeRresult._id,
|
||||
type: completeRresult.type,
|
||||
size: completeRresult.size,
|
||||
name: completeRresult.name,
|
||||
url: completeRresult.path
|
||||
});
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.log({ err });
|
||||
});
|
||||
});
|
||||
RocketChat.sendFileMessage(this.props.rid, fileInfo, response.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Meteor from 'react-native-meteor';
|
||||
import { CachedImage } from 'react-native-img-cache';
|
||||
import { Text, TouchableOpacity } from 'react-native';
|
||||
import { Navigation } from 'react-native-navigation';
|
||||
import {
|
||||
Card,
|
||||
CardImage,
|
||||
// CardTitle,
|
||||
CardContent
|
||||
// CardAction
|
||||
} from 'react-native-card-view';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
|
||||
const close = () => Navigation.dismissModal({
|
||||
animationType: 'slide-down'
|
||||
});
|
||||
|
||||
const CustomButton = ({ text }) => (
|
||||
<TouchableOpacity onPress={close}>
|
||||
<Text style={{ color: 'blue' }}>{text}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
Navigation.registerComponent('CustomButton', () => CustomButton);
|
||||
export default class Cards extends React.PureComponent {
|
||||
static propTypes = {
|
||||
data: PropTypes.object.isRequired
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
const user = Meteor.user();
|
||||
this.state = {};
|
||||
RocketChat.getUserToken().then((token) => {
|
||||
this.setState({ img: `${ RocketChat.currentServer }${ this.props.data.image_url }?rc_uid=${ user._id }&rc_token=${ token }` });
|
||||
});
|
||||
}
|
||||
_onPressButton() {
|
||||
Navigation.showModal({
|
||||
screen: 'Photo',
|
||||
title: this.props.data.title, // title of the screen as appears in the nav bar (optional)
|
||||
passProps: { image: this.state.img },
|
||||
// navigatorStyle: {}, // override the navigator style for the screen, see "Styling the navigator" below (optional)
|
||||
navigatorButtons: {
|
||||
leftButtons: [{
|
||||
id: 'custom-button',
|
||||
component: 'CustomButton',
|
||||
passProps: {
|
||||
text: 'close'
|
||||
}
|
||||
}]
|
||||
}, // override the nav buttons for the screen, see "Adding buttons to the navigator" below (optional)
|
||||
animationType: 'slide-up' // 'none' / 'slide-up' , appear animation for the modal (optional, default 'slide-up')
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return this.state.img ? (
|
||||
<TouchableOpacity onPress={() => this._onPressButton()}>
|
||||
<Card>
|
||||
<CardImage style={{ width: 256, height: 256 }}>
|
||||
<CachedImage
|
||||
style={{ width: 256, height: 256 }}
|
||||
source={{ uri: encodeURI(this.state.img) }}
|
||||
/>
|
||||
</CardImage>
|
||||
<CardContent>
|
||||
<Text style={[{ fontSize: 12, alignSelf: 'center', fontStyle: 'italic' }]}>{this.props.data.title}</Text>
|
||||
<Text style={{ alignSelf: 'center', fontWeight: 'bold' }}>{this.props.data.description}</Text>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TouchableOpacity>
|
||||
) :
|
||||
<Text style={[{ fontSize: 12, alignSelf: 'center', fontStyle: 'italic' }]}>{this.props.data.title}</Text>;
|
||||
}
|
||||
}
|
|
@ -55,6 +55,26 @@ const usersSchema = {
|
|||
}
|
||||
};
|
||||
|
||||
const attachment = {
|
||||
name: 'attachment',
|
||||
properties: {
|
||||
description: { type: 'string', optional: true },
|
||||
|
||||
image_size: { type: 'int', optional: true },
|
||||
|
||||
image_type: { type: 'string', optional: true },
|
||||
|
||||
image_url: { type: 'string', optional: true },
|
||||
title: { type: 'string', optional: true },
|
||||
|
||||
title_link: { type: 'string', optional: true },
|
||||
title_link_download: { type: 'bool', optional: true },
|
||||
type: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
const messagesSchema = {
|
||||
name: 'messages',
|
||||
primaryKey: '_id',
|
||||
|
@ -71,6 +91,7 @@ const messagesSchema = {
|
|||
parseUrls: { type: 'bool', optional: true },
|
||||
groupable: { type: 'bool', optional: true },
|
||||
avatar: { type: 'string', optional: true },
|
||||
attachments: { type: 'list', objectType: 'attachment' },
|
||||
_updatedAt: { type: 'date', optional: true },
|
||||
temp: { type: 'bool', optional: true }
|
||||
}
|
||||
|
@ -136,9 +157,8 @@ const messagesSchema = {
|
|||
// Realm.clearTestState();
|
||||
|
||||
const realm = new Realm({
|
||||
schema: [settingsSchema, serversSchema, subscriptionSchema, messagesSchema, usersSchema]
|
||||
schema: [settingsSchema, serversSchema, subscriptionSchema, messagesSchema, usersSchema, attachment]
|
||||
});
|
||||
|
||||
export default realm;
|
||||
|
||||
// realm.write(() => {
|
||||
|
|
|
@ -3,11 +3,13 @@ import Random from 'react-native-meteor/lib/Random';
|
|||
import { AsyncStorage } from 'react-native';
|
||||
import { hashPassword } from 'react-native-meteor/lib/utils';
|
||||
|
||||
import RNFetchBlob from 'react-native-fetch-blob';
|
||||
import reduxStore from '../lib/createStore';
|
||||
import settingsType from '../constants/settings';
|
||||
import realm from './realm';
|
||||
import * as actions from '../actions';
|
||||
|
||||
|
||||
export { Accounts } from 'react-native-meteor';
|
||||
|
||||
const call = (method, ...params) => new Promise((resolve, reject) => {
|
||||
|
@ -220,47 +222,32 @@ const RocketChat = {
|
|||
Meteor.subscribe('stream-room-messages', rid, false);
|
||||
},
|
||||
|
||||
sendMessage(rid, msg) {
|
||||
getMessage(rid, msg = {}) {
|
||||
const _id = Random.id();
|
||||
const user = Meteor.user();
|
||||
const message = {
|
||||
_id,
|
||||
rid,
|
||||
msg,
|
||||
ts: new Date(),
|
||||
_updatedAt: new Date(),
|
||||
temp: true,
|
||||
_server: { id: RocketChat.currentServer },
|
||||
u: {
|
||||
_id: user._id,
|
||||
username: user.username
|
||||
}
|
||||
};
|
||||
|
||||
realm.write(() => {
|
||||
// write('messages', {
|
||||
// _id,
|
||||
// rid,
|
||||
// msg,
|
||||
// ts: new Date(),
|
||||
// _updatedAt: new Date(),
|
||||
// temp: true,
|
||||
// _server: { id: RocketChat.currentServer },
|
||||
// u: {
|
||||
// _id: user._id,
|
||||
// username: user.username
|
||||
// }
|
||||
// });
|
||||
realm.create('messages', {
|
||||
_id,
|
||||
rid,
|
||||
msg,
|
||||
ts: new Date(),
|
||||
_updatedAt: new Date(),
|
||||
temp: true,
|
||||
_server: { id: RocketChat.currentServer },
|
||||
u: {
|
||||
_id: user._id,
|
||||
username: user.username
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
Meteor.call('sendMessage', { _id, rid, msg }, (error, result) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
return resolve(result);
|
||||
});
|
||||
realm.create('messages', message, true);
|
||||
// write('messages', message, true);
|
||||
});
|
||||
return message;
|
||||
},
|
||||
sendMessage(rid, msg) {
|
||||
const tempMessage = this.getMessage(rid, msg);
|
||||
return call('sendMessage', { _id: tempMessage._id, rid, msg });
|
||||
},
|
||||
|
||||
spotlight(search, usernames) {
|
||||
|
@ -307,7 +294,8 @@ const RocketChat = {
|
|||
"description":""
|
||||
"store":"fileSystem"
|
||||
*/
|
||||
ufsCreate(fileInfo) {
|
||||
_ufsCreate(fileInfo) {
|
||||
// return call('ufsCreate', fileInfo);
|
||||
return new Promise((resolve, reject) => {
|
||||
Meteor.call('ufsCreate', fileInfo, (error, result) => {
|
||||
if (error) {
|
||||
|
@ -319,7 +307,7 @@ const RocketChat = {
|
|||
},
|
||||
|
||||
// ["ZTE8CKHJt7LATv7Me","fileSystem","e8E96b2819"
|
||||
ufsComplete(fileId, store, token) {
|
||||
_ufsComplete(fileId, store, token) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Meteor.call('ufsComplete', fileId, store, token, (error, result) => {
|
||||
if (error) {
|
||||
|
@ -340,9 +328,9 @@ const RocketChat = {
|
|||
"url":"/ufs/fileSystem/ZTE8CKHJt7LATv7Me/yXfExLErmNR5eNPx7.png"
|
||||
}
|
||||
*/
|
||||
sendFileMessage(rid, message) {
|
||||
_sendFileMessage(rid, data, msg = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Meteor.call('sendFileMessage', rid, null, message, (error, result) => {
|
||||
Meteor.call('sendFileMessage', rid, null, data, msg, (error, result) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
@ -350,6 +338,33 @@ const RocketChat = {
|
|||
});
|
||||
});
|
||||
},
|
||||
async sendFileMessage(rid, fileInfo, data) {
|
||||
const placeholder = RocketChat.getMessage(rid, 'Sending an image');
|
||||
try {
|
||||
const result = await RocketChat._ufsCreate({ ...fileInfo, rid });
|
||||
|
||||
await RNFetchBlob.fetch('POST', result.url, {
|
||||
'Content-Type': 'application/octet-stream'
|
||||
}, data);
|
||||
|
||||
const completeRresult = await RocketChat._ufsComplete(result.fileId, fileInfo.store, result.token);
|
||||
|
||||
return await RocketChat._sendFileMessage(completeRresult.rid, {
|
||||
_id: completeRresult._id,
|
||||
type: completeRresult.type,
|
||||
size: completeRresult.size,
|
||||
name: completeRresult.name,
|
||||
url: completeRresult.path
|
||||
});
|
||||
} catch (e) {
|
||||
return e;
|
||||
} finally {
|
||||
realm.write(() => {
|
||||
const msg = realm.objects('messages').filtered('_id = $0', placeholder._id);
|
||||
realm.delete(msg);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
logout() {
|
||||
return AsyncStorage.clear();
|
||||
|
|
|
@ -6,11 +6,13 @@ import NewServerView from './views/serverNew';
|
|||
import ListServerView from './views/serverList';
|
||||
import RoomsListView from './views/roomsList';
|
||||
import RoomView from './views/room';
|
||||
import PhotoView from './views/Photo';
|
||||
import CreateChannel from './views/CreateChannel';
|
||||
import store from './lib/createStore';
|
||||
|
||||
Navigation.registerComponent('Rooms', () => RoomsListView, store, Provider);
|
||||
Navigation.registerComponent('Room', () => RoomView, store, Provider);
|
||||
Navigation.registerComponent('Photo', () => PhotoView, store, Provider);
|
||||
Navigation.registerComponent('ListServer', () => ListServerView, store, Provider);
|
||||
Navigation.registerComponent('Login', () => LoginView, store, Provider);
|
||||
Navigation.registerComponent('NewServer', () => NewServerView, store, Provider);
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import { ScrollView, View } from 'react-native';
|
||||
import { CachedImage } from 'react-native-img-cache';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const styles = {
|
||||
imageWrapper: {
|
||||
flex: 1,
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: '#000'
|
||||
},
|
||||
image: {
|
||||
flex: 1
|
||||
}
|
||||
};
|
||||
|
||||
export default class extends React.PureComponent {
|
||||
static propTypes = {
|
||||
image: PropTypes.string.isRequired
|
||||
}
|
||||
render() {
|
||||
const { image } = this.props;
|
||||
return (
|
||||
<View style={styles.imageWrapper}>
|
||||
<ScrollView contentContainerStyle={styles.imageWrapper} maximumZoomScale={1.5}>
|
||||
<CachedImage
|
||||
style={{ ...styles.image }}
|
||||
source={{ uri: encodeURI(image) }}
|
||||
mutable
|
||||
resizeMode={'contain'}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -209,6 +209,7 @@ export default class RoomView extends React.Component {
|
|||
onEndReached={this.onEndReached}
|
||||
dataSource={this.state.dataSource}
|
||||
renderRow={item => this.renderItem({ item })}
|
||||
initialListSize={10}
|
||||
/>
|
||||
{this.renderFooter()}
|
||||
</KeyboardView>
|
||||
|
|
|
@ -174,53 +174,56 @@ export default class RoomsListView extends React.Component {
|
|||
searchText: text,
|
||||
searching: searchText !== ''
|
||||
});
|
||||
|
||||
if (searchText !== '') {
|
||||
const data = this.state.data.filtered('name CONTAINS[c] $0', searchText).slice();
|
||||
|
||||
const dataSource = [];
|
||||
const usernames = [];
|
||||
data.forEach((sub) => {
|
||||
dataSource.push(sub);
|
||||
|
||||
if (sub.t === 'd') {
|
||||
usernames.push(sub.name);
|
||||
}
|
||||
});
|
||||
|
||||
if (dataSource.length < 7) {
|
||||
RocketChat.spotlight(searchText, usernames)
|
||||
.then((results) => {
|
||||
results.users.forEach((user) => {
|
||||
dataSource.push({
|
||||
...user,
|
||||
name: user.username,
|
||||
t: 'd',
|
||||
search: true
|
||||
});
|
||||
});
|
||||
|
||||
results.rooms.forEach((room) => {
|
||||
dataSource.push({
|
||||
...room,
|
||||
search: true
|
||||
});
|
||||
});
|
||||
|
||||
this.setState({
|
||||
dataSource: ds.cloneWithRows(dataSource)
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
dataSource: ds.cloneWithRows(dataSource)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
if (searchText === '') {
|
||||
return this.setState({
|
||||
dataSource: ds.cloneWithRows(this.state.data)
|
||||
});
|
||||
}
|
||||
|
||||
const data = this.state.data.filtered('name CONTAINS[c] $0', searchText).slice();
|
||||
|
||||
const usernames = [];
|
||||
const dataSource = data.map((sub) => {
|
||||
if (sub.t === 'd') {
|
||||
usernames.push(sub.name);
|
||||
}
|
||||
return sub;
|
||||
});
|
||||
|
||||
if (dataSource.length < 7) {
|
||||
if (this.oldPromise) {
|
||||
this.oldPromise();
|
||||
}
|
||||
Promise.race([
|
||||
RocketChat.spotlight(searchText, usernames),
|
||||
new Promise((resolve, reject) => this.oldPromise = reject)
|
||||
])
|
||||
.then((results) => {
|
||||
results.users.forEach((user) => {
|
||||
dataSource.push({
|
||||
...user,
|
||||
name: user.username,
|
||||
t: 'd',
|
||||
search: true
|
||||
});
|
||||
});
|
||||
|
||||
results.rooms.forEach((room) => {
|
||||
dataSource.push({
|
||||
...room,
|
||||
search: true
|
||||
});
|
||||
});
|
||||
|
||||
this.setState({
|
||||
dataSource: ds.cloneWithRows(dataSource)
|
||||
});
|
||||
}, () => console.log('spotlight stopped'))
|
||||
.then(() => delete this.oldPromise);
|
||||
}
|
||||
this.setState({
|
||||
dataSource: ds.cloneWithRows(dataSource)
|
||||
});
|
||||
}
|
||||
|
||||
setInitialData = (props = this.props) => {
|
||||
|
|
|
@ -199,9 +199,7 @@ export default class NewServerView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
url = url.replace(/\/+$/, '');
|
||||
|
||||
return url;
|
||||
return url.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
renderValidation = () => {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,6 +21,7 @@
|
|||
"react-native-auto-grow-textinput": "^1.2.0",
|
||||
"react-native-autogrow-input": "^0.2.1",
|
||||
"react-native-autogrow-textinput": "^4.1.0",
|
||||
"react-native-card-view": "0.0.3",
|
||||
"react-native-console-time-polyfill": "0.0.6",
|
||||
"react-native-easy-markdown": "git+https://github.com/lappalj4/react-native-easy-markdown.git",
|
||||
"react-native-fetch-blob": "^0.10.8",
|
||||
|
|
Loading…
Reference in New Issue