Merge branch 'develop' into update-rn-60-2

# Conflicts:
#	app/views/ShareListView/index.js
#	ios/Podfile
#	ios/Podfile.lock
#	ios/Pods/Manifest.lock
#	ios/Pods/Pods.xcodeproj/project.pbxproj
#	ios/Pods/Target Support Files/Pods-RocketChatRN/Pods-RocketChatRN.debug.xcconfig
#	ios/Pods/Target Support Files/Pods-RocketChatRN/Pods-RocketChatRN.release.xcconfig
#	ios/Pods/Target Support Files/Pods-ShareRocketChatRN/Pods-ShareRocketChatRN-acknowledgements.markdown
#	ios/Pods/Target Support Files/Pods-ShareRocketChatRN/Pods-ShareRocketChatRN.debug.xcconfig
#	ios/Pods/Target Support Files/Pods-ShareRocketChatRN/Pods-ShareRocketChatRN.release.xcconfig
#	ios/Pods/Target Support Files/react-native-realm-path/react-native-realm-path.xcconfig
#	ios/Pods/Target Support Files/rn-extensions-share/rn-extensions-share.xcconfig
#	ios/RocketChatRN.xcodeproj/project.pbxproj
#	package.json
This commit is contained in:
diegolmello 2019-08-01 18:05:09 -03:00
commit ca93e333ad
66 changed files with 887 additions and 646 deletions

View File

