Share extension

This commit is contained in:
Diego Mello 2020-05-27 18:18:14 -03:00
parent 009de40200
commit ffda9e528f
9 changed files with 151 additions and 172 deletions

View File

@ -30,6 +30,9 @@ import { KEY_COMMAND } from './commands';
import Tablet, { initTabletNav } from './tablet';
import { SplitContext } from './split';
import AppContainer from './AppContainer';
import TwoFactor from './containers/TwoFactor';
import ScreenLockedView from './views/ScreenLockedView';
import ChangePasscodeView from './views/ChangePasscodeView';
RNScreens.enableScreens();
@ -188,6 +191,9 @@ export default class Root extends React.Component {
}}
>
{content}
<TwoFactor />
<ScreenLockedView />
<ChangePasscodeView />
</ThemeContext.Provider>
</Provider>
</AppearanceProvider>

View File

@ -1,21 +1,12 @@
import { NavigationActions } from '@react-navigation/native';
import * as React from 'react';
let _shareNavigator;
const navigationRef = React.createRef();
function setTopLevelNavigator(navigatorRef) {
_shareNavigator = navigatorRef;
}
function navigate(routeName, params) {
_shareNavigator.dispatch(
NavigationActions.navigate({
routeName,
params
})
);
function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
export default {
navigate,
setTopLevelNavigator
navigationRef,
navigate
};

View File

@ -1,5 +1,6 @@
import React, { useState, useContext } from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import { NavigationContainer } from '@react-navigation/native';
import { AppearanceProvider } from 'react-native-appearance';
import { createStackNavigator } from '@react-navigation/stack';
@ -14,14 +15,12 @@ import {
} from './utils/theme';
import Navigation from './lib/ShareNavigation';
import store from './lib/createStore';
import sharedStyles from './views/Styles';
import { isNotch, isIOS, supportSystemTheme } from './utils/deviceInfo';
import { isIOS, supportSystemTheme } from './utils/deviceInfo';
import { defaultHeader, onNavigationStateChange, themedHeader } from './utils/navigation';
import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat';
import { ThemeContext } from './theme';
// Outside Stack
import AuthLoadingView from './views/AuthLoadingView';
import WithoutServersView from './views/WithoutServersView';
// Inside Stack
@ -33,17 +32,25 @@ const Inside = createStackNavigator();
const InsideStack = () => {
const { theme } = useContext(ThemeContext);
const screenOptions = {
...defaultHeader,
...themedHeader(theme)
};
screenOptions.headerStyle = {
...screenOptions.headerStyle,
// TODO: fix on multiple files PR :)
height: 57
};
return (
<Inside.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme) }}>
<Inside.Navigator screenOptions={screenOptions}>
<Inside.Screen
name='ShareListView'
component={ShareListView}
options={props => ShareListView.navigationOptions({ ...props, theme })}
/>
<Inside.Screen
name='ShareView'
component={ShareView}
options={ShareView.navigationOptions}
/>
<Inside.Screen
name='SelectServerView'
@ -69,48 +76,47 @@ const OutsideStack = () => {
);
};
const AuthContext = React.createContext();
// App
const Stack = createStackNavigator();
export const App = () => {
const [loading] = useState(false);
export const App = ({ root }) => {
if (!root) {
return null;
}
return (
<AuthContext.Provider value={{}}>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{loading ? (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<>
{root === 'outside' ? (
<Stack.Screen
name='AuthLoading'
component={AuthLoadingView}
name='OutsideStack'
component={OutsideStack}
/>
) : (
<>
<Stack.Screen
name='OutsideStack'
component={OutsideStack}
/>
<Stack.Screen
name='InsideStack'
component={InsideStack}
/>
</>
)}
</Stack.Navigator>
</AuthContext.Provider>
) : null}
{root === 'inside' ? (
<Stack.Screen
name='InsideStack'
component={InsideStack}
/>
) : null}
</>
</Stack.Navigator>
);
};
App.propTypes = {
root: PropTypes.string
};
class Root extends React.Component {
constructor(props) {
super(props);
this.state = {
isLandscape: false,
theme: defaultTheme(),
themePreferences: {
currentTheme: supportSystemTheme() ? 'automatic' : 'light',
darkLevel: 'dark'
}
},
root: ''
};
this.init();
}
@ -129,10 +135,10 @@ class Root extends React.Component {
const token = await RNUserDefaults.get(RocketChat.TOKEN_KEY);
if (currentServer && token) {
await Navigation.navigate('InsideStack');
this.setState({ root: 'inside' });
await RocketChat.shareExtensionInit(currentServer);
} else {
await Navigation.navigate('OutsideStack');
this.setState({ root: 'outside' });
}
}
@ -145,32 +151,20 @@ class Root extends React.Component {
});
}
handleLayout = (event) => {
const { width, height } = event.nativeEvent.layout;
this.setState({ isLandscape: width > height });
}
render() {
const { isLandscape, theme } = this.state;
const { theme, root } = this.state;
return (
<AppearanceProvider>
<View
style={[sharedStyles.container, isLandscape && isNotch ? sharedStyles.notchLandscapeContainer : {}]}
onLayout={this.handleLayout}
>
<Provider store={store}>
<ThemeContext.Provider value={{ theme }}>
<NavigationContainer
ref={(navigatorRef) => {
Navigation.setTopLevelNavigator(navigatorRef);
}}
onNavigationStateChange={onNavigationStateChange}
>
<App />
</NavigationContainer>
</ThemeContext.Provider>
</Provider>
</View>
<Provider store={store}>
<ThemeContext.Provider value={{ theme }}>
<NavigationContainer
ref={Navigation.navigationRef}
onNavigationStateChange={onNavigationStateChange}
>
<App root={root} />
</NavigationContainer>
</ThemeContext.Provider>
</Provider>
</AppearanceProvider>
);
}