@ -1,13 +1,11 @@
# Rocket.Chat React Native Mobile
[![Greenkeeper badge](https://badges.greenkeeper.io/RocketChat/Rocket.Chat.ReactNative.svg)](https://greenkeeper.io/)
[![Build Status](https://img.shields.io/travis/RocketChat/Rocket.Chat.ReactNative/master.svg)](https://travis-ci.org/RocketChat/Rocket.Chat.ReactNative)
[![Project Dependencies](https://david-dm.org/RocketChat/Rocket.Chat.ReactNative.svg)](https://david-dm.org/RocketChat/Rocket.Chat.ReactNative)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/bb15e2392a71473ea59d3f634f35c54e)](https://www.codacy.com/app/RocketChat/Rocket.Chat.ReactNative?utm_source=github.com&utm_medium=referral&utm_content=RocketChat/Rocket.Chat.ReactNative&utm_campaign=badger)
[![codecov](https://codecov.io/gh/RocketChat/Rocket.Chat.ReactNative/branch/master/graph/badge.svg)](https://codecov.io/gh/RocketChat/Rocket.Chat.ReactNative)
[![CodeFactor](https://www.codefactor.io/repository/github/rocketchat/rocket.chat.reactnative/badge)](https://www.codefactor.io/repository/github/rocketchat/rocket.chat.reactnative)
**Supported Server Versions:** 0.66.0+
**Supported Server Versions:** 0.70.0+
## Download
<a href="https://play.google.com/store/apps/details?id=chat.rocket.reactnative">
@ -59,55 +57,53 @@ If you don't need multiple servers, there is a branch `single-server` just for t
Readme will guide you on how to config.
## Current priorities
1) [NEW] Jitsi integration ([#711][i711])
2) [NEW] Federation ([#706][i706])
3) [NEW] Record video ([#712][i712])
4) [NEW] Slash Commands ([#405][i405])
5) [NEW] Share extension ([#391][i391])
[i711]: https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/711
[i706]: https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/706
[i707]: https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/707
[i712]: https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/712
[i708]: https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/708
[i391]: https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/391
[i405]: https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/405
1) Jitsi integration
2) Notification Preferences
3) Two-way authentication
4) Authentication via SAML
5) Authentication via Custom OAuth
6) Authentication via CAS
7) Bugsnag
8) Optional Analytics
9) Typescript
10) Prettier
## Features
| Feature | Status |
|--------------------------------------------------------------- |-------- |
| Jitsi Integration | ❌ |
| Federation (Directory) | ❌ |
| Threads | ✅ |
| Federation (Directory) | ✅ |
| Discussions | ❌ |
| Threads | ✅ |
| Record Audio | ✅ |
| Record Video | |
| Commands | |
| Record Video | |
| Commands | |
| Draft message per room | ✅ |
| Share Extension | |
| Share Extension | |
| Notifications Preferences | ✅ |
| Edited status | ✅ |
| Upload video | |
| Upload video | |
| Grouped messages | ✅ |
| Mark room as read | |
| Mark room as unread | |
| Mark room as read | |
| Mark room as unread | |
| Tablet Support | ❌ |
| Read receipt | |
| Read receipt | |
| Broadbast Channel | ✅ |
| Authentication via SAML | ❌ |
| Authentication via CAS | ❌ |
| Custom Fields on Signup | |
| Report message | |
| Custom Fields on Signup | |
| Report message | |
| Theming | ❌ |
| Settings -> Review the App | ❌ |
| Settings -> Default Browser | ❌ |
| Admin panel | ✅ |
| Reply message from notification | ❌ |
| Unread counter banner on message list | ✅ |
| E2E | ❌ |
| E2E Encryption | ❌ |
| Join a Protected Room | ❌ |
| Optional Analytics | ❌ |
| Settings -> About us | ❌ |
| Settings -> Contact us | |
| Settings -> Contact us | |
| Settings -> Update App Icon | ❌ |
| Settings -> Share | ❌ |
| Accessibility (Medium) | ❌ |

View File

@ -14,6 +14,11 @@ export const LOGIN = createRequestTypes('LOGIN', [
'SET_SERVICES',
'SET_PREFERENCE'
]);
export const SHARE = createRequestTypes('SHARE', [
'SELECT_SERVER',
'SET_USER',
'SET_SERVER_INFO'
]);
export const USER = createRequestTypes('USER', ['SET']);
export const ROOMS = createRequestTypes('ROOMS', [
...defaultTypes,

15
app/actions/share.js Normal file
View File

@ -0,0 +1,15 @@
import { SHARE } from './actionsTypes';
export function shareSelectServer(server) {
return {
type: SHARE.SELECT_SERVER,
server
};
}
export function shareSetUser(user) {
return {
type: SHARE.SET_USER,
user
};
}

View File

@ -5,6 +5,7 @@ import HeaderButtons, { HeaderButton, Item } from 'react-navigation-header-butto
import { CustomIcon } from '../lib/Icons';
import { isIOS } from '../utils/deviceInfo';
import { COLOR_PRIMARY, COLOR_WHITE } from '../constants/colors';
import I18n from '../i18n';
const color = isIOS ? COLOR_PRIMARY : COLOR_WHITE;
export const headerIconSize = 23;
@ -35,7 +36,7 @@ export const CloseModalButton = React.memo(({ navigation, testID }) => (
export const CloseShareExtensionButton = React.memo(({ onPress, testID }) => (
<CustomHeaderButtons left>
{isIOS
? <Item title='cancel' onPress={onPress} testID={testID} />
? <Item title={I18n.t('Cancel')} onPress={onPress} testID={testID} />
: <Item title='close' iconName='cross' onPress={onPress} testID={testID} />
}
</CustomHeaderButtons>

View File

@ -13,6 +13,7 @@ import Button from '../Button';
import I18n from '../../i18n';
import sharedStyles from '../../views/Styles';
import { isIOS } from '../../utils/deviceInfo';
import { canUploadFile } from '../../utils/media';
import {
COLOR_PRIMARY, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE, COLOR_DANGER
} from '../../constants/colors';
@ -166,8 +167,9 @@ export default class UploadModal extends Component {
if (file.size > FileUpload_MaxFileSize) {
return false;
}
// if white list is empty, all media types are enabled
if (!FileUpload_MediaTypeWhiteList) {
return false;
return true;
}
const allowedMime = FileUpload_MediaTypeWhiteList.split(',');
if (allowedMime.includes(file.mime)) {
@ -290,9 +292,11 @@ export default class UploadModal extends Component {
}
render() {
const { window: { width }, isVisible, close } = this.props;
const {
window: { width }, isVisible, close, file, FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize
} = this.props;
const { name, description } = this.state;
const showError = !this.canUploadFile();
const showError = !canUploadFile(file, { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize });
return (
<Modal
isVisible={isVisible}

View File

@ -489,7 +489,9 @@ class MessageBox extends Component {
}
sendMediaMessage = async(file) => {
const { rid, tmid } = this.props;
const {
rid, tmid, baseUrl: server, user
} = this.props;
this.setState({ file: { isVisible: false } });
const fileInfo = {
name: file.name,
@ -500,7 +502,7 @@ class MessageBox extends Component {
path: file.path
};
try {
await RocketChat.sendFileMessage(rid, fileInfo, tmid);
await RocketChat.sendFileMessage(rid, fileInfo, tmid, server, user);
} catch (e) {
log('err_send_media_message', e);
}
@ -602,14 +604,16 @@ class MessageBox extends Component {
}
finishAudioMessage = async(fileInfo) => {
const { rid, tmid } = this.props;
const {
rid, tmid, baseUrl: server, user
} = this.props;
this.setState({
recording: false
});
if (fileInfo) {
try {
await RocketChat.sendFileMessage(rid, fileInfo, tmid);
await RocketChat.sendFileMessage(rid, fileInfo, tmid, server, user);
} catch (e) {
if (e && e.error === 'error-file-too-large') {
return Alert.alert(I18n.t(e.error));

View File

@ -1,6 +1,9 @@
import React from 'react';
import { View, StyleSheet, TextInput } from 'react-native';
import {
View, StyleSheet, TextInput, Text
} from 'react-native';
import PropTypes from 'prop-types';
import Touchable from 'react-native-platform-touchable';
import I18n from '../i18n';
import { isIOS } from '../utils/deviceInfo';
@ -9,7 +12,10 @@ import sharedStyles from '../views/Styles';
const styles = StyleSheet.create({
container: {
backgroundColor: isIOS ? '#F7F8FA' : '#54585E'
backgroundColor: isIOS ? '#F7F8FA' : '#54585E',
flexDirection: 'row',
alignItems: 'center',
flex: 1
},
searchBox: {
alignItems: 'center',
@ -21,7 +27,8 @@ const styles = StyleSheet.create({
height: 36,
margin: 16,
marginVertical: 10,
paddingHorizontal: 10
paddingHorizontal: 10,
flex: 1
},
input: {
color: '#8E8E93',
@ -31,10 +38,26 @@ const styles = StyleSheet.create({
paddingTop: 0,
paddingBottom: 0,
...sharedStyles.textRegular
},
cancel: {
marginRight: 10
},
cancelText: {
...sharedStyles.textRegular,
...sharedStyles.textColorHeaderBack,
fontSize: 17
}
});
const SearchBox = ({ onChangeText, onSubmitEditing, testID }) => (
const CancelButton = onCancelPress => (
<Touchable onPress={onCancelPress} style={styles.cancel}>
<Text style={styles.cancelText}>{I18n.t('Cancel')}</Text>
</Touchable>
);
const SearchBox = ({
onChangeText, onSubmitEditing, testID, hasCancel, onCancelPress, ...props
}) => (
<View style={styles.container}>
<View style={styles.searchBox}>
<CustomIcon name='magnifier' size={14} color='#8E8E93' />
@ -50,14 +73,18 @@ const SearchBox = ({ onChangeText, onSubmitEditing, testID }) => (
underlineColorAndroid='transparent'
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
{...props}
/>
</View>
{ hasCancel ? CancelButton(onCancelPress) : null }
</View>
);
SearchBox.propTypes = {
onChangeText: PropTypes.func.isRequired,
onSubmitEditing: PropTypes.func,
hasCancel: PropTypes.bool,
onCancelPress: PropTypes.func,
testID: PropTypes.string
};

View File

@ -311,13 +311,13 @@ export default {
Search_global_users: 'Search for global users',
Search_global_users_description: 'If you turn-on, you can search for any user from others companies or servers.',
Select_Avatar: 'Select Avatar',
Select_Channels: 'Select Channels',
Select_Server: 'Select Server',
Select_Users: 'Select Users',
Send: 'Send',
Send_audio_message: 'Send audio message',
Send_crash_report: 'Send crash report',
Send_message: 'Send message',
Send_to: 'Send to...',
Sent_an_attachment: 'Sent an attachment',
Server: 'Server',
Servers: 'Servers',

View File

@ -302,12 +302,12 @@ export default {
Search_global_users: 'Busca por usuários globais',
Search_global_users_description: 'Caso ativado, busca por usuários de outras empresas ou servidores.',
Select_Avatar: 'Selecionar Avatar',
Select_Channels: 'Selecionar Canais',
Select_Server: 'Selecionar Servidor',
Select_Users: 'Selecionar Usuários',
Send: 'Enviar',
Send_audio_message: 'Enviar mensagem de áudio',
Send_message: 'Enviar mensagem',
Send_to: 'Enviar para...',
Sent_an_attachment: 'Enviou um anexo',
Server: 'Servidor',
Set_username_subtitle: 'O usuário é utilizado para permitir que você seja mencionado em mensagens',
@ -383,5 +383,6 @@ export default {
you_were_mentioned: 'você foi mencionado',
you: 'você',
You: 'Você',
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Você precisa acessar ao menos um servidor Rocket.Chat para compartilhar.',
You_will_not_be_able_to_recover_this_message: 'Você não será capaz de recuperar essa mensagem!'
};

View File

@ -0,0 +1,21 @@
import { NavigationActions } from 'react-navigation';
let _shareNavigator;
function setTopLevelNavigator(navigatorRef) {
_shareNavigator = navigatorRef;
}
function navigate(routeName, params) {
_shareNavigator.dispatch(
NavigationActions.navigate({
routeName,
params
})
);
}
export default {
navigate,
setTopLevelNavigator
};

View File

@ -40,6 +40,15 @@ export default async function() {
if (setting._id === 'Site_Name') {
updateServer.call(this, { name: setting.valueAsString });
}
if (setting._id === 'UI_Use_Real_Name') {
updateServer.call(this, { useRealName: setting.valueAsBoolean });
}
if (setting._id === 'FileUpload_MediaTypeWhiteList') {
updateServer.call(this, { FileUpload_MediaTypeWhiteList: setting.valueAsString });
}
if (setting._id === 'FileUpload_MaxFileSize') {
updateServer.call(this, { FileUpload_MaxFileSize: setting.valueAsNumber });
}
})
)
);

View File

@ -1,4 +1,3 @@
import reduxStore from '../createStore';
import database from '../realm';
import log from '../../utils/log';
@ -23,11 +22,12 @@ export function cancelUpload(path) {
}
}
export function sendFileMessage(rid, fileInfo, tmid) {
export function sendFileMessage(rid, fileInfo, tmid, server, user) {
return new Promise((resolve, reject) => {
try {
const { FileUpload_MaxFileSize, Site_Url } = reduxStore.getState().settings;
const { id, token } = reduxStore.getState().login.user;
const { serversDB } = database.databases;
const { FileUpload_MaxFileSize, id: Site_Url } = serversDB.objectForPrimaryKey('servers', server);
const { id, token } = user;
// -1 maxFileSize means there is no limit
if (FileUpload_MaxFileSize > -1 && fileInfo.size > FileUpload_MaxFileSize) {

View File

@ -1,12 +1,12 @@
import messagesStatus from '../../constants/messagesStatus';
import buildMessage from './helpers/buildMessage';
import database from '../realm';
import reduxStore from '../createStore';
import log from '../../utils/log';
import random from '../../utils/random';
export const getMessage = (rid, msg = '', tmid) => {
export const getMessage = (rid, msg = '', tmid, user) => {
const _id = random(17);
const { id, username } = user;
const message = {
_id,
rid,
@ -16,8 +16,8 @@ export const getMessage = (rid, msg = '', tmid) => {
_updatedAt: new Date(),
status: messagesStatus.TEMP,
u: {
_id: reduxStore.getState().login.user.id || '1',
username: reduxStore.getState().login.user.username
_id: id || '1',
username
}
};
try {
@ -43,9 +43,9 @@ export async function sendMessageCall(message) {
return data;
}
export default async function(rid, msg, tmid) {
export default async function(rid, msg, tmid, user) {
try {
const message = getMessage(rid, msg, tmid);
const message = getMessage(rid, msg, tmid, user);
const [room] = database.objects('subscriptions').filtered('rid == $0', rid);
if (room) {

View File

@ -26,6 +26,9 @@ const serversSchema = {
id: 'string',
name: { type: 'string', optional: true },
iconURL: { type: 'string', optional: true },
useRealName: { type: 'bool', optional: true },
FileUpload_MediaTypeWhiteList: { type: 'string', optional: true },
FileUpload_MaxFileSize: { type: 'int', optional: true },
roomsUpdatedAt: { type: 'date', optional: true },
version: 'string?'
}

View File

@ -14,6 +14,9 @@ import {
setUser, setLoginServices, loginRequest, loginFailure, logout
} from '../actions/login';
import { disconnect, connectSuccess, connectRequest } from '../actions/connect';
import {
shareSelectServer, shareSetUser
} from '../actions/share';
import subscribeRooms from './methods/subscriptions/rooms';
import subscribeRoom from './methods/subscriptions/room';
@ -217,6 +220,35 @@ const RocketChat = {
});
},
async shareExtensionInit(server) {
database.setActiveDB(server);
if (this.sdk) {
this.sdk.disconnect();
this.sdk = null;
}
// Use useSsl: false only if server url starts with http://
const useSsl = !/http:\/\//.test(server);
this.sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl });
// set Server
const { serversDB } = database.databases;
reduxStore.dispatch(shareSelectServer(server));
// set User info
const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`);
const user = userId && serversDB.objectForPrimaryKey('user', userId);
reduxStore.dispatch(shareSetUser({
id: user.id,
token: user.token,
username: user.username
}));
await RocketChat.login({ resume: user.token });
},
register(credentials) {
// RC 0.50.0
return this.sdk.post('users.register', credentials, false);

View File

@ -35,7 +35,7 @@ const DirectoryItem = ({
<RoomTypeIcon type={type} />
<Text style={styles.directoryItemName} numberOfLines={1}>{title}</Text>
</View>
<Text style={styles.directoryItemUsername} numberOfLines={1}>{description}</Text>
{ description ? <Text style={styles.directoryItemUsername} numberOfLines={1}>{description}</Text> : null }
</View>
<DirectoryItemLabel text={rightLabel} />
</View>

View File

@ -16,8 +16,8 @@ export default StyleSheet.create({
alignItems: 'center'
},
serverIcon: {
width: 38,
height: 38,
width: 44,
height: 44,
marginHorizontal: 15,
borderRadius: 4
},

View File

@ -11,6 +11,7 @@ import app from './app';
import sortPreferences from './sortPreferences';
import notification from './notification';
import markdown from './markdown';
import share from './share';
export default combineReducers({
settings,
@ -24,5 +25,6 @@ export default combineReducers({
rooms,
sortPreferences,
notification,
markdown
markdown,
share
});

23
app/reducers/share.js Normal file
View File

@ -0,0 +1,23 @@
import { SHARE } from '../actions/actionsTypes';
const initialState = {
user: {},
server: ''
};
export default function share(state = initialState, action) {
switch (action.type) {
case SHARE.SELECT_SERVER:
return {
...state,
server: action.server
};
case SHARE.SET_USER:
return {
...state,
user: action.user
};
default:
return state;
}
}

View File

@ -2,31 +2,34 @@ import React from 'react';
import { View } from 'react-native';
import { createAppContainer, createStackNavigator, createSwitchNavigator } from 'react-navigation';
import { Provider } from 'react-redux';
import RNUserDefaults from 'rn-user-defaults';
import Navigation from './lib/Navigation';
import Navigation from './lib/ShareNavigation';
import store from './lib/createStore';
import { appInit } from './actions';
import ShareListView from './views/ShareListView';
import ShareView from './views/ShareView';
import SelectServerView from './views/SelectServerView';
import AuthLoadingView from './views/AuthLoadingView';
import WithoutServersView from './views/WithoutServersView';
import sharedStyles from './views/Styles';
import { isNotch } from './utils/deviceInfo';
import { isNotch, isIOS } from './utils/deviceInfo';
import { defaultHeader, onNavigationStateChange } from './utils/navigation';
import RocketChat from './lib/rocketchat';
const InsideNavigator = createStackNavigator({
ShareListView,
ShareView,
SelectServerView
ShareListView: {
getScreen: () => require('./views/ShareListView').default
},
ShareView: {
getScreen: () => require('./views/ShareView').default
},
SelectServerView: {
getScreen: () => require('./views/SelectServerView').default
}
}, {
initialRouteName: 'ShareListView',
defaultNavigationOptions: defaultHeader
});
const OutsideNavigator = createStackNavigator({
WithoutServersView
WithoutServersView: {
getScreen: () => require('./views/WithoutServersView').default
}
}, {
initialRouteName: 'WithoutServersView',
defaultNavigationOptions: defaultHeader
@ -35,7 +38,9 @@ const OutsideNavigator = createStackNavigator({
const AppContainer = createAppContainer(createSwitchNavigator({
OutsideStack: OutsideNavigator,
InsideStack: InsideNavigator,
AuthLoading: AuthLoadingView
AuthLoading: {
getScreen: () => require('./views/AuthLoadingView').default
}
},
{
initialRouteName: 'AuthLoading'
@ -44,10 +49,25 @@ const AppContainer = createAppContainer(createSwitchNavigator({
class Root extends React.Component {
constructor(props) {
super(props);
store.dispatch(appInit());
this.state = {
isLandscape: false
};
this.init();
}
init = async() => {
if (isIOS) {
await RNUserDefaults.setName('group.ios.chat.rocket');
}
const currentServer = await RNUserDefaults.get('currentServer');
const token = await RNUserDefaults.get(RocketChat.TOKEN_KEY);
if (currentServer && token) {
await Navigation.navigate('InsideStack');
await RocketChat.shareExtensionInit(currentServer);
} else {
await Navigation.navigate('OutsideStack');
}
}
handleLayout = (event) => {

23
app/utils/media.js Normal file
View File

@ -0,0 +1,23 @@
export const canUploadFile = (file, serverInfo) => {
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = serverInfo;
if (!(file && file.path)) {
return true;
}
if (file.size > FileUpload_MaxFileSize) {
return false;
}
// if white list is empty, all media types are enabled
if (!FileUpload_MediaTypeWhiteList) {
return true;
}
const allowedMime = FileUpload_MediaTypeWhiteList.split(',');
if (allowedMime.includes(file.mime)) {
return true;
}
const wildCardGlob = '/*';
const wildCards = allowedMime.filter(item => item.indexOf(wildCardGlob) > 0);
if (wildCards.includes(file.mime.replace(/(\/.*)$/, wildCardGlob))) {
return true;
}
return false;
};

View File

@ -64,7 +64,13 @@ const styles = StyleSheet.create({
export default class UploadProgress extends Component {
static propTypes = {
window: PropTypes.object,
rid: PropTypes.string
rid: PropTypes.string,
user: PropTypes.shape({
id: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
token: PropTypes.string.isRequired
}),
baseUrl: PropTypes.string.isRequired
}
constructor(props) {
@ -124,13 +130,13 @@ export default class UploadProgress extends Component {
}
tryAgain = async(item) => {
const { rid } = this.props;
const { rid, baseUrl: server, user } = this.props;
try {
database.write(() => {
item.error = false;
});
await RocketChat.sendFileMessage(rid, item);
await RocketChat.sendFileMessage(rid, item, undefined, server, user);
} catch (e) {
log('err_upload_progress_try_again', e);
}

View File

@ -410,8 +410,9 @@ export default class RoomView extends React.Component {
}
sendMessage = (message, tmid) => {
const { user } = this.props;
LayoutAnimation.easeInEaseOut();
RocketChat.sendMessage(this.rid, message, this.tmid || tmid).then(() => {
RocketChat.sendMessage(this.rid, message, this.tmid || tmid, user).then(() => {
this.setLastOpen(null);
});
};
@ -623,7 +624,7 @@ export default class RoomView extends React.Component {
{this.renderFooter()}
{this.renderActions()}
<ReactionPicker onEmojiSelected={this.onReactionPress} />
<UploadProgress rid={this.rid} />
<UploadProgress rid={this.rid} user={user} baseUrl={baseUrl} />
<FileModal
attachment={selectedAttachment}
isVisible={photoModalVisible}

View File

@ -9,14 +9,11 @@ import { SafeAreaView } from 'react-navigation';
import I18n from '../i18n';
import database from '../lib/realm';
import StatusBar from '../containers/StatusBar';
import { selectServerRequest as selectServerRequestAction } from '../actions/server';
import {
COLOR_BACKGROUND_CONTAINER
} from '../constants/colors';
import Navigation from '../lib/Navigation';
import { COLOR_BACKGROUND_CONTAINER } from '../constants/colors';
import Navigation from '../lib/ShareNavigation';
import ServerItem, { ROW_HEIGHT } from '../presentation/ServerItem';
import sharedStyles from './Styles';
import RocketChat from '../lib/rocketchat';
const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index });
const keyExtractor = item => item.id;
@ -36,10 +33,8 @@ const styles = StyleSheet.create({
}
});
@connect(state => ({
server: state.server.server
}), dispatch => ({
selectServerRequest: server => dispatch(selectServerRequestAction(server))
@connect(({ share }) => ({
server: share.server
}))
export default class SelectServerView extends React.Component {
static navigationOptions = () => ({
@ -47,8 +42,7 @@ export default class SelectServerView extends React.Component {
})
static propTypes = {
server: PropTypes.string,
selectServerRequest: PropTypes.func
server: PropTypes.string
}
constructor(props) {
@ -61,15 +55,15 @@ export default class SelectServerView extends React.Component {
};
}
select = (server) => {
select = async(server) => {
const {
server: currentServer, selectServerRequest
server: currentServer
} = this.props;
if (currentServer !== server) {
selectServerRequest(server);
}
Navigation.navigate('ShareListView');
if (currentServer !== server) {
await RocketChat.shareExtensionInit(server);
}
}
renderItem = ({ item }) => {

View File

@ -1,92 +0,0 @@
import React, { PureComponent } from 'react';
import {
View, StyleSheet, Text, Platform
} from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { TextInput } from 'react-native-gesture-handler';
import I18n from '../../i18n';
import { COLOR_WHITE, HEADER_TITLE } from '../../constants/colors';
import sharedStyles from '../Styles';
import { setSearch as setSearchAction } from '../../actions/rooms';
import { isAndroid } from '../../utils/deviceInfo';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center'
},
search: {
fontSize: 20,
color: COLOR_WHITE,
...sharedStyles.textRegular
},
title: {
...Platform.select({
ios: {
fontSize: 17,
...sharedStyles.textSemibold,
color: HEADER_TITLE
},
android: {
fontSize: 20,
...sharedStyles.textRegular,
color: HEADER_TITLE
}
})
}
});
@connect(state => ({
showSearchHeader: state.rooms.showSearchHeader
}), dispatch => ({
setSearch: searchText => dispatch(setSearchAction(searchText))
}))
class ShareListHeader extends PureComponent {
static propTypes = {
showSearchHeader: PropTypes.bool,
setSearch: PropTypes.func
}
componentDidUpdate(prevProps) {
const { showSearchHeader } = this.props;
if (showSearchHeader && prevProps.showSearchHeader !== showSearchHeader) {
setTimeout(() => {
this.searchInputRef.focus();
}, 300);
}
}
onSearchChangeText = (text) => {
const { setSearch } = this.props;
setSearch(text.trim());
}
setSearchInputRef = (ref) => {
this.searchInputRef = ref;
}
render() {
const {
showSearchHeader
} = this.props;
if (showSearchHeader && isAndroid) {
return (
<View style={styles.container}>
<TextInput
ref={this.setSearchInputRef}
style={styles.search}
placeholder={I18n.t('Search')}
placeholderTextColor='rgba(255, 255, 255, 0.5)'
onChangeText={this.onSearchChangeText}
/>
</View>
);
}
return <Text style={styles.title}>{I18n.t('Select_Channels')}</Text>;
}
}
export default ShareListHeader;

View File

@ -0,0 +1,52 @@
import React from 'react';
import {
View, StyleSheet, Text, TextInput
} from 'react-native';
import PropTypes from 'prop-types';
import I18n from '../../../i18n';
import { COLOR_WHITE, HEADER_TITLE } from '../../../constants/colors';
import sharedStyles from '../../Styles';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center'
},
search: {
fontSize: 20,
color: COLOR_WHITE,
...sharedStyles.textRegular,
marginHorizontal: 14
},
title: {
fontSize: 20,
...sharedStyles.textBold,
color: HEADER_TITLE,
marginHorizontal: 16
}
});
const Header = React.memo(({ searching, onChangeSearchText }) => {
if (searching) {
return (
<View style={styles.container}>
<TextInput
style={styles.search}
placeholder={I18n.t('Search')}
placeholderTextColor='rgba(255, 255, 255, 0.5)'
onChangeText={onChangeSearchText}
autoFocus
/>
</View>
);
}
return <Text style={styles.title}>{I18n.t('Send_to')}</Text>;
});
Header.propTypes = {
searching: PropTypes.bool,
onChangeSearchText: PropTypes.func
};
export default Header;

View File

@ -0,0 +1,76 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import {
Keyboard, LayoutAnimation, View, StyleSheet
} from 'react-native';
import ShareExtension from 'rn-extensions-share';
import SearchBox from '../../../containers/SearchBox';
import { CloseShareExtensionButton } from '../../../containers/HeaderButton';
import { HEADER_BACKGROUND } from '../../../constants/colors';
import sharedStyles from '../../Styles';
const styles = StyleSheet.create({
container: {
backgroundColor: HEADER_BACKGROUND,
flexDirection: 'row',
...sharedStyles.separatorBottom
}
});
const Header = React.memo(({
searching, onChangeSearchText, initSearch, cancelSearch
}) => {
const [text, setText] = useState('');
const onChangeText = (searchText) => {
onChangeSearchText(searchText);
setText(searchText);
};
const onCancelPress = () => {
Keyboard.dismiss();
onChangeText('');
cancelSearch();
LayoutAnimation.easeInEaseOut();
};
const onFocus = () => {
initSearch();
LayoutAnimation.easeInEaseOut();
};
return (
<View style={styles.container}>
{
!searching
? (
<CloseShareExtensionButton
onPress={ShareExtension.close}
testID='share-extension-close'
/>
)
: null
}
<SearchBox
value={text}
hasCancel={searching}
onFocus={onFocus}
onCancelPress={onCancelPress}
onChangeText={onChangeText}
testID='rooms-list-view-search'
key='rooms-list-view-search'
/>
</View>
);
});
Header.propTypes = {
searching: PropTypes.bool,
onChangeSearchText: PropTypes.func,
initSearch: PropTypes.func,
cancelSearch: PropTypes.func
};
export default Header;

View File

@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import Header from './Header';
const ShareListHeader = React.memo(({
searching, initSearch, cancelSearch, search
}) => {
const onSearchChangeText = (text) => {
search(text.trim());
};
return (
<Header
searching={searching}
initSearch={initSearch}
cancelSearch={cancelSearch}
onChangeSearchText={onSearchChangeText}
/>
);
});
ShareListHeader.propTypes = {
searching: PropTypes.bool,
initSearch: PropTypes.func,
cancelSearch: PropTypes.func,
search: PropTypes.func
};
export default ShareListHeader;

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
View, Text, LayoutAnimation, InteractionManager, FlatList, ScrollView, ActivityIndicator, Keyboard
View, Text, LayoutAnimation, FlatList, ActivityIndicator, Keyboard, BackHandler
} from 'react-native';
import { SafeAreaView } from 'react-navigation';
import ShareExtension from 'rn-extensions-share';
@ -10,60 +10,58 @@ import RNFetchBlob from 'rn-fetch-blob';
import * as mime from 'react-native-mime-types';
import { isEqual } from 'lodash';
import Navigation from '../../lib/Navigation';
import database, { safeAddListener } from '../../lib/realm';
import debounce from '../../utils/debounce';
import Navigation from '../../lib/ShareNavigation';
import database from '../../lib/realm';
import { isIOS, isAndroid } from '../../utils/deviceInfo';
import I18n from '../../i18n';
import { CustomIcon } from '../../lib/Icons';
import log from '../../utils/log';
import {
openSearchHeader as openSearchHeaderAction,
closeSearchHeader as closeSearchHeaderAction
} from '../../actions/rooms';
import { canUploadFile } from '../../utils/media';
import DirectoryItem, { ROW_HEIGHT } from '../../presentation/DirectoryItem';
import ServerItem, { ROW_HEIGHT as ROW_HEIGHT_SERVER } from '../../presentation/ServerItem';
import ServerItem from '../../presentation/ServerItem';
import { CloseShareExtensionButton, CustomHeaderButtons, Item } from '../../containers/HeaderButton';
import SearchBar from '../RoomsListView/ListHeader/SearchBar';
import ShareListHeader from './Header';
import styles from './styles';
import StatusBar from '../../containers/StatusBar';
const SCROLL_OFFSET = 56;
const getItemLayoutChannel = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index });
const getItemLayoutServer = (data, index) => ({ length: ROW_HEIGHT_SERVER, offset: ROW_HEIGHT_SERVER * index, index });
const LIMIT = 50;
const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index });
const keyExtractor = item => item.rid;
@connect(state => ({
userId: state.login.user && state.login.user.id,
token: state.login.user && state.login.user.token,
useRealName: state.settings.UI_Use_Real_Name,
searchText: state.rooms.searchText,
server: state.server.server,
loading: state.server.loading,
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
baseUrl: state.settings.baseUrl || state.server ? state.server.server : '',
sortBy: state.sortPreferences.sortBy,
groupByType: state.sortPreferences.groupByType,
showFavorites: state.sortPreferences.showFavorites
}), dispatch => ({
openSearchHeader: () => dispatch(openSearchHeaderAction()),
closeSearchHeader: () => dispatch(closeSearchHeaderAction())
@connect(({ share }) => ({
userId: share.user && share.user.id,
token: share.user && share.user.token,
server: share.server,
baseUrl: share ? share.server : ''
}))
/** @extends React.Component */
export default class ShareListView extends React.Component {
static navigationOptions = ({ navigation }) => {
const searching = navigation.getParam('searching');
const cancelSearchingAndroid = navigation.getParam('cancelSearchingAndroid');
const initSearchingAndroid = navigation.getParam('initSearchingAndroid', () => {});
const initSearch = navigation.getParam('initSearch', () => {});
const cancelSearch = navigation.getParam('cancelSearch', () => {});
const search = navigation.getParam('search', () => {});
if (isIOS) {
return {
headerTitle: (
<ShareListHeader
searching={searching}
initSearch={initSearch}
cancelSearch={cancelSearch}
search={search}
/>
)
};
}
return {
headerBackTitle: isIOS ? I18n.t('Back') : null,
headerBackTitle: null,
headerLeft: searching
? (
<CustomHeaderButtons left>
<Item title='cancel' iconName='cross' onPress={cancelSearchingAndroid} />
<Item title='cancel' iconName='cross' onPress={cancelSearch} />
</CustomHeaderButtons>
)
: (
@ -72,13 +70,13 @@ export default class ShareListView extends React.Component {
testID='share-extension-close'
/>
),
headerTitle: <ShareListHeader />,
headerTitle: <ShareListHeader searching={searching} search={search} />,
headerRight: (
searching
? null
: (
<CustomHeaderButtons>
{isAndroid ? <Item title='search' iconName='magnifier' onPress={initSearchingAndroid} /> : null}
{isAndroid ? <Item title='search' iconName='magnifier' onPress={initSearch} /> : null}
</CustomHeaderButtons>
)
)
@ -88,50 +86,38 @@ export default class ShareListView extends React.Component {
static propTypes = {
navigation: PropTypes.object,
server: PropTypes.string,
useRealName: PropTypes.bool,
searchText: PropTypes.string,
FileUpload_MediaTypeWhiteList: PropTypes.string,
FileUpload_MaxFileSize: PropTypes.number,
openSearchHeader: PropTypes.func,
closeSearchHeader: PropTypes.func,
baseUrl: PropTypes.string,
token: PropTypes.string,
userId: PropTypes.string,
sortBy: PropTypes.string,
groupByType: PropTypes.bool,
showFavorites: PropTypes.bool,
loading: PropTypes.bool
userId: PropTypes.string
}
constructor(props) {
super(props);
this.data = [];
this.state = {
showError: false,
searching: false,
searchText: '',
value: '',
isMedia: false,
mediaLoading: false,
loading: true,
fileInfo: null,
search: [],
discussions: [],
channels: [],
favorites: [],
searchResults: [],
chats: [],
privateGroup: [],
direct: [],
livechat: [],
servers: []
servers: [],
loading: true,
serverInfo: null
};
this.didFocusListener = props.navigation.addListener('didFocus', () => BackHandler.addEventListener('hardwareBackPress', this.handleBackPress));
this.willBlurListener = props.navigation.addListener('willBlur', () => BackHandler.addEventListener('hardwareBackPress', this.handleBackPress));
}
async componentDidMount() {
this.getSubscriptions();
const { navigation } = this.props;
const { navigation, server } = this.props;
navigation.setParams({
initSearchingAndroid: this.initSearchingAndroid,
cancelSearchingAndroid: this.cancelSearchingAndroid
initSearch: this.initSearch,
cancelSearch: this.cancelSearch,
search: this.search
});
try {
@ -145,8 +131,7 @@ export default class ShareListView extends React.Component {
name: data.filename,
description: '',
size: data.size,
type: mime.lookup(data.path),
store: 'Uploads',
mime: mime.lookup(data.path),
path: isIOS ? data.path : `file://${ data.path }`
};
}
@ -157,33 +142,36 @@ export default class ShareListView extends React.Component {
log('err_process_media_share_extension', e);
this.setState({ mediaLoading: false });
}
this.getSubscriptions(server);
}
componentWillReceiveProps(nextProps) {
const { searchText, loading } = this.props;
if (nextProps.server && loading !== nextProps.loading) {
if (nextProps.loading) {
this.internalSetState({ loading: true });
} else {
this.getSubscriptions();
}
} else if (searchText !== nextProps.searchText) {
this.search(nextProps.searchText);
const { server } = this.props;
if (nextProps.server !== server) {
this.getSubscriptions(nextProps.server);
}
}
shouldComponentUpdate(nextProps, nextState) {
const { loading, searching } = this.state;
if (nextState.loading !== loading) {
return true;
}
const { searching } = this.state;
if (nextState.searching !== searching) {
return true;
}
const { search } = this.state;
if (!isEqual(nextState.search, search)) {
const { isMedia } = this.state;
if (nextState.isMedia !== isMedia) {
this.getSubscriptions(nextProps.server, nextState.fileInfo);
return true;
}
const { server } = this.props;
if (server !== nextProps.server) {
return true;
}
const { searchResults } = this.state;
if (!isEqual(nextState.searchResults, searchResults)) {
return true;
}
return false;
@ -198,69 +186,32 @@ export default class ShareListView extends React.Component {
this.setState(...args);
}
getSubscriptions = debounce(() => {
if (this.data && this.data.removeAllListeners) {
this.data.removeAllListeners();
}
const {
server, sortBy, showFavorites, groupByType
} = this.props;
getSubscriptions = (server, fileInfo) => {
const { fileInfo: fileData } = this.state;
const { serversDB } = database.databases;
if (server) {
this.data = database.objects('subscriptions').filtered('archived != true && open == true');
if (sortBy === 'alphabetical') {
this.data = this.data.sorted('name', false);
} else {
this.data = this.data.sorted('roomUpdatedAt', true);
}
// servers
this.data = database.objects('subscriptions').filtered('archived != true && open == true').sorted('roomUpdatedAt', true);
this.servers = serversDB.objects('servers');
this.chats = this.data.slice(0, LIMIT);
const serverInfo = serversDB.objectForPrimaryKey('servers', server);
// favorites
if (showFavorites) {
this.favorites = this.data.filtered('f == true');
} else {
this.favorites = [];
}
// type
if (groupByType) {
this.discussions = this.data.filtered('prid != null');
this.channels = this.data.filtered('t == $0 AND prid == null', 'c');
this.privateGroup = this.data.filtered('t == $0 AND prid == null', 'p');
this.direct = this.data.filtered('t == $0 AND prid == null', 'd');
this.livechat = this.data.filtered('t == $0 AND prid == null', 'l');
} else {
this.chats = this.data;
}
safeAddListener(this.data, this.updateState);
this.internalSetState({
chats: this.chats ? this.chats.slice() : [],
servers: this.servers ? this.servers.slice() : [],
loading: false,
showError: !canUploadFile(fileInfo || fileData, serverInfo),
serverInfo
});
this.forceUpdate();
}
}, 300);
};
uriToPath = uri => decodeURIComponent(isIOS ? uri.replace(/^file:\/\//, '') : uri);
// eslint-disable-next-line react/sort-comp
updateState = debounce(() => {
this.updateStateInteraction = InteractionManager.runAfterInteractions(() => {
this.internalSetState({
chats: this.chats ? this.chats.slice() : [],
favorites: this.favorites ? this.favorites.slice() : [],
discussions: this.discussions ? this.discussions.slice() : [],
channels: this.channels ? this.channels.slice() : [],
privateGroup: this.privateGroup ? this.privateGroup.slice() : [],
direct: this.direct ? this.direct.slice() : [],
livechat: this.livechat ? this.livechat.slice() : [],
servers: this.servers ? this.servers.slice() : [],
loading: false
});
this.forceUpdate();
});
}, 300);
getRoomTitle = (item) => {
const { useRealName } = this.props;
const { serverInfo } = this.state;
const { useRealName } = serverInfo;
return ((item.prid || useRealName) && item.fname) || item.name;
}
@ -277,71 +228,52 @@ export default class ShareListView extends React.Component {
});
}
canUploadFile = () => {
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = this.props;
const { fileInfo: file, mediaLoading, loading } = this.state;
search = (text) => {
const result = database.objects('subscriptions').filtered('name CONTAINS[c] $0', text);
this.internalSetState({
searchResults: result.slice(0, LIMIT),
searchText: text
});
}
if (loading || mediaLoading) {
return true;
}
if (!(file && file.path)) {
return true;
}
if (file.size > FileUpload_MaxFileSize) {
return false;
}
if (!FileUpload_MediaTypeWhiteList) {
return false;
}
const allowedMime = FileUpload_MediaTypeWhiteList.split(',');
if (allowedMime.includes(file.type)) {
return true;
}
const wildCardGlob = '/*';
const wildCards = allowedMime.filter(item => item.indexOf(wildCardGlob) > 0);
if (wildCards.includes(file.type.replace(/(\/.*)$/, wildCardGlob))) {
initSearch = () => {
const { chats } = this.state;
const { navigation } = this.props;
this.setState({ searching: true, searchResults: chats });
navigation.setParams({ searching: true });
}
cancelSearch = () => {
const { navigation } = this.props;
this.internalSetState({ searching: false, searchResults: [], searchText: '' });
navigation.setParams({ searching: false });
Keyboard.dismiss();
}
handleBackPress = () => {
const { searching } = this.state;
if (searching) {
this.cancelSearch();
return true;
}
return false;
}
search = (text) => {
const result = database.objects('subscriptions').filtered('name CONTAINS[c] $0', text);
const subscriptions = database.objects('subscriptions');
const data = result.length !== subscriptions.length ? result : [];
this.internalSetState({
search: data
});
}
initSearchingAndroid = () => {
const { openSearchHeader, navigation } = this.props;
this.setState({ searching: true });
navigation.setParams({ searching: true });
openSearchHeader();
}
cancelSearchingAndroid = () => {
if (isAndroid) {
const { closeSearchHeader, navigation } = this.props;
this.setState({ searching: false });
navigation.setParams({ searching: false });
closeSearchHeader();
this.internalSetState({ search: [] });
Keyboard.dismiss();
renderSectionHeader = (header) => {
const { searching } = this.state;
if (searching) {
return null;
}
return (
<View style={styles.headerContainer}>
<Text style={styles.headerText}>
{I18n.t(header)}
</Text>
</View>
);
}
renderListHeader = () => <SearchBar onChangeSearchText={this.search} />;
renderSectionHeader = header => (
<View style={styles.headerContainer}>
<Text style={styles.headerText}>
{I18n.t(header)}
</Text>
</View>
)
renderItem = ({ item }) => {
const { userId, token, baseUrl } = this.props;
return (
@ -367,33 +299,9 @@ export default class ShareListView extends React.Component {
renderSeparator = () => <View style={styles.separator} />;
renderSection = (data, header) => {
if (data && data.length > 0) {
return (
<React.Fragment>
{this.renderSectionHeader(header)}
<View style={styles.bordered}>
<FlatList
data={data}
keyExtractor={keyExtractor}
style={styles.flatlist}
renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator}
getItemLayout={getItemLayoutServer}
enableEmptySections
removeClippedSubviews
keyboardShouldPersistTaps='always'
initialNumToRender={12}
windowSize={20}
/>
</View>
</React.Fragment>
);
}
return null;
}
renderBorderBottom = () => <View style={styles.borderBottom} />;
renderServerSelector = () => {
renderSelectServer = () => {
const { servers } = this.state;
const { server } = this.props;
const currentServer = servers.find(serverFiltered => serverFiltered.id === server);
@ -411,83 +319,97 @@ export default class ShareListView extends React.Component {
) : null;
}
renderContent = () => {
const {
discussions, channels, privateGroup, direct, livechat, search, chats, favorites
} = this.state;
if (search.length > 0) {
return (
<FlatList
data={search}
extraData={search}
keyExtractor={keyExtractor}
style={styles.flatlist}
renderItem={this.renderItem}
getItemLayout={getItemLayoutChannel}
ItemSeparatorComponent={this.renderSeparator}
enableEmptySections
removeClippedSubviews
keyboardShouldPersistTaps='always'
initialNumToRender={12}
windowSize={20}
/>
);
}
renderEmptyComponent = () => (
<View style={[styles.container, styles.emptyContainer]}>
<Text style={styles.title}>{I18n.t('No_results_found')}</Text>
</View>
);
renderHeader = () => {
const { searching } = this.state;
return (
<View style={styles.content}>
{this.renderServerSelector()}
{this.renderSection(favorites, 'Favorites')}
{this.renderSection(discussions, 'Discussions')}
{this.renderSection(channels, 'Channels')}
{this.renderSection(direct, 'Direct_Messages')}
{this.renderSection(privateGroup, 'Private_Groups')}
{this.renderSection(livechat, 'Livechat')}
{this.renderSection(chats, 'Chats')}
</View>
<React.Fragment>
{ !searching
? (
<React.Fragment>
{this.renderSelectServer()}
{this.renderSectionHeader('Chats')}
</React.Fragment>
)
: null
}
</React.Fragment>
);
}
renderContent = () => {
const {
chats, mediaLoading, loading, searchResults, searching, searchText
} = this.state;
renderScrollView = () => {
const { mediaLoading, loading } = this.state;
if (mediaLoading || loading) {
return <ActivityIndicator style={styles.loading} />;
}
return (
<ScrollView
style={styles.scroll}
contentOffset={isIOS ? { x: 0, y: SCROLL_OFFSET } : {}}
<FlatList
data={searching ? searchResults : chats}
keyExtractor={keyExtractor}
style={styles.flatlist}
renderItem={this.renderItem}
getItemLayout={getItemLayout}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={!searching && this.renderBorderBottom}
ListHeaderComponentStyle={!searching ? styles.borderBottom : {}}
ListEmptyComponent={searching && searchText ? this.renderEmptyComponent : null}
enableEmptySections
removeClippedSubviews
keyboardShouldPersistTaps='always'
>
{this.renderListHeader()}
{this.renderContent()}
</ScrollView>
initialNumToRender={12}
windowSize={20}
/>
);
}
renderError = () => {
const { fileInfo: file } = this.state;
const { FileUpload_MaxFileSize } = this.props;
const {
fileInfo: file, loading, searching, serverInfo
} = this.state;
const { FileUpload_MaxFileSize } = serverInfo;
const errorMessage = (FileUpload_MaxFileSize < file.size)
? 'error-file-too-large'
: 'error-invalid-file-type';
if (loading) {
return <ActivityIndicator style={styles.loading} />;
}
return (
<View style={styles.container}>
<Text style={styles.title}>{I18n.t(errorMessage)}</Text>
<CustomIcon name='circle-cross' size={120} style={styles.errorIcon} />
<Text style={styles.fileMime}>{ file.type }</Text>
{ !searching
? (
<React.Fragment>
{this.renderSelectServer()}
</React.Fragment>
)
: null
}
<View style={[styles.container, styles.centered]}>
<Text style={styles.title}>{I18n.t(errorMessage)}</Text>
<CustomIcon name='circle-cross' size={120} style={styles.errorIcon} />
<Text style={styles.fileMime}>{ file.mime }</Text>
</View>
</View>
);
}
render() {
const showError = !this.canUploadFile();
const { showError } = this.state;
return (
<SafeAreaView style={styles.container} forceInset={{ vertical: 'never' }}>
{ showError ? this.renderError() : this.renderScrollView() }
<StatusBar />
{ showError ? this.renderError() : this.renderContent() }
</SafeAreaView>
);
}

View File

@ -8,24 +8,34 @@ import {
export default StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
flex: 1,
backgroundColor: COLOR_BACKGROUND_CONTAINER
},
emptyContainer: {
padding: 20,
justifyContent: 'center',
alignItems: 'center'
},
content: {
flex: 1,
backgroundColor: isIOS ? COLOR_WHITE : '#E1E5E8'
backgroundColor: isIOS ? COLOR_WHITE : '#E1E5E8',
justifyContent: 'center',
alignItems: 'center'
},
centered: {
justifyContent: 'center',
alignItems: 'center'
},
flatlist: {
marginTop: isIOS ? 6 : 0, // the height of the navigation bar with the searchbar is larger
width: '100%',
backgroundColor: COLOR_WHITE
backgroundColor: COLOR_BACKGROUND_CONTAINER
},
bordered: {
...sharedStyles.separatorVertical
},
scroll: {
width: '100%'
borderBottom: {
...sharedStyles.separatorBottom
},
headerContainer: {
paddingHorizontal: 15,
@ -37,8 +47,7 @@ export default StyleSheet.create({
...sharedStyles.textColorNormal,
...sharedStyles.textRegular,
fontSize: 17,
letterSpacing: 0.27,
flex: 1
letterSpacing: 0.27
},
separator: {
...sharedStyles.separatorBottom,

View File

@ -19,12 +19,17 @@ import database from '../../lib/realm';
import { CustomHeaderButtons, Item } from '../../containers/HeaderButton';
import { isReadOnly, isBlocked } from '../../utils/room';
@connect(state => ({
username: state.login.user && state.login.user.username
@connect(({ share }) => ({
user: {
id: share.user && share.user.id,
username: share.user && share.user.username,
token: share.user && share.user.token
},
baseUrl: share ? share.server : ''
}))
export default class ShareView extends React.Component {
static navigationOptions = ({ navigation }) => {
const canSend = navigation.getParam('canSend', false);
const canSend = navigation.getParam('canSend', true);
return ({
title: I18n.t('Share'),
@ -46,7 +51,12 @@ export default class ShareView extends React.Component {
static propTypes = {
navigation: PropTypes.object,
username: PropTypes.string.isRequired
user: PropTypes.shape({
id: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
token: PropTypes.string.isRequired
}),
baseUrl: PropTypes.string.isRequired
};
constructor(props) {
@ -77,11 +87,12 @@ export default class ShareView extends React.Component {
componentDidMount() {
const { room } = this.state;
const { navigation, username } = this.props;
const { navigation, user } = this.props;
const { username } = user;
navigation.setParams({ sendMessage: this._sendMessage, canSend: !(isReadOnly(room, { username }) || isBlocked(room)) });
}
bytesToSize = bits => `${ ((bits / 8) / 1048576).toFixed(2) }MB`;
bytesToSize = bytes => `${ (bytes / 1048576).toFixed(2) }MB`;
_sendMessage = async() => {
const { isMedia } = this.state;
@ -99,11 +110,19 @@ export default class ShareView extends React.Component {
sendMediaMessage = async() => {
const { rid, fileInfo, file } = this.state;
const { baseUrl: server, user } = this.props;
const { name, description } = file;
const fileMessage = { ...fileInfo, name, description };
const fileMessage = {
name,
description,
size: fileInfo.size,
type: fileInfo.mime,
store: 'Uploads',
path: fileInfo.path
};
if (fileInfo && rid !== '') {
try {
await RocketChat.sendFileMessage(rid, fileMessage, undefined);
await RocketChat.sendFileMessage(rid, fileMessage, undefined, server, user);
} catch (e) {
log('err_send_media_message', e);
}
@ -112,9 +131,10 @@ export default class ShareView extends React.Component {
sendTextMessage = async() => {
const { value, rid } = this.state;
const { user } = this.props;
if (value !== '' && rid !== '') {
try {
await RocketChat.sendMessage(rid, value, undefined);
await RocketChat.sendMessage(rid, value, undefined, user);
} catch (error) {
log('err_share_extension_send_message', error);
}
@ -124,7 +144,7 @@ export default class ShareView extends React.Component {
renderPreview = () => {
const { fileInfo } = this.state;
const icon = fileInfo.type.match(/image/)
const icon = fileInfo.mime.match(/image/)
? <Image source={{ isStatic: true, uri: fileInfo.path }} style={styles.mediaImage} />
: (
<View style={styles.mediaIconContainer}>
@ -204,7 +224,8 @@ export default class ShareView extends React.Component {
}
render() {
const { username } = this.props;
const { user } = this.props;
const { username } = user;
const {
name, loading, isMedia, room
} = this.state;

View File

@ -200,7 +200,7 @@ export default StyleSheet.create({
marginVertical: 10
},
notchLandscapeContainer: {
marginTop: -44,
marginTop: -34,
paddingHorizontal: 30,
backgroundColor: COLOR_BACKGROUND_CONTAINER
}

View File

@ -14,7 +14,8 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: COLOR_WHITE,
justifyContent: 'center',
alignItems: 'center'
alignItems: 'center',
padding: 15
},
title: {
fontSize: 18,

View File

@ -1,6 +1,6 @@
PODS:
- boost-for-react-native (1.63.0)
- Crashlytics (3.13.2):
- Crashlytics (3.13.4):
- Fabric (~> 1.10.2)
- DoubleConversion (1.1.6)
- EXAppLoaderProvider (6.0.0)
@ -18,24 +18,24 @@ PODS:
- EXWebBrowser (6.0.0):
- UMCore
- Fabric (1.10.2)
- Firebase/Core (6.3.0):
- Firebase/Core (6.5.0):
- Firebase/CoreOnly
- FirebaseAnalytics (= 6.0.2)
- Firebase/CoreOnly (6.3.0):
- FirebaseCore (= 6.0.3)
- FirebaseAnalytics (6.0.2):
- FirebaseCore (~> 6.0)
- FirebaseAnalytics (= 6.0.4)
- Firebase/CoreOnly (6.5.0):
- FirebaseCore (= 6.1.0)
- FirebaseAnalytics (6.0.4):
- FirebaseCore (~> 6.1)
- FirebaseInstanceID (~> 4.2)
- GoogleAppMeasurement (= 6.0.2)
- GoogleAppMeasurement (= 6.0.4)
- GoogleUtilities/AppDelegateSwizzler (~> 6.0)
- GoogleUtilities/MethodSwizzler (~> 6.0)
- GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)"
- nanopb (~> 0.3)
- FirebaseCore (6.0.3):
- FirebaseCore (6.1.0):
- GoogleUtilities/Environment (~> 6.0)
- GoogleUtilities/Logger (~> 6.0)
- FirebaseInstanceID (4.2.0):
- FirebaseInstanceID (4.2.2):
- FirebaseCore (~> 6.0)
- GoogleUtilities/Environment (~> 6.0)
- GoogleUtilities/UserDefaults (~> 6.0)
@ -49,29 +49,29 @@ PODS:
- DoubleConversion
- glog
- glog (0.3.5)
- GoogleAppMeasurement (6.0.2):
- GoogleAppMeasurement (6.0.4):
- GoogleUtilities/AppDelegateSwizzler (~> 6.0)
- GoogleUtilities/MethodSwizzler (~> 6.0)
- GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)"
- nanopb (~> 0.3)
- GoogleUtilities/AppDelegateSwizzler (6.2.1):
- GoogleUtilities/AppDelegateSwizzler (6.2.3):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (6.2.1)
- GoogleUtilities/Logger (6.2.1):
- GoogleUtilities/Environment (6.2.3)
- GoogleUtilities/Logger (6.2.3):
- GoogleUtilities/Environment
- GoogleUtilities/MethodSwizzler (6.2.1):
- GoogleUtilities/MethodSwizzler (6.2.3):
- GoogleUtilities/Logger
- GoogleUtilities/Network (6.2.1):
- GoogleUtilities/Network (6.2.3):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (6.2.1)"
- GoogleUtilities/Reachability (6.2.1):
- "GoogleUtilities/NSData+zlib (6.2.3)"
- GoogleUtilities/Reachability (6.2.3):
- GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (6.2.1):
- GoogleUtilities/UserDefaults (6.2.3):
- GoogleUtilities/Logger
- libwebp (1.0.2):
- libwebp/core (= 1.0.2)
@ -230,7 +230,7 @@ PODS:
- React
- RNVectorIcons (6.4.2):
- React
- RSKImageCropper (2.2.1)
- RSKImageCropper (2.2.3)
- SDWebImage (5.0.6):
- SDWebImage/Core (= 5.0.6)
- SDWebImage/Core (5.0.6)
@ -479,7 +479,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
Crashlytics: 611738c7847f8291a1a51084e35987b86ba6b3ee
Crashlytics: 2dfd686bcb918dc10ee0e76f7f853fe42c7bd552
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
EXAppLoaderProvider: 7a8185228d8ba9e689a0e2d6d957fe9bdd49c8a0
EXConstants: 5d81e84ca71b9a552529889cc798b4a04e9e22b3
@ -488,14 +488,14 @@ SPEC CHECKSUMS:
EXPermissions: 99e52dc3e5f8e55153f1958004f6df2a30a1f2f5
EXWebBrowser: def838b95aa9d396f9ce71ace4e614ee16e7ee30
Fabric: 706c8b8098fff96c33c0db69cbf81f9c551d0d74
Firebase: 8432d732974498afd5987e9001a05f90f1a3d625
FirebaseAnalytics: 470ddab7253b21ad5a40bebd4a9903d7ae19386a
FirebaseCore: 68f8a7f50cdae542715d4e86afa37c4067217dcb
FirebaseInstanceID: f20243a1d828e0e9a3798b995174dedc16f1b32a
Firebase: dedc9e48ea3f3649ad5f6b982f8a0c73508a14b5
FirebaseAnalytics: 3fb375bc9d13779add4039716f868d233a473fad
FirebaseCore: aecf02fb2274ec361b9bebeac112f5daa18273bd
FirebaseInstanceID: 662b8108a09fe9ed01aafdedba100fde8536b0f6
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
GoogleAppMeasurement: a35a645835bae31b6bdc0576396bc23908f12a22
GoogleUtilities: c7a0b08bda3bf808be823ed151f0e28ac6866e71
GoogleAppMeasurement: 183bd916af7f80deb67c01888368f1108d641832
GoogleUtilities: d2b0e277a95962e09bb27f5cd42f5f0b6a506c7d
libwebp: b068a3bd7c45f7460f6715be7bed1a18fd5d6b48
nanopb: 2901f78ea1b7b4015c860c2fdd1ea2fee1a18d48
QBImagePickerController: d54cf93db6decf26baf6ed3472f336ef35cae022
@ -538,7 +538,7 @@ SPEC CHECKSUMS:
RNScreens: f28b48b8345f2f5f39ed6195518291515032a788
RNUserDefaults: 8a4928443510aa99e4ccb3b53f1bf186593d690b
RNVectorIcons: 6607bd3a30291d0edb56f9bbe7ae411ee2b928b0
RSKImageCropper: 98296ad26b41753f796b6898d015509598f13d97
RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a
SDWebImage: 920f1a2ff1ca8296ad34f6e0510a1ef1d70ac965
SDWebImageWebPCoder: 7568737603c50f6237850afedd7e9e28e5917e6b
UMBarCodeScannerInterface: 84ea2d6b58ff0dc27ef9b68bab71286be18ee020

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -61,6 +61,10 @@ Firebase Messaging works as intended."
#endif
#endif
#if __has_include(<FirebaseMLCommon/FirebaseMLCommon.h>)
#import <FirebaseMLCommon/FirebaseMLCommon.h>
#endif
#if __has_include(<FirebaseMLModelInterpreter/FirebaseMLModelInterpreter.h>)
#import <FirebaseMLModelInterpreter/FirebaseMLModelInterpreter.h>
#endif

View File

@ -3,6 +3,7 @@ framework module FIRAnalyticsConnector {
module * { export * }
link "sqlite3"
link "z"
link framework "CoreData"
link framework "Security"
link framework "StoreKit"
link framework "SystemConfiguration"

View File

@ -4,6 +4,7 @@ framework module FirebaseAnalytics {
module * { export * }
link "sqlite3"
link "z"
link framework "CoreData"
link framework "Security"
link framework "StoreKit"
link framework "SystemConfiguration"

View File

@ -179,6 +179,7 @@ static NSDictionary *sDefaultOptionsDictionary = nil;
if (newOptions) {
newOptions.optionsDictionary = self.optionsDictionary;
newOptions.deepLinkURLScheme = self.deepLinkURLScheme;
newOptions.appGroupID = self.appGroupID;
newOptions.editingLocked = self.isEditingLocked;
newOptions.usingOptionsFromDefaultPlist = self.usingOptionsFromDefaultPlist;
}
@ -340,6 +341,11 @@ static NSDictionary *sDefaultOptionsDictionary = nil;
_optionsDictionary[kFIRBundleID] = [bundleID copy];
}
- (void)setAppGroupID:(NSString *)appGroupID {
[self checkEditingLocked];
_appGroupID = [appGroupID copy];
}
#pragma mark - Internal instance methods
- (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary {

View File

@ -90,6 +90,13 @@ NS_SWIFT_NAME(FirebaseOptions)
*/
@property(nonatomic, copy, nullable) NSString *storageBucket;
/**
* The App Group identifier to share data between the application and the application extensions.
* The App Group must be configured in the application and on the Apple Developer Portal. Default
* value `nil`.
*/
@property(nonatomic, copy, nullable) NSString *appGroupID;
/**
* Initializes a customized instance of FIROptions from the file at the given plist file path. This
* will read the file synchronously from disk.

View File

@ -70,20 +70,24 @@ Instructions for installing binary frameworks via
## Development
Follow the subsequent instructions to develop, debug, unit test, run integration
tests, and try out reference samples:
To develop Firebase software in this repository, ensure that you have at least
the following software:
```
$ git clone git@github.com:firebase/firebase-ios-sdk.git
$ cd firebase-ios-sdk/Example
$ pod update
$ open Firebase.xcworkspace
```
* Xcode 10.1 (or later)
* CocoaPods 1.7.2 (or later)
For the pod that you want to develop:
`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open`
Firestore and Functions have self contained Xcode projects. See
[Firestore/README.md](Firestore/README.md) and
[Functions/README.md](Functions/README.md).
### Adding a New Firebase Pod
See [AddNewPod.md](AddNewPod.md).
### Code Formatting
To ensure that the code is formatted consistently, run the script
@ -92,9 +96,15 @@ before creating a PR.
Travis will verify that any code changes are done in a style compliant way. Install
`clang-format` and `swiftformat`.
This command will get the right `clang-format` version:
These commands will get the right versions:
`brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/773cb75d360b58f32048f5964038d09825a507c8/Formula/clang-format.rb`
```
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/773cb75d360b58f32048f5964038d09825a507c8/Formula/clang-format.rb
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/3dfea1004e0736754bbf49673cca8aaed8a94089/Formula/swiftformat.rb
```
Note: if you already have a newer version of these installed you may need to
`brew switch` to this version.
### Running Unit Tests
@ -188,9 +198,9 @@ To install, add a subset of the following to the Podfile:
pod 'FirebaseAuth'
pod 'FirebaseCore'
pod 'FirebaseDatabase'
pod 'FirebaseFirestore' # Only iOS and macOS
pod 'FirebaseFirestore'
pod 'FirebaseFunctions'
pod 'FirebaseMessaging' # Only iOS and tvOS
pod 'FirebaseMessaging'
pod 'FirebaseStorage'
```

View File

@ -697,13 +697,7 @@ static FIRInstanceID *gInstanceID;
userInfo:userInfo];
}
// If the firebaseApp is available we should send logs for the error through it before
// raising an exception.
+ (void)exitWithReason:(nonnull NSString *)reason forFirebaseApp:(FIRApp *)firebaseApp {
[firebaseApp sendLogsWithServiceName:kFIRIIDServiceInstanceID
version:FIRInstanceIDCurrentLibraryVersion()
error:[self configureErrorWithReason:reason]];
[NSException raise:kFIRIIDErrorDomain
format:@"Could not configure Firebase InstanceID. %@", reason];
}
@ -946,9 +940,9 @@ static FIRInstanceID *gInstanceID;
object:[self.defaultFCMToken copy]];
[[NSNotificationQueue defaultQueue] enqueueNotification:tokenRefreshNotification
postingStyle:NSPostASAP];
[self performDefaultTokenHandlerWithToken:token error:nil];
}
[self performDefaultTokenHandlerWithToken:token error:nil];
}
};

View File

@ -70,20 +70,24 @@ Instructions for installing binary frameworks via
## Development
Follow the subsequent instructions to develop, debug, unit test, run integration
tests, and try out reference samples:
To develop Firebase software in this repository, ensure that you have at least
the following software:
```
$ git clone git@github.com:firebase/firebase-ios-sdk.git
$ cd firebase-ios-sdk/Example
$ pod update
$ open Firebase.xcworkspace
```
* Xcode 10.1 (or later)
* CocoaPods 1.7.2 (or later)
For the pod that you want to develop:
`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open`
Firestore and Functions have self contained Xcode projects. See
[Firestore/README.md](Firestore/README.md) and
[Functions/README.md](Functions/README.md).
### Adding a New Firebase Pod
See [AddNewPod.md](AddNewPod.md).
### Code Formatting
To ensure that the code is formatted consistently, run the script
@ -92,9 +96,15 @@ before creating a PR.
Travis will verify that any code changes are done in a style compliant way. Install
`clang-format` and `swiftformat`.
This command will get the right `clang-format` version:
These commands will get the right versions:
`brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/773cb75d360b58f32048f5964038d09825a507c8/Formula/clang-format.rb`
```
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/773cb75d360b58f32048f5964038d09825a507c8/Formula/clang-format.rb
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/3dfea1004e0736754bbf49673cca8aaed8a94089/Formula/swiftformat.rb
```
Note: if you already have a newer version of these installed you may need to
`brew switch` to this version.
### Running Unit Tests
@ -188,9 +198,9 @@ To install, add a subset of the following to the Podfile:
pod 'FirebaseAuth'
pod 'FirebaseCore'
pod 'FirebaseDatabase'
pod 'FirebaseFirestore' # Only iOS and macOS
pod 'FirebaseFirestore'
pod 'FirebaseFunctions'
pod 'FirebaseMessaging' # Only iOS and tvOS
pod 'FirebaseMessaging'
pod 'FirebaseStorage'
```

View File

@ -3,6 +3,7 @@ framework module GoogleAppMeasurement {
module * { export * }
link "sqlite3"
link "z"
link framework "CoreData"
link framework "Security"
link framework "StoreKit"
link framework "SystemConfiguration"

View File

@ -14,6 +14,7 @@
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <GoogleUtilities/GULAppDelegateSwizzler.h>
#import <GoogleUtilities/GULMutableDictionary.h>

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, GULSwizzlerMessageCode) {
// App Delegate Swizzling.
kGULSwizzlerMessageCodeAppDelegateSwizzling000 = 1000, // I-SWZ001000

View File

@ -17,7 +17,7 @@
#include <asl.h>
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
#import "Public/GULLoggerLevel.h"
#import <GoogleUtilities/GULLoggerLevel.h>
/// ASL client facility name used by GULLogger.
const char *kGULLoggerASLClientFacilityName = "com.google.utilities.logger";

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
#import <Foundation/Foundation.h>
/**
* The log levels used by internal logging.
*/

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
#import <Foundation/Foundation.h>
// Make sure these codes do not overlap with any contained in the FIRAMessageCode enum.
typedef NS_ENUM(NSInteger, GULNetworkMessageCode) {
// GULNetwork.m

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
#import <Foundation/Foundation.h>
// Make sure these codes do not overlap with any contained in the FIRAMessageCode enum.
typedef NS_ENUM(NSInteger, GULReachabilityMessageCode) {
// GULReachabilityChecker.m

56
ios/Pods/Manifest.lock generated
View File

@ -1,6 +1,6 @@
PODS:
- boost-for-react-native (1.63.0)
- Crashlytics (3.13.2):
- Crashlytics (3.13.4):
- Fabric (~> 1.10.2)
- DoubleConversion (1.1.6)
- EXAppLoaderProvider (6.0.0)
@ -18,24 +18,24 @@ PODS:
- EXWebBrowser (6.0.0):
- UMCore
- Fabric (1.10.2)
- Firebase/Core (6.3.0):
- Firebase/Core (6.5.0):
- Firebase/CoreOnly
- FirebaseAnalytics (= 6.0.2)
- Firebase/CoreOnly (6.3.0):
- FirebaseCore (= 6.0.3)
- FirebaseAnalytics (6.0.2):
- FirebaseCore (~> 6.0)
- FirebaseAnalytics (= 6.0.4)
- Firebase/CoreOnly (6.5.0):
- FirebaseCore (= 6.1.0)
- FirebaseAnalytics (6.0.4):
- FirebaseCore (~> 6.1)
- FirebaseInstanceID (~> 4.2)
- GoogleAppMeasurement (= 6.0.2)
- GoogleAppMeasurement (= 6.0.4)
- GoogleUtilities/AppDelegateSwizzler (~> 6.0)
- GoogleUtilities/MethodSwizzler (~> 6.0)
- GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)"
- nanopb (~> 0.3)
- FirebaseCore (6.0.3):
- FirebaseCore (6.1.0):
- GoogleUtilities/Environment (~> 6.0)
- GoogleUtilities/Logger (~> 6.0)
- FirebaseInstanceID (4.2.0):
- FirebaseInstanceID (4.2.2):
- FirebaseCore (~> 6.0)
- GoogleUtilities/Environment (~> 6.0)
- GoogleUtilities/UserDefaults (~> 6.0)
@ -49,29 +49,29 @@ PODS:
- DoubleConversion
- glog
- glog (0.3.5)
- GoogleAppMeasurement (6.0.2):
- GoogleAppMeasurement (6.0.4):
- GoogleUtilities/AppDelegateSwizzler (~> 6.0)
- GoogleUtilities/MethodSwizzler (~> 6.0)
- GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)"
- nanopb (~> 0.3)
- GoogleUtilities/AppDelegateSwizzler (6.2.1):
- GoogleUtilities/AppDelegateSwizzler (6.2.3):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (6.2.1)
- GoogleUtilities/Logger (6.2.1):
- GoogleUtilities/Environment (6.2.3)
- GoogleUtilities/Logger (6.2.3):
- GoogleUtilities/Environment
- GoogleUtilities/MethodSwizzler (6.2.1):
- GoogleUtilities/MethodSwizzler (6.2.3):
- GoogleUtilities/Logger
- GoogleUtilities/Network (6.2.1):
- GoogleUtilities/Network (6.2.3):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (6.2.1)"
- GoogleUtilities/Reachability (6.2.1):
- "GoogleUtilities/NSData+zlib (6.2.3)"
- GoogleUtilities/Reachability (6.2.3):
- GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (6.2.1):
- GoogleUtilities/UserDefaults (6.2.3):
- GoogleUtilities/Logger
- libwebp (1.0.2):
- libwebp/core (= 1.0.2)
@ -230,7 +230,7 @@ PODS:
- React
- RNVectorIcons (6.4.2):
- React
- RSKImageCropper (2.2.1)
- RSKImageCropper (2.2.3)
- SDWebImage (5.0.6):
- SDWebImage/Core (= 5.0.6)
- SDWebImage/Core (5.0.6)
@ -479,7 +479,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
Crashlytics: 611738c7847f8291a1a51084e35987b86ba6b3ee
Crashlytics: 2dfd686bcb918dc10ee0e76f7f853fe42c7bd552
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
EXAppLoaderProvider: 7a8185228d8ba9e689a0e2d6d957fe9bdd49c8a0
EXConstants: 5d81e84ca71b9a552529889cc798b4a04e9e22b3
@ -488,14 +488,14 @@ SPEC CHECKSUMS:
EXPermissions: 99e52dc3e5f8e55153f1958004f6df2a30a1f2f5
EXWebBrowser: def838b95aa9d396f9ce71ace4e614ee16e7ee30
Fabric: 706c8b8098fff96c33c0db69cbf81f9c551d0d74
Firebase: 8432d732974498afd5987e9001a05f90f1a3d625
FirebaseAnalytics: 470ddab7253b21ad5a40bebd4a9903d7ae19386a
FirebaseCore: 68f8a7f50cdae542715d4e86afa37c4067217dcb
FirebaseInstanceID: f20243a1d828e0e9a3798b995174dedc16f1b32a
Firebase: dedc9e48ea3f3649ad5f6b982f8a0c73508a14b5
FirebaseAnalytics: 3fb375bc9d13779add4039716f868d233a473fad
FirebaseCore: aecf02fb2274ec361b9bebeac112f5daa18273bd
FirebaseInstanceID: 662b8108a09fe9ed01aafdedba100fde8536b0f6
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
GoogleAppMeasurement: a35a645835bae31b6bdc0576396bc23908f12a22
GoogleUtilities: c7a0b08bda3bf808be823ed151f0e28ac6866e71
GoogleAppMeasurement: 183bd916af7f80deb67c01888368f1108d641832
GoogleUtilities: d2b0e277a95962e09bb27f5cd42f5f0b6a506c7d
libwebp: b068a3bd7c45f7460f6715be7bed1a18fd5d6b48
nanopb: 2901f78ea1b7b4015c860c2fdd1ea2fee1a18d48
QBImagePickerController: d54cf93db6decf26baf6ed3472f336ef35cae022
@ -538,7 +538,7 @@ SPEC CHECKSUMS:
RNScreens: f28b48b8345f2f5f39ed6195518291515032a788
RNUserDefaults: 8a4928443510aa99e4ccb3b53f1bf186593d690b
RNVectorIcons: 6607bd3a30291d0edb56f9bbe7ae411ee2b928b0
RSKImageCropper: 98296ad26b41753f796b6898d015509598f13d97
RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a
SDWebImage: 920f1a2ff1ca8296ad34f6e0510a1ef1d70ac965
SDWebImageWebPCoder: 7568737603c50f6237850afedd7e9e28e5917e6b
UMBarCodeScannerInterface: 84ea2d6b58ff0dc27ef9b68bab71286be18ee020

View File

@ -1,4 +1,4 @@
## RSKImageCropper [![Build Status](https://travis-ci.org/ruslanskorb/RSKImageCropper.svg)](https://travis-ci.org/ruslanskorb/RSKImageCropper) [![CocoaPods](https://img.shields.io/cocoapods/dt/RSKImageCropper.svg?maxAge=3600)](https://cocoapods.org/pods/RSKImageCropper) [![Coverage Status](https://coveralls.io/repos/ruslanskorb/RSKImageCropper/badge.svg)](https://coveralls.io/r/ruslanskorb/RSKImageCropper) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/ruslanskorb/RSKImageCropper)
## RSKImageCropper [![Build Status](https://travis-ci.org/ruslanskorb/RSKImageCropper.svg)](https://travis-ci.org/ruslanskorb/RSKImageCropper) [![Coverage Status](https://coveralls.io/repos/ruslanskorb/RSKImageCropper/badge.svg)](https://coveralls.io/r/ruslanskorb/RSKImageCropper) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/ruslanskorb/RSKImageCropper)
<p align="center">
<img src="Screenshot.png" alt="Sample">

View File

@ -29,34 +29,42 @@
// Open Radar - http://www.openradar.me/16744288
// Work around this by redeclaring things here.
#undef cos
#define cos(__x) __tg_cos(__tg_promote1((__x))(__x))
#ifdef __tg_promote1
#undef sin
#define sin(__x) __tg_sin(__tg_promote1((__x))(__x))
#undef cos
#define cos(__x) __tg_cos(__tg_promote1((__x))(__x))
#undef atan2
#define atan2(__x, __y) __tg_atan2(__tg_promote2((__x), (__y))(__x), \
__tg_promote2((__x), (__y))(__y))
#undef sin
#define sin(__x) __tg_sin(__tg_promote1((__x))(__x))
#undef pow
#define pow(__x, __y) __tg_pow(__tg_promote2((__x), (__y))(__x), \
__tg_promote2((__x), (__y))(__y))
#undef sqrt
#define sqrt(__x) __tg_sqrt(__tg_promote1((__x))(__x))
#undef sqrt
#define sqrt(__x) __tg_sqrt(__tg_promote1((__x))(__x))
#undef fabs
#define fabs(__x) __tg_fabs(__tg_promote1((__x))(__x))
#undef fabs
#define fabs(__x) __tg_fabs(__tg_promote1((__x))(__x))
#undef ceil
#define ceil(__x) __tg_ceil(__tg_promote1((__x))(__x))
#undef ceil
#define ceil(__x) __tg_ceil(__tg_promote1((__x))(__x))
#undef floor
#define floor(__x) __tg_floor(__tg_promote1((__x))(__x))
#undef floor
#define floor(__x) __tg_floor(__tg_promote1((__x))(__x))
#undef round
#define round(__x) __tg_round(__tg_promote1((__x))(__x))
#undef round
#define round(__x) __tg_round(__tg_promote1((__x))(__x))
#endif /* __tg_promote1 */
#ifdef __tg_promote2
#undef atan2
#define atan2(__x, __y) __tg_atan2(__tg_promote2((__x), (__y))(__x), \
__tg_promote2((__x), (__y))(__y))
#undef pow
#define pow(__x, __y) __tg_pow(__tg_promote2((__x), (__y))(__x), \
__tg_promote2((__x), (__y))(__y))
#endif /* __tg_promote2 */
#ifdef CGFLOAT_IS_DOUBLE
#define RSK_EPSILON DBL_EPSILON

View File

@ -448,6 +448,7 @@ static const CGFloat kLayoutImageScrollViewAnimationDuration = 0.25;
CGAffineTransform imageScrollViewTransform = self.imageScrollView.transform;
self.imageScrollView.transform = CGAffineTransformIdentity;
CGPoint imageScrollViewContentOffset = self.imageScrollView.contentOffset;
CGRect imageScrollViewFrame = self.imageScrollView.frame;
self.imageScrollView.frame = self.maskRect;
@ -485,6 +486,7 @@ static const CGFloat kLayoutImageScrollViewAnimationDuration = 0.25;
cropRect = CGRectApplyAffineTransform(cropRect, CGAffineTransformMakeScale(imageScale, imageScale));
self.imageScrollView.frame = imageScrollViewFrame;
self.imageScrollView.contentOffset = imageScrollViewContentOffset;
self.imageScrollView.transform = imageScrollViewTransform;
return cropRect;
@ -594,6 +596,7 @@ static const CGFloat kLayoutImageScrollViewAnimationDuration = 0.25;
CGFloat rotation = (rotationAngle - self.rotationAngle);
CGAffineTransform transform = CGAffineTransformRotate(self.imageScrollView.transform, rotation);
self.imageScrollView.transform = transform;
[self layoutImageScrollView];
}
}
@ -630,7 +633,10 @@ static const CGFloat kLayoutImageScrollViewAnimationDuration = 0.25;
- (void)handleRotation:(UIRotationGestureRecognizer *)gestureRecognizer
{
[self setRotationAngle:(self.rotationAngle + gestureRecognizer.rotation)];
CGFloat rotation = gestureRecognizer.rotation;
CGAffineTransform transform = CGAffineTransformRotate(self.imageScrollView.transform, rotation);
self.imageScrollView.transform = transform;
gestureRecognizer.rotation = 0;
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
@ -668,7 +674,6 @@ static const CGFloat kLayoutImageScrollViewAnimationDuration = 0.25;
}
[self resetRotation];
[self resetFrame];
[self resetZoomScale];
[self resetContentOffset];
@ -697,11 +702,6 @@ static const CGFloat kLayoutImageScrollViewAnimationDuration = 0.25;
self.imageScrollView.contentOffset = contentOffset;
}
- (void)resetFrame
{
[self layoutImageScrollView];
}
- (void)resetRotation
{
[self setRotationAngle:0.0];
@ -766,6 +766,25 @@ static const CGFloat kLayoutImageScrollViewAnimationDuration = 0.25;
}
}
- (void)centerImageScrollViewZoomView
{
// center imageScrollView.zoomView as it becomes smaller than the size of the screen
CGPoint contentOffset = self.imageScrollView.contentOffset;
// center vertically
if (self.imageScrollView.contentSize.height < CGRectGetHeight(self.imageScrollView.bounds)) {
contentOffset.y = -(CGRectGetHeight(self.imageScrollView.bounds) - self.imageScrollView.contentSize.height) * 0.5f;
}
// center horizontally
if (self.imageScrollView.contentSize.width < CGRectGetWidth(self.imageScrollView.bounds)) {
contentOffset.x = -(CGRectGetWidth(self.imageScrollView.bounds) - self.imageScrollView.contentSize.width) * 0.5f;;
}
self.imageScrollView.contentOffset = contentOffset;
}
- (void)layoutImageScrollView
{
CGRect frame = CGRectZero;
@ -842,7 +861,10 @@ static const CGFloat kLayoutImageScrollViewAnimationDuration = 0.25;
CGAffineTransform transform = self.imageScrollView.transform;
self.imageScrollView.transform = CGAffineTransformIdentity;
self.imageScrollView.frame = frame;
[self centerImageScrollViewZoomView];
self.imageScrollView.transform = transform;
}

View File

@ -136,41 +136,20 @@
{
// center zoomView as it becomes smaller than the size of the screen
// we need to use contentInset instead of contentOffset for better positioning when zoomView fills the screen
if (self.aspectFill) {
CGFloat top = 0;
CGFloat left = 0;
// center vertically
if (self.contentSize.height < CGRectGetHeight(self.bounds)) {
top = (CGRectGetHeight(self.bounds) - self.contentSize.height) * 0.5f;
}
// center horizontally
if (self.contentSize.width < CGRectGetWidth(self.bounds)) {
left = (CGRectGetWidth(self.bounds) - self.contentSize.width) * 0.5f;
}
self.contentInset = UIEdgeInsetsMake(top, left, top, left);
} else {
CGRect frameToCenter = self.zoomView.frame;
// center horizontally
if (CGRectGetWidth(frameToCenter) < CGRectGetWidth(self.bounds)) {
frameToCenter.origin.x = (CGRectGetWidth(self.bounds) - CGRectGetWidth(frameToCenter)) * 0.5f;
} else {
frameToCenter.origin.x = 0;
}
// center vertically
if (CGRectGetHeight(frameToCenter) < CGRectGetHeight(self.bounds)) {
frameToCenter.origin.y = (CGRectGetHeight(self.bounds) - CGRectGetHeight(frameToCenter)) * 0.5f;
} else {
frameToCenter.origin.y = 0;
}
self.zoomView.frame = frameToCenter;
CGFloat top = 0;
CGFloat left = 0;
// center vertically
if (self.contentSize.height < CGRectGetHeight(self.bounds)) {
top = (CGRectGetHeight(self.bounds) - self.contentSize.height) * 0.5f;
}
// center horizontally
if (self.contentSize.width < CGRectGetWidth(self.bounds)) {
left = (CGRectGetWidth(self.bounds) - self.contentSize.width) * 0.5f;
}
self.contentInset = UIEdgeInsetsMake(top, left, top, left);
}
#pragma mark - Configure scrollView to display new image
@ -231,7 +210,7 @@
if (minScale > maxScale) {
minScale = maxScale;
}
self.maximumZoomScale = maxScale;
self.minimumZoomScale = minScale;
}

View File

@ -1,6 +1,6 @@
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore
GCC_C_LANGUAGE_STANDARD = c99
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 FIRCore_VERSION=6.0.3 Firebase_VERSION=6.3.0
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 FIRCore_VERSION=6.1.0 Firebase_VERSION=6.5.0
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/FirebaseCore" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/FirebaseCore" "${PODS_ROOT}/Headers/Public/GoogleUtilities"
OTHER_CFLAGS = -fno-autolink
PODS_BUILD_DIR = ${BUILD_DIR}

View File

@ -1,6 +1,6 @@
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstanceID
GCC_C_LANGUAGE_STANDARD = c99
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 FIRInstanceID_LIB_VERSION=4.2.0
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 FIRInstanceID_LIB_VERSION=4.2.2
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/FirebaseInstanceID" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/FirebaseCore" "${PODS_ROOT}/Headers/Public/FirebaseInstanceID" "${PODS_ROOT}/Headers/Public/GoogleUtilities"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

View File

@ -737,7 +737,7 @@
$PODS_CONFIGURATION_BUILD_DIR/Firebase,
);
INFOPLIST_FILE = ShareRocketChatRN/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
@ -745,7 +745,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Development chat.rocket.reactnative.ShareExtension";
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
@ -785,14 +785,14 @@
$PODS_CONFIGURATION_BUILD_DIR/Firebase,
);
INFOPLIST_FILE = ShareRocketChatRN/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "chat.rocket.reactnative.ShareExtension AppStore";
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
};

View File

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.17.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSAppTransportSecurity</key>

View File

@ -3688,11 +3688,6 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
base-64@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb"
integrity sha1-eAqZyE59YAJgNhURxId2E78k9rs=
base64-js@^1.0.2, base64-js@^1.1.2, base64-js@^1.2.3:
version "1.3.0"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
@ -6792,18 +6787,6 @@ glob-to-regexp@^0.3.0:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
glob@7.0.6:
version "7.0.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a"
integrity sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.2"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
@ -9934,7 +9917,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.3, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==