View File

@ -3,7 +3,6 @@ import { TransitionPresets } from '@react-navigation/stack';
import { analytics, leaveBreadcrumb } from './log';
import { themes } from '../constants/colors';
import { isIOS } from './deviceInfo';
export const defaultHeader = {
headerBackTitleVisible: false,

View File

@ -36,14 +36,14 @@ class SelectServerView extends React.Component {
static propTypes = {
server: PropTypes.string,
navigation: PropTypes.object,
route: PropTypes.object,
theme: PropTypes.string
}
constructor(props) {
super(props);
const { navigation } = this.props;
const servers = navigation.getParam('servers', []);
const { route } = this.props;
const servers = route.params?.servers ?? [];
const filteredServers = servers.filter(server => server.roomsUpdatedAt);
this.state = {
servers: filteredServers

View File

@ -12,7 +12,7 @@ import { Q } from '@nozbe/watermelondb';
import Navigation from '../../lib/ShareNavigation';
import database from '../../lib/database';
import { isIOS, isAndroid } from '../../utils/deviceInfo';
import { isIOS } from '../../utils/deviceInfo';
import I18n from '../../i18n';
import { CustomIcon } from '../../lib/Icons';
import log from '../../utils/log';
@ -35,53 +35,6 @@ const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT
const keyExtractor = item => item.rid;
class ShareListView extends React.Component {
static navigationOptions = ({ route, theme }) => {
const searching = route.params?.searching;
const initSearch = route.params?.initSearch ?? (() => {});
const cancelSearch = route.params?.cancelSearch ?? (() => {});
const search = route.params?.search ?? (() => {});
if (isIOS) {
return {
headerStyle: { backgroundColor: themes[theme].headerBackground },
headerTitle: () => (
<ShareListHeader
searching={searching}
initSearch={initSearch}
cancelSearch={cancelSearch}
search={search}
theme={theme}
/>
)
};
}
return {
headerLeft: () => (searching
? (
<CustomHeaderButtons left>
<Item title='cancel' iconName='cross' onPress={cancelSearch} />
</CustomHeaderButtons>
)
: (
<CancelModalButton
onPress={ShareExtension.close}
testID='share-extension-close'
/>
)),
headerTitle: () => <ShareListHeader searching={searching} search={search} theme={theme} />,
headerRight: () => (
searching
? null
: (
<CustomHeaderButtons>
{isAndroid ? <Item title='search' iconName='magnifier' onPress={initSearch} /> : null}
</CustomHeaderButtons>
)
)
};
}
static propTypes = {
navigation: PropTypes.object,
server: PropTypes.string,
@ -107,18 +60,13 @@ class ShareListView extends React.Component {
loading: true,
serverInfo: null
};
this.setHeader();
this.didFocusListener = props.navigation.addListener('didFocus', () => BackHandler.addEventListener('hardwareBackPress', this.handleBackPress));
this.willBlurListener = props.navigation.addListener('willBlur', () => BackHandler.addEventListener('hardwareBackPress', this.handleBackPress));
}
componentDidMount() {
const { navigation, server } = this.props;
navigation.setParams({
initSearch: this.initSearch,
cancelSearch: this.cancelSearch,
search: this.search
});
const { server } = this.props;
setTimeout(async() => {
try {
const { value, type } = await ShareExtension.data();
@ -181,6 +129,51 @@ class ShareListView extends React.Component {
return false;
}
setHeader = () => {
const { searching } = this.state;
const { navigation, theme } = this.props;
if (isIOS) {
navigation.setOptions({
header: () => (
<ShareListHeader
searching={searching}
initSearch={this.initSearch}
cancelSearch={this.cancelSearch}
search={this.search}
theme={theme}
/>
)
});
return;
}
navigation.setOptions({
headerLeft: () => (searching
? (
<CustomHeaderButtons left>
<Item title='cancel' iconName='cross' onPress={this.cancelSearch} />
</CustomHeaderButtons>
)
: (
<CancelModalButton
onPress={ShareExtension.close}
testID='share-extension-close'
/>
)),
headerTitle: () => <ShareListHeader searching={searching} search={this.search} theme={theme} />,
headerRight: () => (
searching
? null
: (
<CustomHeaderButtons>
<Item title='search' iconName='magnifier' onPress={this.initSearch} />
</CustomHeaderButtons>
)
)
});
}
// eslint-disable-next-line react/sort-comp
internalSetState = (...args) => {
const { navigation } = this.props;
@ -259,15 +252,11 @@ class ShareListView extends React.Component {
initSearch = () => {
const { chats } = this.state;
const { navigation } = this.props;
this.setState({ searching: true, searchResults: chats });
navigation.setParams({ searching: true });
this.setState({ searching: true, searchResults: chats }, () => this.setHeader());
}
cancelSearch = () => {
const { navigation } = this.props;
this.internalSetState({ searching: false, searchResults: [], searchText: '' });
navigation.setParams({ searching: false });
this.internalSetState({ searching: false, searchResults: [], searchText: '' }, () => this.setHeader());
Keyboard.dismiss();
}

View File

@ -18,29 +18,9 @@ import { isReadOnly } from '../../utils/isReadOnly';
import { withTheme } from '../../theme';
class ShareView extends React.Component {
static navigationOptions = ({ route }) => {
const canSend = route.params?.canSend ?? true;
return ({
title: I18n.t('Share'),
headerRight:
() => (canSend
? (
<CustomHeaderButtons>
<Item
title={I18n.t('Send')}
onPress={route.params?.sendMessage}
testID='send-message-share-view'
buttonStyle={styles.send}
/>
</CustomHeaderButtons>
)
: null)
});
}
static propTypes = {
navigation: PropTypes.object,
route: PropTypes.object,
theme: PropTypes.string,
user: PropTypes.shape({
id: PropTypes.string.isRequired,
@ -52,13 +32,13 @@ class ShareView extends React.Component {
constructor(props) {
super(props);
const { navigation } = this.props;
const rid = navigation.getParam('rid', '');
const name = navigation.getParam('name', '');
const value = navigation.getParam('value', '');
const isMedia = navigation.getParam('isMedia', false);
const fileInfo = navigation.getParam('fileInfo', {});
const room = navigation.getParam('room', { rid });
const { route } = this.props;
const rid = route.params?.rid;
const name = route.params?.name;
const value = route.params?.value;
const isMedia = route.params?.isMedia ?? false;
const fileInfo = route.params?.fileInfo ?? {};
const room = route.params?.room ?? { rid };
this.state = {
rid,
@ -72,30 +52,49 @@ class ShareView extends React.Component {
file: {
name: fileInfo ? fileInfo.name : '',
description: ''
}
},
canSend: false
};
this.setReadOnly();
this.setHeader();
}
componentDidMount() {
setHeader = () => {
const { canSend } = this.state;
const { navigation } = this.props;
navigation.setParams({ sendMessage: this._sendMessage });
navigation.setOptions({
title: I18n.t('Share'),
headerRight:
() => (canSend
? (
<CustomHeaderButtons>
<Item
title={I18n.t('Send')}
onPress={this.sendMessage}
testID='send-message-share-view'
buttonStyle={styles.send}
/>
</CustomHeaderButtons>
)
: null)
});
}
setReadOnly = async() => {
const { room } = this.state;
const { navigation, user } = this.props;
const { user } = this.props;
const { username } = user;
const readOnly = await isReadOnly(room, { username });
this.setState({ readOnly });
navigation.setParams({ canSend: !(readOnly || isBlocked(room)) });
this.setState({ readOnly, canSend: !(readOnly || isBlocked(room)) });
this.setHeader();
}
bytesToSize = bytes => `${ (bytes / 1048576).toFixed(2) }MB`;
_sendMessage = async() => {
sendMessage = async() => {
const { isMedia, loading } = this.state;
if (loading) {
return;

View File

@ -31,6 +31,7 @@ const styles = StyleSheet.create({
class WithoutServerView extends React.Component {
static navigationOptions = {
title: 'Rocket.Chat',
headerLeft: () => (
<CancelModalButton
onPress={ShareExtension.close}

View File

@ -18,7 +18,7 @@ if (__DEV__) {
}
AppRegistry.registerComponent(appName, () => require('./app/index.js').default);
// AppRegistry.registerComponent(shareName, () => require('./app/share.js').default);
AppRegistry.registerComponent(shareName, () => require('./app/share.js').default);
// For storybook, comment everything above and uncomment below
// import './storybook';